关于如何构建包含滑块和复选框的设置组件的基础概览。
在这篇博文中,我想分享一下关于构建一个适用于 Web 的设置组件的想法,该组件应具有自适应性、支持多种设备输入,并且可在各种浏览器中运行。试用演示版。
如果您偏爱视频,或者想预览我们正在构建的界面/用户体验,不妨观看以下 YouTube 上的简短演示:
概览
我已将此组件的各个方面分为以下部分:
布局
这是第一个完全基于 CSS 网格的 GUI 挑战赛演示。以下是使用 Chrome 网格开发者工具突出显示的每个网格:
仅限差距
最常见的布局:
foo {
display: grid;
gap: var(--something);
}
我将此布局称为“仅用于间距”,因为它仅使用网格在块之间添加间距。
有 5 个布局使用此策略,下面显示了所有这些布局:
包含每个输入组 (.fieldset-item
) 的 fieldset
元素使用 gap: 1px
在元素之间创建细线边框。无需使用复杂的边框解决方案!
.grid { display: grid; gap: 1px; background: var(--bg-surface-1); & > .fieldset-item { background: var(--bg-surface-2); } }
.grid { display: grid; & > .fieldset-item { background: var(--bg-surface-2); &:not(:last-child) { border-bottom: 1px solid var(--bg-surface-1); } } }
自然网格换行
最复杂的布局最终是宏布局,即介于 <main>
和 <form>
之间的逻辑布局系统。
将封装内容居中
Flexbox 和网格都提供 align-items
或 align-content
功能,而在处理换行元素时,content
布局对齐方式会将空间分配给子元素(作为一个组)。
main {
display: grid;
gap: var(--space-xl);
place-content: center;
}
主要元素使用 place-content: center
对齐方式简写,以便子元素在单列和双列布局中均垂直居中和水平居中。
观看上方视频,了解即使发生了换行,“内容”仍保持居中。
重复自动调整 minmax
<form>
为每个部分使用自适应网格布局。此布局会根据可用空间从单列切换到双列。
form {
display: grid;
gap: var(--space-xl) var(--space-xxl);
grid-template-columns: repeat(auto-fit, minmax(min(10ch, 100%), 35ch));
align-items: flex-start;
max-width: 89vw;
}
此网格的 row-gap
(--space-xl) 值与 column-gap
(--space-xxl) 值不同,以便为自适应布局添加自定义效果。当列堆叠时,我们希望有较大的间距,但不要像在宽屏幕上那样大。
grid-template-columns
属性使用了 3 个 CSS 函数:repeat()
、minmax()
和 min()
。Una Kravets 撰写了一篇出色的布局博文,其中将此概念称为 RAM。
与 Una 的布局相比,我们的布局有 3 个特殊之处:
- 我们传递了一个额外的
min()
函数。 - 我们指定了
align-items: flex-start
。 - 有
max-width: 89vw
风格。
Evan Minto 在其博客中发表的博文 Intrinsically Responsive CSS Grid with minmax() and min() 详细介绍了额外的 min()
函数。建议您阅读该博文。flex-start
对齐校正旨在移除默认的拉伸效果,以便此布局的子级不必具有相同的高度,而是可以具有自然的固有高度。YouTube 视频简要介绍了此对齐方式的添加。
max-width: 89vw
值得在本博文中简要介绍一下。
下面我将展示应用样式和未应用样式时的布局:
发生了什么?指定 max-width
时,它会为 auto-fit
布局算法提供上下文、明确的尺寸或确定的尺寸,以便该算法了解可以在空间中容纳多少次重复。虽然空间“全宽”看起来很明显,但根据 CSS 网格规范,必须提供确定的尺寸或最大尺寸。我已提供最大尺寸。
那么,为什么选择 89vw
?因为我的布局“有效”。
我和其他几位 Chrome 工程师正在调查为什么像 100vw
这样更合理的值不够,以及这是否确实是一个 bug。
间距
此布局的大部分和谐感都来自有限的间距调色板,确切来说是 7 个。
:root {
--space-xxs: .25rem;
--space-xs: .5rem;
--space-sm: 1rem;
--space-md: 1.5rem;
--space-lg: 2rem;
--space-xl: 3rem;
--space-xxl: 6rem;
}
这些流与网格、CSS @nest 和 @media 的第 5 级语法搭配使用效果非常好。下面是一个示例,即完全 <main>
的布局样式集。
main {
display: grid;
gap: var(--space-xl);
place-content: center;
padding: var(--space-sm);
@media (width >= 540px) {
& {
padding: var(--space-lg);
}
}
@media (width >= 800px) {
& {
padding: var(--space-xl);
}
}
}
内容居中、默认填充适中的网格(如移动设备上所示)。但随着视口空间越来越大,它会通过增加内边距来展开。 2021 年 CSS 看起来相当不错!
还记得之前的布局“仅用于间隙”吗?下面是这些变量在此组件中的显示方式(更完整):
header {
display: grid;
gap: var(--space-xxs);
}
section {
display: grid;
gap: var(--space-md);
}
颜色
通过对色彩的巧妙运用,此设计在表达丰富情感的同时又显得简约。我的做法如下:
:root {
--surface1: lch(10 0 0);
--surface2: lch(15 0 0);
--surface3: lch(20 0 0);
--surface4: lch(25 0 0);
--text1: lch(95 0 0);
--text2: lch(75 0 0);
}
我使用数字来命名界面和文字颜色,而不是使用 surface-dark
和 surface-darker
等名称,因为在媒体查询中,我会翻转这些颜色,而浅色和深色将不再有意义。
我会在偏好设置媒体查询中翻转它们,如下所示:
:root {
...
@media (prefers-color-scheme: light) {
& {
--surface1: lch(90 0 0);
--surface2: lch(100 0 0);
--surface3: lch(98 0 0);
--surface4: lch(85 0 0);
--text1: lch(20 0 0);
--text2: lch(40 0 0);
}
}
}
在深入了解颜色语法细节之前,我们先快速了解一下总体情况和策略。不过,由于我有点超前了,所以现在先回过头来。
LCH?
我们不会深入探讨色彩理论,但 LCH 是一种面向人类的语法,它迎合了我们感知颜色的方式,而不是我们用数学方法(如 255)测量颜色的方式。这使其具有明显的优势,因为人类可以更轻松地编写它,而其他人也会适应这些调整。

