高级表单样式

在本文中,我们将了解如何使用 CSS 样式化那些更难样式化的表单控件类型 — “糟糕”和“丑陋”类别。正如我们在 上一篇文章 中看到的,文本字段和按钮非常容易样式化;现在我们将深入研究如何样式化更复杂的部分。

先决条件 HTMLCSS 的基本理解。
目标 为了理解表单中哪些部分难以进行样式设置,以及原因;学习如何自定义它们。

回顾我们之前文章中提到的内容,我们有

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

丑陋的地方:某些元素无法使用 CSS 彻底进行样式设置。这些元素包括

首先让我们谈谈 appearance 属性,它对于使以上所有元素更易于设置样式非常有用。

appearance: 控制操作系统级别的样式

在之前的文章中,我们提到历史上,Web 表单控件的样式很大程度上来自底层操作系统,这也是自定义这些控件外观的问题所在。

appearance 属性是为了控制应用于 Web 表单控件的 OS 或系统级样式而创建的。迄今为止,最实用的值(可能也是你唯一用到的值)是 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 样式设置稍微容易一些,但并不真正必要。在几个情况下 - 搜索和单选按钮/复选框,它变得更加有用。我们现在将看看这些情况。

驯服搜索框

<input type="search"> 本质上只是一个文本输入框,那么为什么 appearance: none; 在这里有用呢?答案是 Safari 搜索框有一些样式限制 - 例如,你无法自由调整它们的 heightfont-size

这可以使用我们的老朋友 appearance: none; 来解决,它会禁用默认外观

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

在下面的示例中,你可以看到两个相同样式的搜索框。右边的搜索框应用了 appearance: none;,而左边的没有。如果你在 macOS 上的 Safari 中查看,你会发现左边的搜索框尺寸不正确。

有趣的是,在搜索框上设置边框/背景也会解决这个问题。以下带样式的搜索框没有应用 appearance: none;,但在 Safari 中不会像之前的示例那样出现问题。

注意:你可能已经注意到,在搜索框中,当搜索值不为空时出现的“x”删除图标,在 Edge 和 Chrome 中,当输入框失去焦点时会消失,但在 Safari 中会保留。要使用 CSS 移除它,可以使用 input[type="search"]:not(:focus, :active)::-webkit-search-cancel-button { display: none; }

样式设置复选框和单选按钮

默认情况下,样式设置复选框或单选按钮很棘手。复选框和单选按钮的大小并非旨在通过其默认设计进行更改,并且浏览器在尝试时反应非常不同。

例如,考虑以下简单的测试用例

html
<label
  ><span><input type="checkbox" name="q5" value="true" /></span> True</label
>
<label
  ><span><input type="checkbox" name="q5" value="false" /></span> False</label
>
css
span {
  display: inline-block;
  background: red;
}

input[type="checkbox"] {
  width: 100px;
  height: 100px;
}

不同的浏览器对复选框和 span 的处理方式不同,通常是丑陋的方式

浏览器 渲染
Firefox 71 (macOS) Rounded corners and 1px light grey border
Firefox 57 (Windows 10) Rectangular corners with 1px medium grey border
Chrome 77 (macOS)、Safari 13、Opera Rounded corner with 1px medium grey border
Chrome 63 (Windows 10) Rectangular borders with slightly greyish background instead of white.
Edge 16 (Windows 10) Rectangular borders with slightly greyish background instead of white.

对单选按钮/复选框使用 appearance: none

如前所述,你可以使用 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: #ddd;
  color: gray;
}

你将在 下一篇文章 中了解更多关于这些伪类以及更多内容;上面的伪类执行以下操作

  • :checked - 复选框(或单选按钮)处于选中状态 - 用户已单击/激活它。
  • :disabled - 复选框(或单选按钮)处于禁用状态 - 无法与之交互。

你可以看到实时结果

我们还创建了一些其他示例,以提供更多想法

如果你在不支持 appearance 的浏览器中查看这些复选框,你的自定义设计将丢失,但它们仍然看起来像复选框并且可以使用。

