构建媒体滚动条组件

有关如何为电视、手机、桌面设备等设备构建自适应水平滚动视图的基础概览。

在本文中,我想分享一些关于如何为 Web 打造横向滚动体验的想法,这些体验应尽可能简洁、响应迅速、易于访问,并且能够在各种浏览器和平台(例如电视!)上正常运行。试用演示版

演示

如果您更喜欢视频,请观看此帖子的 YouTube 版本:

概览

我们将构建一个水平滚动布局,用于托管媒体或商品的缩略图。该组件最初只是一个简单的 <ul> 列表,但通过 CSS 的转换,它可以提供令人满意的流畅滚动体验,展示图片并将其贴靠到网格中。添加了 JavaScript 以便于导航索引交互,帮助键盘用户跳过 100 多个项的遍历。此外,实验性媒体查询 prefers-reduced-data 用于将媒体滚动条转换为轻量级标题滚动条体验。

从无障碍标记开始

媒体滚动条仅由几个核心组件组成,即包含项的列表。列表是最简单的形式,可以传播到世界各地,并且所有人都能清晰地使用。用户访问此页面后,可以浏览列表并点击链接查看商品。这是我们的无障碍基地。

提交包含 <ul> 元素的列表:

<ul class="horizontal-media-scroller">
  <li></li>
  <li></li>
  <li></li>
  ...
<ul>

使用 <a> 元素使列表项具有交互性:

<li>
  <a href="#">
    ...
  </a>
</li>

使用 <figure> 元素以语义方式表示图片及其说明:

<figure>
  <picture>
    <img alt="..." loading="lazy" src="https://picsum.photos/500/500?1">
  </picture>
  <figcaption>Legends</figcaption>
</figure>

请注意 <img> 上的 altloading 属性。媒体滚动条的替代文本是一个用户体验改进机会,可帮助为缩略图提供额外的上下文,或者在图片未加载时用作后备文本,或者为依赖辅助技术(例如屏幕阅读器)的用户提供语音界面。如需了解详情,请参阅打造合规替代文本的五条黄金法则

loading 属性接受关键字 lazy,以指示仅当图片位于视口内时才应提取此图片来源。对于大型列表,这非常有用,因为用户只会下载滚动到视野范围内的内容的图片。

支持用户的配色方案偏好设置

color-scheme 用作 <meta> 标记,以向浏览器发出信号,表明您的网页同时需要提供浅色和深色用户代理样式。这是一种免费的深色模式或浅色模式,具体取决于您如何看待:

<meta name="color-scheme" content="dark light">

元标记会尽早提供信号,因此如果用户的偏好设置为深色主题,浏览器就可以选择深色默认画布颜色。这意味着,在网站的网页之间导航时,在加载期间不会闪烁白色画布背景。在加载期间无缝切换深色主题,看起来更舒服。

如需详细了解,请访问 https://web.dev/color-scheme/,观看 Thomas Steiner 的演讲。

添加内容

鉴于 ul > li > a > figure > picture > img 的上述内容结构,下一个任务是添加可滚动的图片和标题。我已在演示中添加了静态占位符图片和文本,但您可以随意使用自己偏好的数据源。

使用 CSS 添加样式

现在,是时候 CSS 获取这个通用内容列表了,并将其转化为一种体验。Netflix、App Store 以及许多其他网站和应用均使用水平滚动区域来在视口中打包各种类别和选项。

创建滚动条布局

请务必避免截断布局中的内容,也不要过多依赖于使用省略号截断文本。许多电视机都具有与此类似的媒体滚动条,但都经常会采用省略内容的方式。此布局不支持! 它还允许媒体内容替换列大小,使 1 个布局足够灵活,可以处理许多有趣的组合。

显示了 2 个滚动行。其中一个标题没有省略号,这意味着标题更高,并且每个标题都完全清晰可辨。另一种是较短的,许多标题会被省略号截断。

该容器允许通过提供默认大小作为自定义属性来替换列大小。此网格布局对列大小有自己的看法,它只会管理间距和方向:

.horizontal-media-scroller {
  --size: 150px;

  display: grid;
  grid-auto-flow: column;
  gap: calc(var(--gap) / 2); /* parent owned value for children to be relative to*/
  margin: 0;
}

然后,<picture> 元素使用该自定义属性创建基本宽高比:一个框:

