构建媒体滚动条组件

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

在本文中,我想分享一些关于如何为 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、应用商店以及许多其他网站和应用都使用水平滚动区域,以便在视口中填充类别和选项。

创建滚动条布局

请务必避免截断布局中的内容,也不要过多依赖于使用省略号截断文本。许多电视机都具有与此类似的媒体滚动条,但都经常会采用省略内容的方式。此布局不支持! 它还允许媒体内容替换列大小,使 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%)
    );
}

滚动内边距

与页面内容对齐以及无边框滚动区域对于打造一个协调的最小组件至关重要。

如需实现与我们的排版和布局线一致的全屏滚动布局,请使用与 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 及更高版本中已修复!)。如需了解相关历史信息,请参阅此处。简而言之,滚动视图中并不总是会考虑内边距。

最后一个列表项的 inline-end 边上突出显示了一个框,表示内边距和元素的宽度相同,以便实现所需的对齐方式。

为了诱使浏览器将内边距放置在滚动条的末尾,我将定位每个列表中的最后一个图形,并附加一个伪元素(该伪元素是所需的内边距大小)。

.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);
  }
}

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

滚动捕捉

一行 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;
  }
}

专注

此组件的灵感来源于它在电视、App Store 等平台上的巨大人气。许多视频游戏平台都使用与其非常相似的媒体滚动条作为其主要主屏幕布局。聚焦功能是一项重要的用户体验,而不仅仅是一项小小的附加功能。假设您坐在沙发上使用遥控器操作此媒体滚动条,那么您可以对此互动进行一些小改进:

.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 或托管自己的演示,然后在推特上向我发送,我会将其添加到下方的“社区混剪作品”部分。

来源

社区混剪作品

此处尚无可显示的内容!