如何处理“丑陋”的元素?

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

问题是这些元素在不同浏览器中具有非常不同的默认外观,虽然你可以在某些方面对它们进行样式设置,但它们内部的某些部分实际上无法设置样式。

如果你准备接受外观和感觉上的差异,你可以使用一些简单的样式来使尺寸保持一致,对背景颜色等进行统一样式设置,并使用外观来摆脱一些系统级样式。

以下示例展示了几个“丑陋”的表单功能的实际应用

此示例应用了以下 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 #ccc;
  border-radius: 5px;
}

label {
  margin-bottom: 5px;
}

button {
  width: 60%;
  margin: 0 auto;
}

注意:如果你想在多个浏览器中同时测试这些示例,你可以 点击此处查看(也可以 点击此处查看源代码)。

还要记住,我们在页面中添加了一些 JavaScript 代码,这些代码列出了文件选择器选择的

如你所见,我们已经成功地使它们在现代浏览器中看起来相当统一。

我们对所有控件及其标签应用了一些全局规范化 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 #ccc;
  border-radius: 5px;
}

在其他控件(如范围类型、进度条和仪表)上,它们只是在控件区域周围添加一个丑陋的框,因此没有意义。

让我们讨论一下这些控件类型的具体情况,并在此过程中重点介绍一些困难。

选择器和数据列表

在现代浏览器中,只要你不想改变默认外观太多,选择器和数据列表通常很容易设置样式。

我们已经成功地使框的基本外观看起来相当统一且一致。数据列表控件本身就是 <input type="text">,因此我们知道这不会有问题。

有两件事稍微麻烦一些。首先,选择器的“箭头”图标指示它是一个下拉菜单,在不同的浏览器中有所不同。如果增大选择框的大小,或者以丑陋的方式调整大小,它也会发生变化。为了在我们的示例中修复这个问题,我们首先使用了我们的老朋友 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> 出现时的自动完成列表,也是如此。

如果你确实需要完全控制选项的样式设置,你必须使用某种库来生成自定义控件,或者构建你自己的自定义控件,或者在选择器的情况下使用 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 代码,包括多个非标准的、特定于浏览器的伪元素。查看 Styling Cross-Browser Compatible Range Inputs with CSS,了解 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 #ccc;
  background: linear-gradient(to bottom, #eee, #ccc);
  border: 1px solid rgb(169, 169, 169);
  border-radius: 5px;
  text-align: center;
  line-height: 1.5;
}

label[for="file"]:hover {
  background: linear-gradient(to bottom, #fff, #ddd);
}

label[for="file"]:active {
  box-shadow: inset 1px 1px 3px #ccc;
}

您可以在下面的实时示例中看到上述 CSS 样式的结果(另请参见 styled-file-picker.html 实时,以及 源代码)。

仪表和进度条

<meter><progress> 可能是最糟糕的。正如您在前面的示例中看到的,我们可以将它们设置为相对准确的所需宽度。但除此之外,它们真的很难以任何方式设置样式。它们在彼此之间以及浏览器之间不一致地处理高度设置,您可以对背景进行颜色设置,但不能对前景条进行颜色设置,并且对它们设置 appearance: none 会使情况更糟,而不是更好。

如果您想控制样式,或者使用第三方解决方案(如 progressbar.js),最好创建自己的自定义解决方案。

文章 How to build custom form controls 提供了如何使用 HTML、CSS 和 JavaScript 构建自定义设计的 select 的示例。

总结

虽然使用 CSS 与 HTML 表单仍然存在困难,但有一些方法可以解决许多问题。没有干净的、通用的解决方案,但现代浏览器提供了新的可能性。目前,最好的解决方案是更多地了解不同浏览器在将 CSS 应用于 HTML 表单控件时对 CSS 的支持方式。

在本模块的下一篇文章中,我们将探讨现代浏览器为我们提供的不同 UI 伪类,用于在不同状态下对表单进行样式设置。

高级主题