.horizontal-media-scroller {
  --size: 150px;

  display: grid;
  grid-auto-flow: column;
  gap: calc(var(--gap) / 2);
  margin: 0;

  & picture {
    inline-size: var(--size);
    block-size: var(--size);
  }
}

只需再添加一些次要样式,即可完成媒体滚动条的基本框架:

.horizontal-media-scroller {
  --size: 150px;

  display: grid;
  grid-auto-flow: column;
  gap: calc(var(--gap) / 2);
  margin: 0;

  overflow-x: auto;
  overscroll-behavior-inline: contain;

  & > li {
    display: inline-block; /* removes the list-item bullet */
  }

  & picture {
    inline-size: var(--size);
    block-size: var(--size);
  }
}

设置 overflow 会设置 <ul>,以允许滚动和使用键盘导航其列表,然后通过获取新的显示类型 inline-block 移除每个直接子 <li> 元素的 ::marker

不过,图片尚不支持响应式功能,并且会直接从其所在的框中弹出。您可以使用一些尺寸、适合度和边框样式来控制它们,并在延迟加载时为其添加渐变背景:

img {
  /* smash into whatever box it's in */
  inline-size: 100%;
  block-size: 100%;

  /* don't squish but do cover the space */
  object-fit: cover;

  /* soften the edges */
  border-radius: 1ex;
  overflow: hidden;

  /* if empty, show a gradient placeholder */
  background-image:
    linear-gradient(
      to bottom,
      hsl(0 0% 40%),
      hsl(0 0% 20%)
    );
}

滚动内边距

与网页内容对齐以及边到边滚动 Surface 区域对于打造和谐且极简的组件至关重要。

如需实现与排版和布局线对齐的边到边滚动布局,请使用与 scroll-padding 匹配的 padding

.horizontal-media-scroller {
  --size: 150px;

  display: grid;
  grid-auto-flow: column;
  gap: calc(var(--gap) / 2);
  margin: 0;

  overflow-x: auto;
  overscroll-behavior-inline: contain;

  padding-inline: var(--gap);
  scroll-padding-inline: var(--gap);
  padding-block: calc(var(--gap) / 2); /* make space for scrollbar and focus outline */
}

修复了水平滚动内边距 bug 上文展示了如何轻松为滚动容器设置内边距,但存在一些未解决的兼容性问题(不过在 Chromium 91 及更高版本中已修复!)。如需了解相关历史信息,请参阅此处。简而言之,滚动视图中并不总是会考虑内边距。

最后一个列表项的内嵌端会突出显示一个方框,显示内边距和元素的宽度与创建所需的对齐方式相同。

为了欺骗浏览器将内边距放置在滚动条的末尾,我将定位到每个列表中的最后一个数字,并附加一个等于所需内边距大小的伪元素。

.horizontal-media-scroller > li:last-of-type figure {
  position: relative;

  &::after {
    content: "";
    position: absolute;

    inline-size: var(--gap);
    block-size: 100%;

    inset-block-start: 0;
    inset-inline-end: calc(var(--gap) * -1);
  }
}

使用逻辑属性可让媒体滚动条在任何写入模式和文档方向下正常运行。

滚动捕捉

具有 overflow 的滚动容器可以成为具有一行 CSS 的贴靠视口,然后由子元素指定它们与该视口的对齐方式。

.horizontal-media-scroller {
  --size: 150px;

  display: grid;
  grid-auto-flow: column;
  gap: calc(var(--gap) / 2);
  margin: 0;

  overflow-x: auto;
  overscroll-behavior-inline: contain;

  padding-inline: var(--gap);
  scroll-padding-inline: var(--gap);
  padding-block-end: calc(var(--gap) / 2);

  scroll-snap-type: inline mandatory;

  & figure {
    scroll-snap-align: start;
  }
}

专注

此组件的灵感来自于它在电视、应用商店等平台上的广泛普及。许多视频游戏平台都将与此类似的媒体滚动条用作主要的主屏幕布局。聚焦功能是一项重要的用户体验,而不仅仅是一项小小的附加功能。假设您坐在沙发上使用遥控器操作此媒体滚动条,那么您可以对此互动进行一些小改进:

.horizontal-media-scroller a {
  outline-offset: 12px;

  &:focus {
    outline-offset: 7px;
  }

  @media (prefers-reduced-motion: no-preference) {
    & {
      transition: outline-offset .25s ease;
    }
  }
}

这会将焦点轮廓样式 7px 设置为远离框,为其留出一些空间。如果用户没有关于减少动作的偏好设置,系统会转换偏移量,为焦点事件提供细微的动作。

