高级表单样式

在本文中,我们将了解如何使用 CSS 为那些较难设置样式的表单控件类型(即“不好”和“丑陋”类别)设置样式。正如我们在上一篇文章中看到的,文本字段和按钮非常容易设置样式;现在我们将深入研究如何为那些更棘手的部分设置样式。

预备知识 HTMLCSS 的基本了解。
目标 了解表单的哪些部分难以设置样式以及原因;学习如何自定义它们。

回顾我们在上一篇文章中说过的,我们有

不好的:有些元素更难设置样式,需要更复杂的 CSS 或一些更具体的技巧

丑陋的:有些元素无法使用 CSS 彻底设置样式。这些包括

让我们首先讨论 appearance 属性,它有助于使上述所有内容更具可样式性。

appearance:控制 OS 级别样式

在上一篇文章中,我们提到,从历史上看,Web 表单控件的样式很大程度上源于底层操作系统,这也是难以自定义这些控件外观的部分原因。

appearance 属性的创建是为了控制将哪些 OS 或系统级样式应用于 Web 表单控件。到目前为止,最有用的值(也可能是您唯一会使用的值)是 none。它会尽可能阻止您应用它的任何控件使用系统级样式,并允许您使用 CSS 自己构建样式。

例如,让我们来看以下控件

html
<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 会删除系统级样式。

css
input {
  appearance: none;
}

下面的实时示例向您展示了它们在您的系统中的外观——左侧是默认样式,右侧是应用了上述 CSS 后的样式。

在大多数情况下,效果是删除样式化的边框,这使得 CSS 样式更容易一些,但并非必不可少。在某些情况下,例如单选按钮和复选框,它变得更加有用。我们现在就来看看这些。

搜索框和 appearance

appearance: none; 值过去在一致地设置 <input type="search"> 元素的样式时特别有用。没有它,Safari 不允许对其设置 heightfont-size 值。然而,Safari 16 及更高版本已不再如此。如果您的浏览器支持矩阵包含旧于 16 的 Safari 版本,您可能仍希望显式地使用 appearance: none; 定位 input[type="search"]

在搜索输入中,当值不为空时出现的“x”删除按钮在 Edge 和 Chrome 中失去焦点时消失,但在 Safari 中保留。要通过 CSS 删除,您可以使用以下规则

css
input[type="search"]:not(:focus, :active)::-webkit-search-cancel-button {
  display: none;
}

使用 appearance 为复选框和单选按钮设置样式

默认情况下,为复选框或单选按钮设置样式很棘手。复选框和单选按钮的默认样式大小不应更改,当您尝试更改时,浏览器的反应会非常不同。有些会增加控件的大小,有些则保持控件大小不变并在其周围添加额外的空间。

更好的方法是使用 appearance: none; 完全移除复选框和单选按钮的默认外观,然后为其各种状态添加您自己的样式。

让我们来看这个 HTML 示例

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>

让我们用自定义复选框设计来设置这些样式。我们将首先移除原始复选框样式

css
input[type="checkbox"] {
  appearance: none;
}

然后我们可以使用 :checked:disabled 伪类来改变我们自定义复选框的状态变化时的外观

css
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 — 复选框(或单选按钮)处于禁用状态 — 无法与其交互。

您可以看到实时结果

我们还创建了几个其他示例,为您提供更多想法

对于“丑陋”的元素能做些什么?

现在让我们将注意力转向“丑陋”的控件——那些真正难以彻底设置样式的控件。简而言之,它们是下拉框、复杂控件类型(如 colordatetime-local)以及面向反馈的控件(如 <progress><meter>)。

问题在于这些元素在不同浏览器中的默认外观差异很大,虽然您可以以某些方式设置它们的样式,但它们内部的某些部分是无法设置样式的。

如果您愿意接受外观和感觉上的一些差异,您可以使用一些简单的样式来显著改善情况。这包括一致的大小和像 background-color 这样的属性的样式,以及使用 appearance 来移除一些系统级样式。

请看下面的示例,它展示了许多“丑陋”的表单功能。

您还可以按“播放”按钮,在 MDN Playground 中运行该示例并编辑源代码。

