构建媒体滚动条组件

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

在这篇博文中,我想分享一些想法,介绍如何为网页创建极简、自适应、无障碍且可在各种浏览器和平台(如电视!)上运行的横向滚动体验。试用演示版

演示

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

概览

我们将构建一个水平滚动布局,用于托管媒体或产品的缩略图。该组件最初是一个简单的 <ul> 列表,但通过 CSS 转换为令人满意的流畅滚动体验,可展示图片并将其贴靠到网格。添加了 JavaScript 以方便实现 roving-index 互动,帮助键盘用户跳过遍历 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 属性。媒体滚动器的替代文本是一个 UX 机会,可用于提供缩略图的额外背景信息,或作为图片未加载时的后备文本,或为依赖辅助技术(例如屏幕阅读器)的用户提供语音界面。如需了解详情,请参阅有关合规替代文字的五条黄金法则

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

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

使用 color-scheme 作为 <meta> 标记,向浏览器表明您的网页需要浅色和深色用户代理样式。这是一种免费的深色模式或浅色模式,具体取决于您看待事物的角度:

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

元标记可提供最早的信号,因此如果用户偏好深色主题,浏览器可以选择深色默认画布颜色。这意味着,在加载时,网站页面之间的导航不会闪烁白色画布背景。加载时深色主题无缝切换,对眼睛更友好。

如需详细了解,请访问 Thomas Steiner 的博客:https://web.dev/color-scheme/

添加内容

鉴于 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 及更高版本中修复!)。如需了解相关历史记录,请参阅此处,但简而言之,滚动视图中并非始终会考虑内边距。

最后一个列表项的内联末尾侧突出显示了一个框,表明内边距和元素的宽度相同,从而实现所需的对齐效果。

为了让浏览器将内边距放在滚动条的末尾,我将定位每个列表中的最后一个 figure,并附加一个伪元素,该伪元素就是所需的内边距量。

.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

宽高比

截至撰写此博文时,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 代码节省大量网页加载时间和数据使用量。级别 5prefers-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 毫秒内加载了 100 KB 的资源)

ALT_TEXT_HERE

添加 prefers-reduced-data CSS 后的网站性能:

ALT_TEXT_HERE

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

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

总结

现在您已经知道我是如何做到的,那么您会怎么做呢?🙂

让我们丰富方法,了解在网络上构建内容的所有方式。 创建 Codepen 或自行托管演示,通过 Twitter 私信发送给我,我会将其添加到下方的“社区混音”部分。

来源

社区混音作品

此处尚无可显示的内容!