漫游索引

在这些包含大量滚动内容和选项的长列表中,需要特别注意游戏手柄和键盘用户。用于解决此问题的常见模式称为巡回索引。当项的容器获得键盘焦点,但一次只能有 1 个子项保持焦点时,就会发生这种情况。每次点按一个可聚焦项的体验旨在绕过可能很长的项列表,而不是按 Tab 键 50 多次将焦点移到最后。

演示的第一个滚动条中包含 300 项。我们可以采取更好的方法,而不是让它们遍历所有这些节点来到达下一部分。

为了实现这种体验,JavaScript 需要监听键盘事件和焦点事件。我在 npm 上创建了一个小型开源库,以帮助轻松实现这种用户体验。以下是针对 3 个滚动条的使用方法:

import {rovingIndex} from 'roving-ux';

rovingIndex({
  element: someElement
});

此演示会查询文档中的滚动条,并针对每个滚动条调用 rovingIndex() 函数。将 rovingIndex() 传递给元素以获得漫游体验,例如列表容器,并传递目标查询选择器,以防焦点目标不是直接子孙。

document.querySelectorAll('.horizontal-media-scroller')
  .forEach(scroller =>
    rovingIndex({
      element: scroller,
      target: 'a',
}))

如需详细了解此效果,请参阅开源库 roving-ux

aspect-ratio

在撰写这篇文章时,Firefox 中aspect-ratio 的支持需要通过标志启用,但 Chromium 浏览器或设置顶部框中提供此支持。由于媒体滚动条网格布局仅指定方向和间距,因此大小可以在媒体查询内更改,该功能用于检查是否支持宽高比。对一些更具动态性的媒体滚动条进行了渐进式增强。

宽高比为 4:4 的框显示在使用 16:9 和 4:3 的其他设计宽高比旁边

@supports (aspect-ratio: 1) {
  .horizontal-media-scroller figure > picture {
    inline-size: auto; /* for a block-size driven ratio */
    aspect-ratio: 1; /* boxes by default */

    @nest section:nth-child(2) & {
      aspect-ratio: 16/9;
    }

    @nest section:nth-child(3) & {
      /* double the size of the others */
      block-size: calc(var(--size) * 2);
      aspect-ratio: 4/3;

      /* adjust size to fit more items into the viewport */
      @media (width <= 480px) {
        block-size: calc(var(--size) * 1.5);
      }
    }
  }
}

如果浏览器支持 aspect-ratio 语法,则媒体滚动图片会升级为 aspect-ratio 大小。使用草稿嵌套语法时,每张图片都会根据其宽高比是第一行、第二行还是第三行。借助嵌套语法,您还可以使用其他大小调整逻辑直接设置一些细微的视口调整。

由于该功能在更多浏览器引擎中可用,因此使用该 CSS 时,系统会呈现易于管理但视觉效果更出色的布局。

首选减少数据

虽然下一项技术仅适用于 Canary 中的标志,但我想分享一下如何通过几行 CSS 节省大量网页加载时间和数据使用。第 5 级prefers-reduced-data 媒体查询允许询问设备是否处于任何数据流量减少状态(例如流量节省模式)。如果是,我可以修改文档 在本示例中,可以隐藏图片

ALT_TEXT_HERE

figure {
  @media (prefers-reduced-data: reduce) {
    & {
      min-inline-size: var(--size);

      & > picture {
        display: none;
      }
    }
  }
}

内容仍然可导航,但无需下载大量图片。以下是添加 prefers-reduced-data CSS 之前的网站:

(7 个请求,131 毫秒内使用 100kb 的资源)

ALT_TEXT_HERE

添加 prefers-reduced-data CSS 后的网站性能如下所示:

ALT_TEXT_HERE

(71 个请求,1.07 秒内使用了 1.2MB 的资源)

减少了 64 个请求,即此浏览器标签页视口中大约 60 张图片(在宽屏显示屏上进行的测试),网页加载速度提高了约 80%,通过网络传输的数据减少了 10%。非常强大的 CSS。

总结

你知道我怎么做到的了,你会怎么做?!🙂

让我们一起采用多样化的方法,学习在 Web 上构建应用的所有方法。 创建一个 Codepen 或托管自己的演示,然后在推特上向我发送,我会将其添加到下方的“社区混剪作品”部分。

来源

社区混剪作品

此处尚无可显示的内容!