此示例已应用以下 CSS

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"> 参考页面上示例的简化版本

js
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,使它们以相同的方式调整大小,采用其父字体等,正如上一篇文章中提到的那样

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;
}

我们还在适当的控件上添加了一些统一的阴影和圆角

css
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 完全去掉了图标

css
select {
  appearance: none;
}

然后我们使用生成的内容创建了自己的图标。我们在控件周围添加了一个额外的包装器,因为 ::before/::after 不适用于 <select> 元素(它们的内容完全由浏览器控制)

html
<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>

然后我们使用生成的内容生成一个小小的向下箭头,并使用定位将其放置在正确的位置

css
.select-wrapper {
  position: relative;
}

.select-wrapper::after {
  content: "▼";
  font-size: 1rem;
  top: 6px;
  right: 10px;
  position: absolute;
}

第二个稍微更重要的问题是,当您单击 <select> 框打开它时,您无法控制包含选项的出现框。您可以继承父元素上设置的字体,但无法设置间距和颜色等属性。对于 <datalist> 出现的自动完成列表也是如此。

如果您确实需要完全控制选项样式,您将不得不使用库来生成自定义控件或自己构建。对于 <select>,您还可以使用 multiple 属性,这会使所有选项都显示在页面上,从而避免了这个问题

html
<label for="select">Select fruits</label>
<select id="select" name="select" multiple>
  …
</select>

当然,这可能也不符合您正在追求的设计,但值得注意!

日期输入类型

日期/时间输入类型(datetime-localtimeweekmonth)都存在相同的主要相关问题。实际的包含框与任何文本输入一样容易设置样式,我们在此演示中得到的结果看起来很好。

但是,控件的内部部件(例如,用于选择日期的弹出日历,可用于增加/减少值的微调器)根本无法设置样式,并且无法使用 appearance: none; 将它们去除。如果您确实需要完全控制样式,则必须使用库生成自定义控件或自己构建。

注意:这里也值得提及 <input type="number"> —— 它也有一个微调器,您可以使用它来增加/减少值,因此可能会遇到相同的问题。但是,在 number 类型的情况下,收集的数据更简单,并且可以轻松地改用 tel 输入类型,它具有 text 的外观,但在具有触摸键盘的设备中显示数字键盘。

范围输入类型

<input type="range"> 的样式设置很麻烦。您可以使用以下代码完全移除默认的滑块轨道,并将其替换为自定义样式(在本例中为一条细红色轨道)

css
input[type="range"] {
  appearance: none;
  background: red;
  height: 2px;
  padding: 0;
  outline: 1px solid transparent;
}

然而,要自定义范围控件的拖动手柄样式非常困难——要完全控制范围样式,您需要使用一些复杂的 CSS 代码,包括多个非标准、特定于浏览器的伪元素。请查看 CSS Tricks 上的使用 CSS 样式化跨浏览器兼容的范围输入,了解所需内容的详细说明。

颜色输入类型

颜色类型的输入控件还不错。在支持的浏览器中,它们通常会给您一个带有小边框的纯色块。

您可以使用以下方法移除边框,只留下颜色块

css
input[type="color"] {
  border: 0;
  padding: 0;
}

然而,自定义解决方案是获得显著不同的唯一方法。

文件输入类型

文件类型的输入通常没问题——正如您在示例中看到的,创建与页面其余部分很好地融合的东西相当容易——如果告诉输入这样做,作为控件一部分的输出行将继承父字体,并且您可以以任何您想要的方式设置文件名称和大小的自定义列表;毕竟是我们创建的。

文件选择器唯一的问题是,用于打开文件选择器的按钮完全无法设置样式——它无法调整大小或颜色,甚至不接受不同的字体。

一种解决方法是利用这样一个事实:如果表单控件关联了一个标签,单击标签将激活该控件。因此,您可以使用类似以下代码隐藏实际的表单输入

css
input[type="file"] {
  height: 0;
  padding: 0;
  opacity: 0;
}

然后将标签样式化为按钮,当按下时,它将按预期打开文件选择器

css
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> 元素