高階表單樣式

在本文中,我們將瞭解如何使用 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> 元素