构建媒体滚动条组件

简要介绍了如何为电视、手机、桌面设备等构建自适应横向滚动视图的基础知识。

在本文中,我想分享一些关于如何为 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%)
    );
}

滚动内边距

与网页内容对齐以及边到边滚动 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 及更高版本中已修复!)。如需了解相关历史信息,请参阅此处。简而言之,滚动视图中并不总是会考虑内边距。

最后一个列表项的 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;
  }
}

专注

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

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

来源

社区混剪作品

此处尚无可显示的内容!