高级表单样式
在本文中,我们将了解如何使用 CSS 为那些较难设置样式的表单控件类型(即“不好”和“丑陋”类别)设置样式。正如我们在上一篇文章中看到的,文本字段和按钮非常容易设置样式;现在我们将深入研究如何为那些更棘手的部分设置样式。
回顾我们在上一篇文章中说过的,我们有
不好的:有些元素更难设置样式,需要更复杂的 CSS 或一些更具体的技巧
- 复选框和单选按钮
<input type="search">
丑陋的:有些元素无法使用 CSS 彻底设置样式。这些包括
- 涉及创建下拉小部件的元素,包括
<select>
、<option>
、<optgroup>
和<datalist>
。注意:一些浏览器现在支持可自定义的 select 元素,这是一组 HTML 和 CSS 功能,它们共同实现了对
<select>
元素及其内容的完全自定义,就像任何常规 DOM 元素一样。 <input type="color">
- 日期相关控件,例如
<input type="datetime-local">
<input type="range">
<input type="file">
<progress>
和<meter>
让我们首先讨论 appearance
属性,它有助于使上述所有内容更具可样式性。
appearance
:控制 OS 级别样式
在上一篇文章中,我们提到,从历史上看,Web 表单控件的样式很大程度上源于底层操作系统,这也是难以自定义这些控件外观的部分原因。
appearance
属性的创建是为了控制将哪些 OS 或系统级样式应用于 Web 表单控件。到目前为止,最有用的值(也可能是您唯一会使用的值)是 none
。它会尽可能阻止您应用它的任何控件使用系统级样式,并允许您使用 CSS 自己构建样式。
例如,让我们来看以下控件
<form>
<p>
<label for="search">search: </label>
<input id="search" name="search" type="search" />
</p>
<p>
<label for="text">text: </label>
<input id="text" name="text" type="text" />
</p>
<p>
<label for="date">date: </label>
<input id="date" name="date" type="datetime-local" />
</p>
<p>
<label for="radio">radio: </label>
<input id="radio" name="radio" type="radio" />
</p>
<p>
<label for="checkbox">checkbox: </label>
<input id="checkbox" name="checkbox" type="checkbox" />
</p>
<p><input type="submit" value="submit" /></p>
<p><input type="button" value="button" /></p>
</form>
对它们应用以下 CSS 会删除系统级样式。
input {
appearance: none;
}
下面的实时示例向您展示了它们在您的系统中的外观——左侧是默认样式,右侧是应用了上述 CSS 后的样式。
在大多数情况下,效果是删除样式化的边框,这使得 CSS 样式更容易一些,但并非必不可少。在某些情况下,例如单选按钮和复选框,它变得更加有用。我们现在就来看看这些。
搜索框和 appearance
appearance: none;
值过去在一致地设置 <input type="search">
元素的样式时特别有用。没有它,Safari 不允许对其设置 height
或 font-size
值。然而,Safari 16 及更高版本已不再如此。如果您的浏览器支持矩阵包含旧于 16 的 Safari 版本,您可能仍希望显式地使用 appearance: none;
定位 input[type="search"]
。
在搜索输入中,当值不为空时出现的“x”删除按钮在 Edge 和 Chrome 中失去焦点时消失,但在 Safari 中保留。要通过 CSS 删除,您可以使用以下规则
input[type="search"]:not(:focus, :active)::-webkit-search-cancel-button {
display: none;
}
使用 appearance
为复选框和单选按钮设置样式
默认情况下,为复选框或单选按钮设置样式很棘手。复选框和单选按钮的默认样式大小不应更改,当您尝试更改时,浏览器的反应会非常不同。有些会增加控件的大小,有些则保持控件大小不变并在其周围添加额外的空间。
更好的方法是使用 appearance: none;
完全移除复选框和单选按钮的默认外观,然后为其各种状态添加您自己的样式。
让我们来看这个 HTML 示例
<form>
<fieldset>
<legend>Fruit preferences</legend>
<p>
<label>
<input type="checkbox" name="fruit" value="cherry" />
I like cherry
</label>
</p>
<p>
<label>
<input type="checkbox" name="fruit" value="banana" disabled />
I can't like banana
</label>
</p>
<p>
<label>
<input type="checkbox" name="fruit" value="strawberry" />
I like strawberry
</label>
</p>
</fieldset>
</form>
让我们用自定义复选框设计来设置这些样式。我们将首先移除原始复选框样式
input[type="checkbox"] {
appearance: none;
}
然后我们可以使用 :checked
和 :disabled
伪类来改变我们自定义复选框的状态变化时的外观
input[type="checkbox"] {
position: relative;
width: 1em;
height: 1em;
border: 1px solid gray;
/* Adjusts the position of the checkboxes on the text baseline */
vertical-align: -2px;
/* Set here so that Windows' High-Contrast Mode can override */
color: green;
}
input[type="checkbox"]::before {
content: "✔";
position: absolute;
font-size: 1.2em;
right: -1px;
top: -0.3em;
visibility: hidden;
}
input[type="checkbox"]:checked::before {
/* Use `visibility` instead of `display` to avoid recalculating layout */
visibility: visible;
}
input[type="checkbox"]:disabled {
border-color: black;
background: #dddddd;
color: gray;
}
您将在下一篇文章中找到更多关于此类伪类的信息;以上这些伪类执行以下操作
:checked
— 复选框(或单选按钮)处于选中状态 — 用户已单击/激活它。:disabled
— 复选框(或单选按钮)处于禁用状态 — 无法与其交互。
您可以看到实时结果
我们还创建了几个其他示例,为您提供更多想法
对于“丑陋”的元素能做些什么?
现在让我们将注意力转向“丑陋”的控件——那些真正难以彻底设置样式的控件。简而言之,它们是下拉框、复杂控件类型(如 color
和 datetime-local
)以及面向反馈的控件(如 <progress>
和 <meter>
)。
问题在于这些元素在不同浏览器中的默认外观差异很大,虽然您可以以某些方式设置它们的样式,但它们内部的某些部分是无法设置样式的。
如果您愿意接受外观和感觉上的一些差异,您可以使用一些简单的样式来显著改善情况。这包括一致的大小和像 background-color
这样的属性的样式,以及使用 appearance
来移除一些系统级样式。
请看下面的示例,它展示了许多“丑陋”的表单功能。
您还可以按“播放”按钮,在 MDN Playground 中运行该示例并编辑源代码。
此示例已应用以下 CSS
body {
font-family: "Josefin Sans", sans-serif;
margin: 20px auto;
max-width: 400px;
}
form > div {
margin-bottom: 20px;
}
select {
appearance: none;
width: 100%;
height: 100%;
}
.select-wrapper {
position: relative;
}
.select-wrapper::after {
content: "▼";
font-size: 1rem;
top: 3px;
right: 10px;
position: absolute;
}
button,
label,
input,
select,
progress,
meter {
display: block;
font-family: inherit;
font-size: 100%;
margin: 0;
box-sizing: border-box;
width: 100%;
padding: 5px;
height: 30px;
}
input[type="text"],
input[type="datetime-local"],
input[type="color"],
select {
box-shadow: inset 1px 1px 3px #cccccc;
border-radius: 5px;
}
label {
margin-bottom: 5px;
}
button {
width: 60%;
margin: 0 auto;
}
我们向页面添加了一些 JavaScript,用于列出文件选择器选择的文件,位于控件下方。这是 <input type="file">
参考页面上示例的简化版本
const fileInput = document.querySelector("#file");
const fileList = document.querySelector("#file-list");
fileInput.addEventListener("change", updateFileList);
function updateFileList() {
while (fileList.firstChild) {
fileList.removeChild(fileList.firstChild);
}
const curFiles = fileInput.files;
if (!(curFiles.length === 0)) {
for (const file of curFiles) {
const listItem = document.createElement("li");
listItem.textContent = `File name: ${file.name}; file size: ${returnFileSize(file.size)}.`;
fileList.appendChild(listItem);
}
}
}
function returnFileSize(number) {
if (number < 1e3) {
return `${number} bytes`;
} else if (number >= 1e3 && number < 1e6) {
return `${(number / 1e3).toFixed(1)} KB`;
}
return `${(number / 1e6).toFixed(1)} MB`;
}
“全局”样式
在前面的示例中,我们已经很好地让我们的丑陋控件在现代浏览器中看起来统一。
我们对所有控件及其标签应用了一些全局标准化 CSS,使它们以相同的方式调整大小,采用其父字体等,正如上一篇文章中提到的那样
button,
label,
input,
select,
progress,
meter {
display: block;
font-family: inherit;
font-size: 100%;
margin: 0;
box-sizing: border-box;
width: 100%;
padding: 5px;
height: 30px;
}
我们还在适当的控件上添加了一些统一的阴影和圆角
input[type="text"],
input[type="datetime-local"],
input[type="color"],
select {
box-shadow: inset 1px 1px 3px #cccccc;
border-radius: 5px;
}
对于其他控件,如范围类型、进度条和计量器,它们只是在控件区域周围添加了一个丑陋的框,所以这没有意义。
让我们讨论一下这些控件中每种类型的具体情况,并在此过程中突出显示遇到的困难。
选择和数据列表
一些浏览器现在支持可自定义的 select 元素,这是一组 HTML 和 CSS 功能,它们共同实现了对 <select>
元素及其内容的完全自定义,就像任何常规 DOM 元素一样。在支持的浏览器和代码库中,您不再需要担心下面描述的 <select>
元素的旧技术。
样式化数据列表和选择器(在不支持可自定义选择器的浏览器中)允许可接受的自定义级别,前提是您不希望外观和感觉与默认值相差太大。我们已经设法使这些框看起来相当统一和一致。数据列表调用控件无论如何都是一个 <input type="text">
,所以我们知道这不是问题。
有两件事稍微更麻烦。首先,指示它是下拉菜单的 select 的“箭头”图标在不同浏览器中有所不同。如果您增加 select 框的大小或以丑陋的方式调整其大小,它也倾向于改变。为了解决我们示例中的这个问题,我们首先使用我们的老朋友 appearance: none
完全去掉了图标
select {
appearance: none;
}
然后我们使用生成的内容创建了自己的图标。我们在控件周围添加了一个额外的包装器,因为 ::before
/::after
不适用于 <select>
元素(它们的内容完全由浏览器控制)
<label for="select">Select a fruit</label>
<div class="select-wrapper">
<select id="select" name="select">
<option>Banana</option>
<option>Cherry</option>
<option>Lemon</option>
</select>
</div>
然后我们使用生成的内容生成一个小小的向下箭头,并使用定位将其放置在正确的位置
.select-wrapper {
position: relative;
}
.select-wrapper::after {
content: "▼";
font-size: 1rem;
top: 6px;
right: 10px;
position: absolute;
}
第二个稍微更重要的问题是,当您单击 <select>
框打开它时,您无法控制包含选项的出现框。您可以继承父元素上设置的字体,但无法设置间距和颜色等属性。对于 <datalist>
出现的自动完成列表也是如此。
如果您确实需要完全控制选项样式,您将不得不使用库来生成自定义控件或自己构建。对于 <select>
,您还可以使用 multiple
属性,这会使所有选项都显示在页面上,从而避免了这个问题
<label for="select">Select fruits</label>
<select id="select" name="select" multiple>
…
</select>
当然,这可能也不符合您正在追求的设计,但值得注意!
日期输入类型
日期/时间输入类型(datetime-local
、time
、week
、month
)都存在相同的主要相关问题。实际的包含框与任何文本输入一样容易设置样式,我们在此演示中得到的结果看起来很好。
但是,控件的内部部件(例如,用于选择日期的弹出日历,可用于增加/减少值的微调器)根本无法设置样式,并且无法使用 appearance: none;
将它们去除。如果您确实需要完全控制样式,则必须使用库生成自定义控件或自己构建。
注意:这里也值得提及 <input type="number">
—— 它也有一个微调器,您可以使用它来增加/减少值,因此可能会遇到相同的问题。但是,在 number
类型的情况下,收集的数据更简单,并且可以轻松地改用 tel
输入类型,它具有 text
的外观,但在具有触摸键盘的设备中显示数字键盘。
范围输入类型
<input type="range">
的样式设置很麻烦。您可以使用以下代码完全移除默认的滑块轨道,并将其替换为自定义样式(在本例中为一条细红色轨道)
input[type="range"] {
appearance: none;
background: red;
height: 2px;
padding: 0;
outline: 1px solid transparent;
}
然而,要自定义范围控件的拖动手柄样式非常困难——要完全控制范围样式,您需要使用一些复杂的 CSS 代码,包括多个非标准、特定于浏览器的伪元素。请查看 CSS Tricks 上的使用 CSS 样式化跨浏览器兼容的范围输入,了解所需内容的详细说明。
颜色输入类型
颜色类型的输入控件还不错。在支持的浏览器中,它们通常会给您一个带有小边框的纯色块。
您可以使用以下方法移除边框,只留下颜色块
input[type="color"] {
border: 0;
padding: 0;
}
然而,自定义解决方案是获得显著不同的唯一方法。
文件输入类型
文件类型的输入通常没问题——正如您在示例中看到的,创建与页面其余部分很好地融合的东西相当容易——如果告诉输入这样做,作为控件一部分的输出行将继承父字体,并且您可以以任何您想要的方式设置文件名称和大小的自定义列表;毕竟是我们创建的。
文件选择器唯一的问题是,用于打开文件选择器的按钮完全无法设置样式——它无法调整大小或颜色,甚至不接受不同的字体。
一种解决方法是利用这样一个事实:如果表单控件关联了一个标签,单击标签将激活该控件。因此,您可以使用类似以下代码隐藏实际的表单输入
input[type="file"] {
height: 0;
padding: 0;
opacity: 0;
}
然后将标签样式化为按钮,当按下时,它将按预期打开文件选择器
label[for="file"] {
box-shadow: 1px 1px 3px #cccccc;
background: linear-gradient(to bottom, #eeeeee, #cccccc);
border: 1px solid darkgrey;
border-radius: 5px;
text-align: center;
line-height: 1.5;
}
label[for="file"]:hover {
background: linear-gradient(to bottom, white, #dddddd);
}
label[for="file"]:active {
box-shadow: inset 1px 1px 3px #cccccc;
}
您可以在下面的实时示例中看到上述 CSS 样式的结果。
您还可以按“播放”按钮,在 MDN Playground 中运行该示例并编辑源代码。
计量器和进度条
<meter>
和 <progress>
可能是最差的。正如您在之前的示例中看到的,我们可以相对准确地将它们设置为所需的宽度。但除此之外,它们在任何方面都非常难以设置样式。它们在彼此之间和浏览器之间处理高度设置不一致,您可以为背景着色,但不能为前景条着色,并且在它们上设置 appearance: none
只会使情况变得更糟,而不是更好。
如果您想控制这些功能的样式,或者使用第三方解决方案(例如 progressbar.js),则更容易创建自己的自定义解决方案。
总结
尽管使用 CSS 处理 HTML 表单仍然存在困难,但仍有许多方法可以解决这些问题。没有简洁、通用的解决方案,但现代浏览器提供了新的可能性。目前,最好的解决方案是更多地了解不同浏览器在应用于 HTML 表单控件时对 CSS 的支持方式。
在本模块的下一篇文章中,我们将探讨如何使用为此目的提供的专用现代 HTML 和 CSS 功能来创建完全自定义的 <select>
元素。