在此演示中,我们先重点介绍语法以及我切换的用于实现浅色和深色的值。我们来看一下 1 种表面颜色和 1 种文字颜色:
:root {
--surface1: lch(10 0 0);
--text1: lch(95 0 0);
@media (prefers-color-scheme: light) {
& {
--surface1: lch(90 0 0);
--text1: lch(40 0 0);
}
}
}
--surface1: lch(10 0 0)
转换为 10%
明度、0 色度和 0 色调:一种非常深的无色灰色。然后,在浅色模式的媒体查询中,明度会通过 --surface1: lch(90 0 0);
翻转为 90%
。以上就是该策略的要点。首先,只需更改这 2 个主题之间的亮度,同时保持设计所需的对比度或可保持无障碍功能的对比度。
lch()
的优势在于,明度是面向人类的,我们可以放心地将其更改为 %
,因为在感知上,它始终会相差 %
。hsl()
例如,不如。
如果您有兴趣,可以详细了解色彩空间和 lch()
。即将推出!
CSS 目前完全无法访问这些颜色。 请允许我再次强调:我们无法访问大多数现代显示器中三分之一的颜色。这些并不是普通的颜色,而是屏幕可以显示的最生动的颜色。我们的网站色彩暗淡,这是因为显示器硬件的发展速度快于 CSS 规范和浏览器实现。
Lea Verou
具有 color-scheme 的自适应表单控件
许多浏览器(目前为 Safari 和 Chromium)都提供深色主题控件,但您必须在 CSS 或 HTML 中指定您的设计使用这些控件。
上图展示了开发者工具的“样式”面板中相应属性的效果。此演示使用 HTML 标记,我认为这通常是更好的位置:
<meta name="color-scheme" content="dark light">
如需详细了解,请参阅 Thomas Steiner 撰写的这篇color-scheme
文章。除了深色复选框输入之外,还有更多值得探索的功能!
CSS accent-color
最近,人们对表单元素中的 accent-color
进行了相关活动,这是一个单一的 CSS 样式,可以更改浏览器输入元素中使用的色调颜色。如需详细了解,请点击此处前往 GitHub。我已将其包含在此组件的样式中。随着浏览器支持该功能,我的复选框将更符合主题,并带有粉色和紫色点缀。
input[type="checkbox"] {
accent-color: var(--brand);
}
局部彩色照片,具有固定的渐变效果和 focus-within
颜色在少量使用时最能凸显出来,而我喜欢通过丰富多彩的界面互动来实现这一点。
上述视频中包含许多层界面反馈和互动,可通过以下方式为互动增添个性:
- 突出显示上下文。
- 提供界面反馈,显示值在范围内的“饱和度”。
- 提供界面反馈,表明某个字段正在接受输入。
为了在与元素互动时提供反馈,CSS 使用 :focus-within
伪类来更改各种元素的外观。下面我们来详细了解一下 .fieldset-item
,它非常有趣:
.fieldset-item {
...
&:focus-within {
background: var(--surface2);
& svg {
fill: white;
}
& picture {
clip-path: circle(50%);
background: var(--brand-bg-gradient) fixed;
}
}
}
当此元素的某个子元素具有 focus-within 时:
.fieldset-item
背景被分配了对比度更高的 Surface 颜色。- 嵌套的
svg
填充为白色,以提高对比度。 - 嵌套的
<picture>
clip-path
会展开为完整的圆形,并且背景会填充明亮的固定渐变。
自定义范围
假设有以下 HTML 输入元素,我将向您展示如何自定义其外观:
<input type="range">
此元素有 3 个部分需要自定义:
范围元素样式
input[type="range"] {
/* style setting variables */
--track-height: .5ex;
--track-fill: 0%;
--thumb-size: 3ex;
--thumb-offset: -1.25ex;
--thumb-highlight-size: 0px;
appearance: none; /* clear styles, make way for mine */
display: block;
inline-size: 100%; /* fill container */
margin: 1ex 0; /* ensure thumb isn't colliding with sibling content */
background: transparent; /* bg is in the track */
outline-offset: 5px; /* focus styles have space */
}
前几行 CSS 是样式的自定义部分,希望明确标记它们会有所帮助。其余样式大多是重置样式,用于为构建组件的棘手部分提供一致的基础。
轨道样式
input[type="range"]::-webkit-slider-runnable-track {
appearance: none; /* clear styles, make way for mine */
block-size: var(--track-height);
border-radius: 5ex;
background:
/* hard stop gradient:
- half transparent (where colorful fill we be)
- half dark track fill
- 1st background image is on top
*/
linear-gradient(
to right,
transparent var(--track-fill),
var(--surface1) 0%
),
/* colorful fill effect, behind track surface fill */
var(--brand-bg-gradient) fixed;
}
此效果的诀窍在于“显示”鲜艳的填充颜色。这是通过在顶部使用硬停止梯度来实现的。在填充百分比之前,渐变是透明的,之后使用未填充的轨道表面颜色。在未填充的 Surface 后面,是一种全宽颜色,等待透明度来显示它。
滑轨填充样式
我的设计需要 JavaScript 才能保持填充样式。虽然有纯 CSS 策略,但它们要求滑块元素的高度与轨道相同,而我无法在这些限制范围内找到和谐的解决方案。
/* grab sliders on page */
const sliders = document.querySelectorAll('input[type="range"]')
/* take a slider element, return a percentage string for use in CSS */
const rangeToPercent = slider => {
const max = slider.getAttribute('max') || 10;
const percent = slider.value / max * 100;
return `${parseInt(percent)}%`;
};
/* on page load, set the fill amount */
sliders.forEach(slider => {
slider.style.setProperty('--track-fill', rangeToPercent(slider));
/* when a slider changes, update the fill prop */
slider.addEventListener('input', e => {
e.target.style.setProperty('--track-fill', rangeToPercent(e.target));
})
})
我认为这会带来不错的视觉升级。滑块在没有 JavaScript 的情况下也能正常运行,无需使用 --track-fill
属性,如果未提供该属性,滑块将不会具有填充样式。如果 JavaScript 可用,则填充自定义属性,同时观察任何用户更改,并将自定义属性与该值同步。
这是一篇由 Ana Tudor 在 CSS-Tricks 上发表的精彩博文,其中演示了仅使用 CSS 实现轨道填充的解决方案。我还发现这个 range
元素非常令人鼓舞。
拇指样式
input[type="range"]::-webkit-slider-thumb {
appearance: none; /* clear styles, make way for mine */
cursor: ew-resize; /* cursor style to support drag direction */
border: 3px solid var(--surface3);
block-size: var(--thumb-size);
inline-size: var(--thumb-size);
margin-top: var(--thumb-offset);
border-radius: 50%;
background: var(--brand-bg-gradient) fixed;
}
这些样式大多用于创建漂亮的圆形。
您会再次看到固定的背景渐变,它统一了缩略图、轨道和相关联 SVG 元素的动态颜色。
我将互动样式分离开来,以帮助隔离用于悬停突出显示的 box-shadow
技术:
@custom-media --motionOK (prefers-reduced-motion: no-preference);
::-webkit-slider-thumb {
…
/* shadow spread is initally 0 */
box-shadow: 0 0 0 var(--thumb-highlight-size) var(--thumb-highlight-color);
/* if motion is OK, transition the box-shadow change */
@media (--motionOK) {
& {
transition: box-shadow .1s ease;
}
}
/* on hover/active state of parent, increase size prop */
@nest input[type="range"]:is(:hover,:active) & {
--thumb-highlight-size: 10px;
}
}
目标是为用户反馈提供易于管理且具有动画效果的视觉突出显示。 通过使用盒子阴影,我可以避免因特效而触发布局。为此,我创建了一个不模糊且与拇指元素的圆形形状相匹配的阴影。然后,我更改并过渡了悬停时的展开大小。
如果复选框的突出显示效果也能这么简单就好了…
跨浏览器选择器
我发现需要使用 -webkit-
和 -moz-
选择器才能实现跨浏览器一致性:
input[type="range"] {
&::-webkit-slider-runnable-track {}
&::-moz-range-track {}
&::-webkit-slider-thumb {}
&::-moz-range-thumb {}
}
自定义复选框
假设有以下 HTML 输入元素,我将向您展示如何自定义其外观:
<input type="checkbox">
此元素有 3 个部分需要自定义:
复选框元素
input[type="checkbox"] {
inline-size: var(--space-sm); /* increase width */
block-size: var(--space-sm); /* increase height */
outline-offset: 5px; /* focus style enhancement */
accent-color: var(--brand); /* tint the input */
position: relative; /* prepare for an absolute pseudo element */
transform-style: preserve-3d; /* create a 3d z-space stacking context */
margin: 0;
cursor: pointer;
}
transform-style
和 position
样式为我们稍后将介绍的伪元素做好准备,以便设置突出显示的样式。否则,我主要会提供一些个人风格的次要意见。我希望光标为指针,希望有轮廓偏移,默认复选框太小,如果支持 accent-color
,则将这些复选框纳入品牌配色方案。
复选框标签
为复选框提供标签非常重要,原因有以下两点:第一个是表示复选框值用于什么,以回答“开启或关闭什么?”其次是为了改善用户体验,因为 Web 用户已经习惯于通过复选框的相关标签来与之互动。
<input type="checkbox" id="text-notifications" name="text-notifications" >
<label for="text-notifications"> <h3>Text Messages</h3> <small>Get notified about all text messages sent to your device</small> </label>
在标签上,放置一个通过 ID 指向复选框的 for
属性:<label for="text-notifications">
。在复选框中,同时使用名称和 ID,以确保可以通过各种工具和技术(例如鼠标或屏幕阅读器)找到该复选框:
<input type="checkbox" id="text-notifications" name="text-notifications">
。
:hover
、:active
等功能可免费使用,让用户能以更多方式与您的表单互动。
复选框突出显示
我想保持界面的一致性,并且滑块元素具有不错的缩略图突出显示效果,我想将其与复选框搭配使用。缩略图能够使用 box-shadow
及其 spread
属性来放大和缩小阴影。不过,这种效果在这里不起作用,因为我们的复选框是正方形,也应该是正方形。
我使用伪元素和一些棘手的 CSS 实现了相同的视觉效果:
@custom-media --motionOK (prefers-reduced-motion: no-preference);
input[type="checkbox"]::before {
--thumb-scale: .01; /* initial scale of highlight */
--thumb-highlight-size: var(--space-xl);
content: "";
inline-size: var(--thumb-highlight-size);
block-size: var(--thumb-highlight-size);
clip-path: circle(50%); /* circle shape */
position: absolute; /* this is why position relative on parent */
top: 50%; /* pop and plop technique (https://web.dev/centering-in-css#5-pop-and-plop) */
left: 50%;
background: var(--thumb-highlight-color);
transform-origin: center center; /* goal is a centered scaling circle */
transform: /* order here matters!! */
translateX(-50%) /* counter balances left: 50% */
translateY(-50%) /* counter balances top: 50% */
translateZ(-1px) /* PUTS IT BEHIND THE CHECKBOX */
scale(var(--thumb-scale)) /* value we toggle for animation */
;
will-change: transform;
@media (--motionOK) { /* transition only if motion is OK */
& {
transition: transform .2s ease;
}
}
}
/* on hover, set scale custom property to "in" state */
input[type="checkbox"]:hover::before {
--thumb-scale: 1;
}
创建圆形伪元素是一项简单的工作,但将其放置在所附加到的元素后面则比较困难。以下是修复前后的效果对比:
这绝对是一种微互动,但对我来说,保持视觉一致性非常重要。动画缩放技术与我们在其他地方使用的相同。我们将自定义属性设置为新值,并让 CSS 根据运动偏好设置进行过渡。此处的关键功能是 translateZ(-1px)
。父元素创建了一个 3D 空间,而这个伪元素子元素通过将自己放置在 z 空间中略微靠后的位置来利用该空间。
无障碍
该 YouTube 视频很好地演示了此设置组件的鼠标、键盘和屏幕阅读器互动。下面我将介绍一些详细信息。
HTML 元素选项
<form>
<header>
<fieldset>
<picture>
<label>
<input>
每个文件都包含针对用户浏览工具的提示和技巧。有些元素提供互动提示,有些元素连接互动,还有些元素有助于塑造屏幕阅读器导航的无障碍树。
HTML 属性
我们可以隐藏屏幕阅读器不需要的元素,在本例中,即滑块旁边的图标:
<picture aria-hidden="true">
上面的视频演示了 Mac OS 上的屏幕阅读器流程。请注意,输入焦点如何直接从一个滑块移到下一个滑块。这是因为我们隐藏了可能位于前往下一个滑块的途中的图标。如果没有此属性,用户需要停止、聆听并跳过他们可能看不到的图片。
SVG 是一堆数学公式,我们来添加一个 <title>
元素,用于提供鼠标悬停时的自由标题,以及有关数学公式所创建内容的易读注释:
<svg viewBox="0 0 24 24">
<title>A note icon</title>
<path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/>
</svg>
除此之外,我们还使用了足够多的明确标记的 HTML,因此表单测试在鼠标、键盘、视频游戏控制器和屏幕阅读器上表现都非常出色。
JavaScript
我已经介绍过如何通过 JavaScript 管理轨道填充颜色,现在我们来看看相关的 JavaScript 代码 <form>
:
const form = document.querySelector('form');
form.addEventListener('input', event => {
const formData = Object.fromEntries(new FormData(form));
console.table(formData);
})
每次与表单互动并更改表单时,控制台都会将表单作为对象记录到表格中,以便在提交到服务器之前轻松查看。
总结
现在您已经知道我是如何做到的,那么您会怎么做呢?这使得组件架构非常有趣!谁将率先在自己喜欢的框架中制作出带有 slot 的第一个版本?🙂
让我们丰富方法,了解在网络上构建内容的所有方式。 制作演示视频,在 Twitter 上向我发送链接,我会将其添加到下方的社区混音部分!