关于如何构建类似于 iOS 和 Android 应用中的标签页组件的基本概述。
在这篇博文中,我想分享关于构建适用于 Web 的标签页组件的想法 响应式、支持多种设备输入并可跨浏览器运行。 试用演示版。
<ph type="x-smartling-placeholder">如果你更喜欢视频,可以参阅此博文的 YouTube 版本:
概览
标签页是设计系统的常见组件,但可以有多种形状和
表单。最初,桌面标签页是基于 <frame>
元素构建的,现在我们有了
基于物理特性为内容添加动画效果的黄色移动组件。
它们都想方设法做同样的事情:节省空间。
如今,标签页用户体验的基本要素就是按钮导航区域 用于控制内容在显示框架中的可见性。许多不同的 这些内容区域共用同一个空间,但会根据不同的内容有条件地 按钮。
<ph type="x-smartling-placeholder">网络策略
总而言之,我发现这个组件的构建非常简单,这要归功于 几项重要的 Web 平台功能:
scroll-snap-points
,可让您顺畅地通过滑动和键盘与以下对象进行交互: 相应的滚动停止位置- 通过网址哈希实现深层链接 浏览器处理的页内滚动锚定和共享支持
- 屏幕阅读器支持
<a>
和id="#hash"
元素标记 prefers-reduced-motion
,用于启用淡入淡出过渡和即时效果 页内滚动- 草稿版
@scroll-timeline
Web 功能,用于动态添加下划线和 更改所选标签页的颜色
HTML
从根本上说,用户体验就是:点击链接,让网址代表嵌套式页面 页面状态,然后看到内容区域在浏览器滚动到 匹配元素。
其中包含一些结构化内容成员:链接和 :target
。周三
需要一个 <nav>
非常适合的链接列表,以及一个 <article>
列表
元素,这是 <section>
的理想之选。每个链接哈希都会匹配一个部分
让浏览器通过锚定滚动网页
例如,点击链接会自动聚焦于以下部分中的 :target
文章:
Chrome 89,无需 JS。然后,用户可以滚动浏览文章内容,
输入设备它是免费内容,如
标记。
我使用以下标记来整理标签页:
<snap-tabs>
<header>
<nav>
<a></a>
<a></a>
<a></a>
<a></a>
</nav>
</header>
<section>
<article></article>
<article></article>
<article></article>
<article></article>
</section>
</snap-tabs>
我可以使用以下代码在 <a>
和 <article>
元素之间建立连接:
href
和 id
属性,如下所示:
<snap-tabs>
<header>
<nav>
<a href="#responsive"></a>
<a href="#accessible"></a>
<a href="#overscroll"></a>
<a href="#more"></a>
</nav>
</header>
<section>
<article id="responsive"></article>
<article id="accessible"></article>
<article id="overscroll"></article>
<article id="more"></article>
</section>
</snap-tabs>
接下来,我在文章中加入了混合的洛勒姆元素,并用不同的颜色标记链接 混合长度和图片形式的标题。有了合适的内容后,我们就可以开始 布局。
滚动布局
此组件中有 3 种不同类型的滚动区域:
- 导航 (粉色)是水平导航 可滚动
- 内容区域(蓝色)处于水平状态 可滚动
- 每个报道项(绿色)垂直显示 可滚动。
滚动涉及到两种不同类型的元素:
- 窗口
一个具有已定义尺寸的框,其中overflow
属性样式。 - 超大 Surface
在此布局中,它是列表容器:nav 链接、版块文章和文章内容。
“<snap-tabs>
”布局
我选择的顶级布局是 flex (Flexbox)。我已将方向设为
column
,因此标题和部分垂直排序。这是我们推出的第一个
滚动窗口,它会隐藏所有内容,同时隐藏溢出。标头和
部分很快将采用滚动,作为单个区域。
<snap-tabs> <header></header> <section></section> </snap-tabs>
snap-tabs { display: flex; flex-direction: column; /* establish primary containing box */ overflow: hidden; position: relative; & > section { /* be pushy about consuming all space */ block-size: 100%; } & > header { /* defend againstneeding 100% */ flex-shrink: 0; /* fixes cross browser quarks */ min-block-size: fit-content; } }
再次回到彩色的 3 滚动图:
<header>
现已准备好成为 (粉色) 滚动容器。<section>
已准备好成为(蓝色)滚动 容器。
我在下面突出显示的这些帧 VisBug帮助我们查看窗口 创建的所有滚动容器
标签页 <header>
布局
下一个布局几乎相同:我使用 flex 来创建垂直排序。
<snap-tabs> <header> <nav></nav> <span class="snap-indicator"></span> </header> <section></section> </snap-tabs>
header { display: flex; flex-direction: column; }
.snap-indicator
应与链接组沿水平方向移动,
此标题布局可帮助您做好万全准备。此处没有绝对定位元素!
接下来是滚动样式原来我们可以分享滚动样式
两个水平滚动区域(标题和部分)之间,因此我构建了一个实用程序,
类 .scroll-snap-x
。
.scroll-snap-x {
/* browser decide if x is ok to scroll and show bars on, y hidden */
overflow: auto hidden;
/* prevent scroll chaining on x scroll */
overscroll-behavior-x: contain;
/* scrolling should snap children on x */
scroll-snap-type: x mandatory;
@media (hover: none) {
scrollbar-width: none;
&::-webkit-scrollbar {
width: 0;
height: 0;
}
}
}
每个元素需要在 x 轴上溢出,滚动包含条件来阻止滚动,处于隐藏状态 为触摸设备设置滚动条,最后使用滚动贴靠功能锁定内容 演示区域。键盘标签页顺序易于访问,并且提供了任何互动指南 能够自然地专注滚动贴靠容器也采用了很棒的轮播样式 与键盘进行的互动。
标签页标题 <nav>
布局
导航链接需要垂直布局,不要换行 每个链接项都应与滚动条扣容器对齐。Swift 适用于 2021 CSS!
<nav> <a></a> <a></a> <a></a> <a></a> </nav>
nav { display: flex; & a { scroll-snap-align: start; display: inline-flex; align-items: center; white-space: nowrap; } }
每个链接的样式和大小本身,因此导航布局只需指定 导航项具有独特的宽度,因而可在标签页之间转换 因为指示器会根据新的目标调整自身宽度,所以非常有趣。取决于 元素是否在此处,浏览器是否会呈现滚动条。
标签页 <section>
布局
此版块属于弹性商品,需要占据主要的空间。它
还需要为要放置的文章创建列。再次快速
适用于 CSS 2021!block-size: 100%
会拉伸此元素,以填充
那么对于自己的布局,它会创建一系列
宽度为 100%
且宽度为父项的列。百分比在这里效果很好
因为我们已经为父级设定了强有力的约束条件。
<section> <article></article> <article></article> <article></article> <article></article> </section>
section { block-size: 100%; display: grid; grid-auto-flow: column; grid-auto-columns: 100%; }
这就好比是在说“以一种强迫的方式尽可能垂直展开”
(请注意,我们将标头设置为 flex-shrink: 0
,这是
展开推移),用于为一组全高列设置行高。通过
auto-flow
样式指示网格始终以水平方向排列子项
没有换行,这正是我们想要的溢出父窗口。
我觉得这些东西有时很难包起来!此部分元素为 同时也创建了一组箱子。我希望这些视觉元素 解释对我有所帮助。
标签页 <article>
布局
用户应能够滚动文章内容,且滚动条应 只有在溢出时才会显示。这些文章元素整齐地 排名。它们同时是滚动父级和滚动子级。通过 浏览器确实会处理一些棘手的触摸、鼠标和键盘互动
<article> <h2></h2> <p></p> <p></p> <h2></h2> <p></p> <p></p> ... </article>
article { scroll-snap-align: start; overflow-y: auto; overscroll-behavior-y: contain; }
我选择让报道在父级滚动条中贴靠。我真的很喜欢 导航链接项和文章元素如何与 inline-start 对齐 滚动容器的效果看起来和感觉和谐的 关系。
文章是一个网格子项,其大小已预先确定为视口 我们希望提供滚动用户体验的方面也就是说,不需要指定高度或宽度 因此我需要定义其溢出方式。我将“overflow-y”设为“auto” 然后利用便捷的滚动行为来捕获滚动互动 属性。
回顾 3 个滚动区域
下方,我在系统设置中选择了“始终显示滚动条”。我觉得 打开此设置后,布局功能也就显得更为重要 查看布局和滚动编排
我认为在此组件中看到滚动条边线有助于清晰地显示 滚动区域的位置、支持的方向以及滚动区域的方式 相互交流。考虑一下每个滚动窗口框架的柔性或 grid 父项添加到布局中。
开发者工具可以帮助我们直观地了解这一点:
<ph type="x-smartling-placeholder">滚动布局已完成:贴靠、可深层链接、键盘 可访问性。为提升用户体验、打造风格和打造愉悦的用户体验奠定坚实基础。
功能亮点
在调整大小的过程中,滚动对齐的子节点会保持锁定位置。这意味着 JavaScript 不需要在设备旋转或浏览器上呈现任何内容 调整大小。在 Chromium 开发者工具中试用设备 Mode由 选择除自适应以外的任何模式,然后调整设备框架的大小。 请注意,该元素始终显示在视图中,并与其内容一起锁定。这是 可用,因为 Chromium 更新了其实现以符合规范。以下是 相关的博文。
动画
此处动画工作的目的是明确将互动与界面相关联 反馈。这有助于引导或协助用户(希望) 让用户能顺畅地发现所有内容。我会有目的地添加动态效果 。用户现在可以指定其动作 偏好设置, 而且我非常喜欢在我的界面中根据他们的偏好做出回应。
我要将标签下划线与报道滚动位置相关联。“贴靠”功能不
它还可以锚定动画的开始和结束
这会保留 <nav>
,其作用类似于
迷你地图,与内容相关联。
我们将从 CSS 和 JS 检查用户的动作偏好设置。这里有
几个值得考虑的好地方!
滚动行为
有机会增强 :target
和
element.scrollIntoView()
。默认情况下,它是即时的。浏览器只会将
滚动位置。如果我们要过渡到该滚动位置,
而不是眨眼的那一刻?
@media (prefers-reduced-motion: no-preference) {
.scroll-snap-x {
scroll-behavior: smooth;
}
}
由于我们在此处引入运动,以及用户无法控制的运动 (与滚动一样),仅当用户在滚动浏览模式时 其操作系统的动作减少。这样,我们就只 并制作一些动画效果。
标签页指示符
此动画的目的是帮助将指示器与状态相关联
内容。我决定为用户提供淡入淡出的border-bottom
样式
以及一个滚动链接的滑动 + 颜色淡入淡出动画
适合乐于运动的用户。
在 Chromium Devtools 中,我可以切换偏好设置,并演示 2 不同的过渡风格在构建这款游戏的过程中,我收获了无穷乐趣。
@media (prefers-reduced-motion: reduce) {
snap-tabs > header a {
border-block-end: var(--indicator-size) solid hsl(var(--accent) / 0%);
transition: color .7s ease, border-color .5s ease;
&:is(:target,:active,[active]) {
color: var(--text-active-color);
border-block-end-color: hsl(var(--accent));
}
}
snap-tabs .snap-indicator {
visibility: hidden;
}
}
如果用户更喜欢减少动作,我会隐藏 .snap-indicator
,因为我不
您再也不需要它了然后,将其替换为 border-block-end
样式和
transition
。另请注意,在标签页交互中,活动的导航项
只突出显示了品牌下划线,但其文本颜色也较深。通过
活动元素具有较高的文字色彩对比度和明亮的背光强调效果。
多出几行 CSS 就会让人感觉自己被看到(从某种意义上说, 我们会谨慎考虑他们的动作偏好)。我很喜欢。
@scroll-timeline
在上一部分中,我向大家介绍了如何处理动态淡入淡出 在本节中,我将展示如何将指示器和 滚动区域。接下来我们要介绍一些有趣的实验性内容。希望你 我和我一样兴奋
const { matches:motionOK } = window.matchMedia(
'(prefers-reduced-motion: no-preference)'
);
我首先在 JavaScript 中检查用户的动作偏好设置。如果
这是 false
,这意味着用户更喜欢减少动作,则我们不会运行任何
用于关联动作效果的滚动效果
if (motionOK) {
// motion based animation code
}
在撰写本文时,浏览器支持
@scroll-timeline
为无。这是一个
规范草稿,其中仅包含
实验性实现不过它有一个 polyfill
演示。
ScrollTimeline
CSS 和 JavaScript 都可以创建滚动时间轴 JavaScript,以便我可以在动画中使用实时元素测量值。
const sectionScrollTimeline = new ScrollTimeline({
scrollSource: tabsection, // snap-tabs > section
orientation: 'inline', // scroll in the direction letters flow
fill: 'both', // bi-directional linking
});
我想让一个元素跟随另一个对象的滚动位置,
ScrollTimeline
我定义了滚动链接的驱动因素 scrollSource
。
通常,Web 上的动画会针对全局时间帧 tick 运行,但具有
内存中的自定义 sectionScrollTimeline
,我可以更改所有这些对象。
tabindicator.animate({
transform: ...,
width: ...,
}, {
duration: 1000,
fill: 'both',
timeline: sectionScrollTimeline,
}
);
在介绍动画的主帧之前,我觉得有必要先确定
指出滚动的关注者 tabindicator
将基于
也就是版块的滚动条这样就完成了关联
缺少添加动画效果的有状态点,也称为
关键帧。
动态关键帧
你可以使用一种非常强大的纯声明式 CSS 方法
@scroll-timeline
,但我选择使用的动画过于动感。我们没有
能够在 auto
宽度之间转换,而且无法动态创建
基于子元素长度的关键帧数量。
不过,JavaScript 知道如何获取这些信息,因此,我们会对 并在运行时获取计算值:
tabindicator.animate({
transform: [...tabnavitems].map(({offsetLeft}) =>
`translateX(${offsetLeft}px)`),
width: [...tabnavitems].map(({offsetWidth}) =>
`${offsetWidth}px`)
}, {
duration: 1000,
fill: 'both',
timeline: sectionScrollTimeline,
}
);
对于每个 tabnavitem
,解构 offsetLeft
位置并返回一个字符串
并将其用作 translateX
值。此操作会为
动画。对宽度也是如此,系统会询问每个广告素材的动态宽度是多少
然后将其用作关键帧值
下面是基于我的字体和浏览器偏好设置的输出示例:
TranslateX 关键帧:
[...tabnavitems].map(({offsetLeft}) =>
`translateX(${offsetLeft}px)`)
// results in 4 array items, which represent 4 keyframe states
// ["translateX(0px)", "translateX(121px)", "translateX(238px)", "translateX(464px)"]
宽度关键帧:
[...tabnavitems].map(({offsetWidth}) =>
`${offsetWidth}px`)
// results in 4 array items, which represent 4 keyframe states
// ["121px", "117px", "226px", "67px"]
总结一下策略,标签指示器现在跨 4 个关键帧以动画形式呈现 具体取决于版块滚动条的滚动贴靠位置。贴靠点 明确划分各个关键帧 动画的同步感觉。
用户通过互动来驱动动画,查看宽度和 指示符的位置从一个部分移动到另一个部分, 完美呈现。
您可能没有注意到,但作为角色的这种改变,我感到非常自豪。 选中的导航项就会变为选中状态
当未选中的浅灰色突出显示时, 对比度越高。文本颜色的转换颜色很常见,例如悬停时 选中后,它会在滚动时进行下一层转换 与下划线指示符同步
具体操作步骤如下:
tabnavitems.forEach(navitem => {
navitem.animate({
color: [...tabnavitems].map(item =>
item === navitem
? `var(--text-active-color)`
: `var(--text-color)`)
}, {
duration: 1000,
fill: 'both',
timeline: sectionScrollTimeline,
}
);
});
每个标签页导航链接都需要这种新的彩色动画来跟踪相同的滚动 作为下划线指示符我使用与之前相同的时间轴: 其作用是在滚动时发出一个 tick,我们可以在任何类型的 所需的动画效果和之前一样,我循环创建了 4 个关键帧 颜色。
[...tabnavitems].map(item =>
item === navitem
? `var(--text-active-color)`
: `var(--text-color)`)
// results in 4 array items, which represent 4 keyframe states
// [
"var(--text-active-color)",
"var(--text-color)",
"var(--text-color)",
"var(--text-color)",
]
颜色为 var(--text-active-color)
的关键帧突出显示了该链接;
否则会采用标准文本颜色其中的嵌套循环使其相对
直接,因为外圈是每个导航项,内圈是每个导航项
navitem 的个人关键帧。我检查外圈元素是否与
并使用此内部循环来获知它在何时被选中。
写这本书我收获了很多乐趣。喜欢得不得了
更多 JavaScript 增强功能
值得一提的是,我演示的核心功能 JavaScript。既然如此,我们还是看看 可用。
深层链接
深层链接更像是一个移动术语,但我认为深层链接
在这里,您可以直接分享指向标签页内容的网址。通过
浏览器将在页内导航至与网址哈希值相匹配的 ID。我找到了
此 onload
处理程序会跨平台生效。
window.onload = () => {
if (location.hash) {
tabsection.scrollLeft = document
.querySelector(location.hash)
.offsetLeft;
}
}
滚动结束同步
我们的用户并不总是点击或使用键盘,有时他们 用户应能够自由滚动浏览当版块滚动条停止运行时 需要与顶部导航栏中的哪个位置相匹配。
等待滚动结束的方法如下:
js
tabsection.addEventListener('scroll', () => {
clearTimeout(tabsection.scrollEndTimer);
tabsection.scrollEndTimer = setTimeout(determineActiveTabSection, 100);
});
每当滚动部分时,请清除部分超时(如果有), 然后创建一个新请求当各部分停止滚动时,请勿清除超时 并在静息 100 毫秒后触发。当它触发时,调用试图计算的函数 用户停止的位置
const determineActiveTabSection = () => {
const i = tabsection.scrollLeft / tabsection.clientWidth;
const matchingNavItem = tabnavitems[i];
matchingNavItem && setActiveTab(matchingNavItem);
};
假设滚动已贴靠,当前滚动位置与宽度相同 应是整数,而不是小数。然后,我尝试 通过此计算索引从缓存中获取 navitem,如果找到 我发送匹配,让它开始生效
const setActiveTab = tabbtn => {
tabnav
.querySelector(':scope a[active]')
.removeAttribute('active');
tabbtn.setAttribute('active', '');
tabbtn.scrollIntoView();
};
要设置活动标签页,首先请清除所有当前活动的标签页,然后
传入的导航项启用状态属性。对 scrollIntoView()
的调用
它与 CSS 进行了有趣的互动
.scroll-snap-x {
overflow: auto hidden;
overscroll-behavior-x: contain;
scroll-snap-type: x mandatory;
@media (prefers-reduced-motion: no-preference) {
scroll-behavior: smooth;
}
}
在水平滚动贴靠实用工具 CSS 中
nested 媒体查询,它应用
smooth
滚动。JavaScript 可以自由地
调用将元素滚动到视图中,CSS 可以通过声明方式管理用户体验。
偶尔也会搭上她那可爱的小家伙。
总结
现在你知道我怎么做到的了,你会怎么做?!这样很有趣 组件架构!谁会制作在他们的 您最喜欢的框架是什么?🙂
让我们一起采用多样化的方法,学习所有在 Web 上构建应用的方法。 创建 Glitch,发 Twitter 微博 我会将其添加到社区混剪视频中 部分。
社区混剪作品
- 使用 Web 组件的 @devnook、@rob_dodson 和 @DasSurma:文章。
- 带有按钮的 @jhvanderschee:Codepen。