使用纯 CSS 自定义选择样式
这是系列文章的第 20 集,该系列文章探讨了现代 CSS 解决方案,以解决我过去 13 多年来作为前端开发者所遇到的各种问题。访问ModernCSS.dev即可查看整个系列文章及其他资源。
现代 CSS 为我们提供了一系列属性,以实现自定义选择样式,这些样式select
对于顶级浏览器中的单个、多个和禁用元素具有几乎相同的初始外观。
我们的解决方案将使用一些属性和技术:
clip-path
创建自定义下拉箭头- CSS 网格布局对齐原生选择和箭头
- 自定义 CSS 变量以实现灵活的样式
em
相对尺寸单位
现已推出:我的 Egghead 视频课程:无障碍跨浏览器 CSS 表单样式。你将学习如何将本教程中描述的技术提升到一个新的水平,创建一个可主题化的表单设计系统,并扩展到你的各个项目。
本机选择的常见问题
与所有表单字段类型一样,<select>
其初始外观在不同浏览器中有所不同。
从左到右,这是<select>
Firefox、Chrome 和 Safari 中的初始外观:
差异包括框大小、字体大小、行高,最突出的是下拉指示器样式的差异。
我们的目标是在这些浏览器中创建相同的初始外观,包括多项选择和禁用状态。
注意:下拉列表仍然不支持样式设置,因此一旦
<select>
打开,它仍然会根据浏览器的不同,自动获取option
列表的样式。这没关系——我们可以处理这个问题,以保留原生选择框的自由可访问性!
基本 HTML
<select>
首先我们将集中讨论一个单曲。
<label for="standard-select">Standard Select</label>
<div class="select">
<select id="standard-select">
<option value="Option 1">Option 1</option>
<option value="Option 2">Option 2</option>
<option value="Option 3">Option 3</option>
<option value="Option 4">Option 4</option>
<option value="Option 5">Option 5</option>
<option value="Option length">Option that has too long of a value to fit</option>
</select>
</div>
标签不是我们样式练习的一部分,但它作为一般要求包含在内,特别是for
具有 值的属性。id
<select>
select
为了实现我们的自定义样式,在本教程中,为了简单起见,我们将原生选择包装在一个带有类的额外 div 中。
重置和删除继承的样式
正如我的所有教程中所包含的现代最佳实践一样,我们首先添加以下重置:
*,
*::before,
*::after {
box-sizing: border-box;
}
接下来,我们可以开始制定本机规则select
并应用以下内容来休息其外观:
select {
// A reset of styles, including removing the default dropdown arrow
appearance: none;
// Additional resets for further consistency
background-color: transparent;
border: none;
padding: 0 1em 0 0;
margin: 0;
width: 100%;
font-family: inherit;
font-size: inherit;
cursor: inherit;
line-height: inherit;
}
虽然其中大多数可能都很熟悉,但奇怪的是appearance
。这是一个不常用的属性,你会注意到它并没有达到我们想要的支持程度,但在本例中,它主要为我们提供了删除原生浏览器下拉箭头的功能。
注意:CodePen 已设置为使用自动前缀器,它将添加属性所需的前缀版本
appearance
。您可能需要为您的项目专门设置此功能,或手动添加。我的HTML / Sass Jumpstart已将自动前缀器作为生产版本的一部分。
好消息是,如果您需要的话,我们可以添加一条规则来删除较低 IE 版本中的箭头:
select::-ms-expand {
display: none;
}
这个技巧是在 Filament Group 的优秀文章中找到的,其中展示了创建选择样式的另一种方法。
最后一步是删除默认的outline
。别担心,我们稍后会添加一个替代的:focus
state!
select {
// ...existing styles
outline: none;
下面是我们进度的动图。你可以看到,现在没有任何视觉迹象表明这是在select
点击之前发生的:
自定义选择框样式
首先,我们来设置一些 CSS 变量。这样我们的选择项就可以灵活地重新着色,例如表示错误状态。
:root {
--select-border: #777;
--select-focus: blue;
--select-arrow: var(--select-border);
}
可访问性注意事项:作为用户界面元素,选择边框与周围表面颜色的对比度必须达到 3:1 或更高。
现在是时候创建我们将应用于包装的自定义选择样式了div.select
:
.select {
width: 100%;
min-width: 15ch;
max-width: 30ch;
border: 1px solid var(--select-border);
border-radius: 0.25em;
padding: 0.25em 0.5em;
font-size: 1.25rem;
cursor: pointer;
line-height: 1.1;
background-color: #fff;
background-image: linear-gradient(to top, #f9f9f9, #fff 33%);
}
首先,我们设置一些宽度约束。min-width
和 的max-width
值主要用于本演示,您可以根据自己的使用情况选择删除或修改它们。
然后我们应用一些盒子模型属性,包括border
、border-radius
和padding
。注意单位的使用em
,它将使这些属性与集合保持比例font-size
。
在重置样式中,我们为 设置了几个属性inherit
,因此我们在这里定义这些属性,包括font-size
、cursor
和line-height
。
最后,我们为其提供了背景属性,包括用于稍微增加维度的渐变。如果删除背景属性,选中内容将变为透明,并会拾取页面背景。这可能是理想的选择,但请注意并测试对比度效果。
自定义选择下拉箭头
对于我们的下拉箭头,我们将使用最令人兴奋的现代 CSS 属性之一:clip-path
。
剪切路径让我们能够通过“剪切”大多数元素默认的正方形和矩形来创建各种形状。我clip-path
在最近的作品集网站重新设计中很享受使用这个功能。
在获得更好的支持之前clip-path
,替代方法包括:
background-image
- 通常是 png,稍微现代一点的是 SVG- 内联 SVG 作为附加元素
- 创建三角形的边框技巧
SVG 或许感觉像是最佳解决方案,然而当它被用作图标时,background-image
它就失去了图标的功能,因为除非完全重新定义,否则无法更改其属性(例如填充颜色)。这意味着我们无法使用 CSS 自定义变量。
将 SVG 放置在内联位置可以解决fill
颜色问题,但这意味着每次<select>
定义时都要包含一个以上的元素。
通过clip-path
,我们得到了一个清晰、可扩展的箭头“图形”,感觉就像一个 SVG,但具有能够使用我们的自定义变量并包含在样式与 HTML 标记中的好处。
为了创建箭头,我们将其定义为::after
伪元素。
.select::after {
content: "";
width: 0.8em;
height: 0.5em;
background-color: var(--select-arrow);
clip-path: polygon(100% 0%, 0 0%, 50% 100%);
}
语法clip-path
有点奇怪,而且由于这不是本文的重点,因此我推荐以下资源:
- Colby Fayock 在这个 egghead 视频中用一个例子解释了语法
- Clippy是一个在线工具,允许您选择形状并调整点,同时动态生成
clip-path
CSS
width
如果你一直在关注,你可能会注意到,尽管定义了和 ,但箭头并没有出现height
。检查后发现,::after
的宽度实际上并不允许。
我们将通过更新.select
以使用 CSS 网格布局来解决此问题。
.select {
// ...existing styles
display: grid;
}
这样,通过本质上扩展类似于“块”的显示值,箭头就会出现。
在此阶段,我们可以验证我们确实创建了一个三角形。
为了修复对齐问题,我们将使用我最喜欢的 CSS 网格 hack(如果你读过这里的几篇文章,这对你来说已经很熟悉了!)。
旧 CSS 解决方案:position: absolute
新 CSS 解决方案:单一解决方案grid-template-areas
包含所有内容
首先我们定义一个区域,然后定义select
和::after
都使用它。该区域的名称限定于其创建时所针对的元素,为了方便起见,我们将其命名为“select”:
.select {
// ...existing styles
grid-template-areas: "select";
}
select,
.select:after {
grid-area: select;
}
由于通过源顺序的堆叠上下文,这给我们带来了本机选择上方箭头的重叠:
我们现在可以使用网格属性来确定每个元素的对齐方式:
.select {
// ...existing styles
align-items: center;
}
.select:after {
// ...existing styles
justify-self: end;
}
哒哒!
:focus
状态
哦对了,还记得我们是怎么删除的吗outline
?我们需要解决:focus
删除后丢失的状态问题。
我们可以使用一个即将推出的属性,:focus-within
但此时最好为其包含一个 polyfill。
在本教程中,我们将使用另一种方法来达到相同的结果,只是稍微繁琐一些。
不幸的是,这意味着我们需要在 DOM 中添加一个元素。
在原生选择元素之后,作为 中的最后一个子元素.select
,添加:
<span class="focus"></span>
为什么是“之后”?因为这是一个纯 CSS 解决方案,将它放在原生选择器之后意味着我们可以在select
获得焦点时,通过使用相邻的兄弟选择器来修改它+
。
这使我们能够创建以下规则:
select:focus + .focus {
position: absolute;
top: -1px;
left: -1px;
right: -1px;
bottom: -1px;
border: 2px solid var(--select-focus);
border-radius: inherit;
}
您可能想知道为什么我们position: absolute
在刚刚学习了之前的grid-area
技巧之后又回到了这里。
这样做是为了避免根据内边距重新计算调整值。如果您自己尝试一下,就会发现即使将 设置width
为height
100%,它仍然位于内边距内。
这项工作position: absolute
最擅长的是匹配元素的大小。我们在每个方向上额外拉一个像素,以确保它与 border 属性重叠。
但是,我们需要再添加一个以.select
确保它与我们的选择相关 - 嗯position: relative
。
.select {
// ...existing styles
position: relative;
以下是我们在 Chrome 中看到的自定义全选:
多选
选择还有第二种形式,允许用户选择多个选项。从 HTML 的角度来看,这仅仅意味着添加一个multiple
属性,但我们还会添加一个类来帮助创建样式调整,它被称为select--multiple
:
<label for="multi-select">Multiple Select</label>
<div class="select select--multiple">
<select id="multi-select" multiple>
<option value="Option 1">Option 1</option>
<option value="Option 2">Option 2</option>
<option value="Option 3">Option 3</option>
<option value="Option 4">Option 4</option>
<option value="Option 5">Option 5</option>
<option value="Option length">Option that has too long of a value to fit</option>
</select>
<span class="focus"></span>
</div>
看看它,我们可以看到它继承了我们的大多数风格,只是我们不需要这个视图中的箭头。
这是一个快速修复方法,用于调整定义箭头的选择器。我们用它:not()
来排除新定义的类:
.select:not(.select--multiple)::after
我们对多项选择做了一些小的调整,首先是删除之前添加的填充以便为箭头腾出空间:
select[multiple] {
padding-right: 0;
}
默认情况下,具有长值的选项将溢出可见区域并被剪裁,但我发现主要浏览器允许根据需要覆盖包装:
select[multiple] option {
white-space: normal;
}
可选地,我们可以在选择框上设置一个,height
以实现更可靠的跨浏览器行为。通过测试,我发现 Chrome 和 Firefox 会显示部分选项,但 Safari 会完全隐藏无法完全显示的选项。
高度必须直接在原生选择器上设置。考虑到我们的其他样式,该值6rem
将能够显示 3 个选项:
select[multiple] {
// ...existing styles
height: 6rem;
}
目前,由于当前的浏览器支持,我们已尽可能地做出调整。
:selected
Chrome 中的状态可options
自定义程度较高,Firefox 中则略有不同,Safari 中则完全无法自定义。CodePen演示中有一个部分可以取消注释来预览。
:disabled
样式
虽然我主张简单地不显示禁用的控件,但我们应该为该状态准备样式以覆盖我们的基础。
为了强调禁用状态,我们想应用灰色背景。但是由于我们已经设置了背景样式,.select
并且没有:parent
选择器,所以我们需要创建最后一个类来处理这个状态:
.select--disabled {
cursor: not-allowed;
background-color: #eee;
background-image: linear-gradient(to top, #ddd, #eee 33%);
}
在这里,我们更新了光标,作为该字段无法交互的额外提示,并将我们之前设置为白色的背景值更新为禁用状态的灰色。
这会导致以下结果:
演示
您可以自己测试一下,但这里是 Firefox、Chrome 和 Safari(从左到右)完整解决方案的预览:
文章来源:https://dev.to/5t3ph/custom-select-styles-with-pure-css-4f58