认识 selectmenu,一个完全可样式化的 select 元素

Avatar of Patrick Brosset
Patrick Brosset

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 $200 免费积分!

我想向您介绍一个新的实验性表单控件,名为 <selectmenu>。 我们将深入探讨它,包括它比传统 <select> 元素更容易样式化 的方式。 不过首先,让我们了解一下为什么首先需要像 <selectmenu> 这样的东西,因为它仍在不断发展和开发中。

问问任何 Web 开发人员他们认为当今 Web 平台缺少什么,他们很有可能在他们的清单中列出对表单控件进行样式设置的能力。 事实上,表单样式被评为 2020 年 CSS 状态调查中缺少的十大事物之一。 然后,它由 Greg Whitworth 进一步调查,他 展示了<select> 是 Web 开发人员在使用 CSS 进行样式设置时遇到的问题最多的控件。

虽然对 <select> 的按钮部分(在弹出窗口关闭时在页面中看到的东西)的外观进行样式设置相对容易,但几乎不可能对选项(在弹出窗口打开时看到的东西)进行样式设置,更不用说在弹出窗口中添加更多内容了。

Showing the default UI of the select element in Safari.
Safari 中 <select> 元素的默认 UI

因此,设计系统和组件库一直在推出自己的选择,这些选择从头开始使用自定义 HTML 标记、CSS 以及大量的 JavaScript 构建而成,以便拥有能够与其他组件很好地集成的选择。

不幸的是,以正确的可访问性语义、键盘支持和弹出窗口定位正确地做到这一点并不容易。 多年来,Web 开发人员投入了大量时间,试图一遍又一遍地解决相同的问题,并且有很多不可访问的选择。

是时候拥有一个可样式化的内置 <select>,这样我们就不必再写这些代码了!

Open UI 计划

The Open UI logo, which is a green oval with a rounded fork-like shape with three prongs inside.

Open UI 是一个由开发人员、设计师和浏览器实现者组成的团队,他们着手解决这个问题,并在解决这个问题的同时,也解决了其他缺少的控件。

Open UI 的目的是最终使 Web 开发人员能够对内置 UI 控件进行样式设置和扩展(这包括 <select>,但也包括下拉菜单、复选框、单选按钮等)。 为了实现这一点,他们会生成有关如何在 Web 平台上实现这些控件以及它们应该解决的可访问性要求的规范。

该项目仍处于起步阶段,但进展迅速,正如我们将在下面看到的那样,令人兴奋的事情正在发生。

您可以加入该团队,参与会议、研究和规范工作。

<selectmenu> 控件

根据 Open UI 的 <select> 提案,新的 <selectmenu> 控件的实现已在 Chromium 中启动! 这项工作由 Microsoft Edge 团队与 Google Chrome 团队合作完成。 它甚至已经在基于 Chromium 的浏览器中提供,方法是在 about:flags 页面中启用“实验性 Web 平台功能”标志。

<selectmenu> 是一个新的内置控件,它提供选项选择用户体验,就像 <select> 一样,带有显示所选值标签的按钮、单击该按钮时出现的弹出窗口以及显示的选项列表。

为什么是新名称?

为什么不直接替换现有的 <select> 控件? “selectmenu” 这个名称最初是一个工作名称,但它似乎一直沿用至今,还没有人想出更好的名称。

更重要的是,现有的 <select> 控件在 Web 上已经使用了很长时间。 因此,它可能永远无法以任何重大方式更改,否则会导致重大兼容性问题。

因此,计划(记住,这都是非常实验性的)是让 <selectmenu> 成为一个新的控件,独立于 <select>

立即尝试

这还没有准备好用于生产环境,但如果您像我一样兴奋地使用它,以下是如何操作

  1. 打开基于 Chromium 的浏览器的 Canary 版本(Chrome、Edge)。
  2. about:flags 页面中切换“实验性 Web 平台功能”标志,然后重新启动。
  3. 在网页中将任何 <select> 替换为 <selectmenu>

就是这样! 默认情况下它不会做太多,但正如我们将在后面看到的那样,您将能够使用此标签名称更改对控件进行广泛的样式设置和扩展。

我们喜欢反馈!

在我们深入了解如何使用该控件之前,如果您确实使用了它,Open UI 团队和在 Chromium 中负责实施该控件的人员非常乐意听取您的任何反馈。

通过成为早期测试人员,您可以积极帮助他们为所有人改进该控件。 因此,如果您在设计控件时遇到错误或限制,请通过 在 Open UI GitHub 存储库中创建一个问题 发送您的反馈!

现在,让我们谈谈该控件是如何工作的。

<selectmenu> 控件的解剖结构

因为 selectmenu 的各个部分可以进行样式设置,所以首先了解它的内部解剖结构很重要。

Showing the boundaries of a selectmenu element.
  • <selectmenu> 是包含按钮和列表框的根元素。
  • <button> 是触发列表框可见性的元素。
  • <selected-value> 是显示当前选择选项值的元素(可选)。 请注意,这部分不一定必须放在 <button> 部分中。
  • <listbox> 是包含 <option><optgroup> 的包装器。
  • <optgroup> 将选项分组在一起,并带有可选的标签。
  • <option> 表示用户可以选择的潜在值。 可以有一个或多个。

默认行为

<selectmenu> 控件的默认行为模仿 <select> 控件的行为。 您可以像使用原生 <select> 一样使用它,使用以下最小标记。

<selectmenu>
  <option>Option 1</option>
  <option>Option 2</option>
  <option>Option 3</option>
</selectmenu>

这样做时,默认的 <button><selected-value><listbox >将为您创建。

对控件的各个部分进行样式设置

这就是事情变得有趣的地方! 对控件进行样式设置以匹配您的要求的一种方法是使用 CSS ::part() 伪元素来选择要设置样式的控件解剖结构中的不同部分。

考虑以下使用 ::part() 对按钮和列表框部分进行样式设置的示例

<style>
  .my-select-menu::part(button) {
    color: white;
    background-color: #f00;
    padding: 5px;
    border-radius: 5px;
  }

  .my-select-menu::part(listbox) {
    padding: 10px;
    margin-top: 5px;
    border: 1px solid red;
    border-radius: 5px;
  }
</style>
<selectmenu class="my-select-menu">
  <option>Option 1</option>
  <option>Option 2</option>
  <option>Option 3</option>
</selectmenu>

上面的示例会产生以下样式

A styled selectmenu element with a red button background and a red border around the listbox.

::part() 可用于对控件的 <button><selected-value><listbox> 部分进行样式设置。

使用您自己的标记

如果以上内容不足以满足您的需求,您可以通过提供自己的标记来替换默认标记,并扩展或重新排序各个部分,从而更深入地自定义控件。

一个 <selectmenu> 具有命名的 插槽,可以引用它们来替换默认部分。例如,要将默认按钮替换为自己的按钮,您可以执行以下操作:

<style>
  .my-custom-select [slot='button'] {
    display: flex;
    align-content: center;
  }
  .my-custom-select button {
    padding: 5px;
    border: none;
    background: #f06;
    border-radius: 5px 0 0 5px;
    color: white;
    font-weight: bold;
  }
  .my-custom-select .label {
    padding: 5px;
    border: 1px solid #f06;
    border-radius: 0 5px 5px 0;
  }
</style>
<selectmenu class="my-custom-select">
  <div slot="button">
    <button behavior="button">Open</button>
    <span class="label">Choose an option</span>
  </div>
  <option>Option 1</option>
  <option>Option 2</option>
  <option>Option 3</option>
</selectmenu>

外部 <div> 上的 slot="button" 属性告诉 <selectmenu> 将其默认按钮替换为 <div> 的内容。

内部 <button> 上的 behavior="button" 属性告诉浏览器,此元素是我们想要用作新按钮的元素。浏览器会自动将所有点击和键盘处理行为应用于此元素,以及相应的可访问性语义。

以上代码段将生成以下样式:

A styled selectmenu with a bright pink open button and a box-shadow around the listbox.

请注意,slotbehavior 属性也可以在同一个元素上使用。

您可以通过类似的方式替换默认的列表框部分:

<style>
  .my-custom-select [popup] {
    width: 300px;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
    gap: 10px;
    padding: 10px;
    box-shadow: none;
    margin: 10px 0;
    border: 1px solid;
    background: #f7f7f7;
  }
</style>
<selectmenu class="my-custom-select">
  <div slot="listbox">
    <div popup behavior="listbox">
      <option>Option 1</option>
      <option>Option 2</option>
      <option>Option 3</option>
      <option>Option 4</option>
      <option>Option 5</option>
    </div>
  </div>
</selectmenu>

有趣的是,这里使用的 <div popup> 也正在由 Open UI 提议 并目前在 Chromium 中实现。

具有 behavior="listbox" 的元素必须是 <div popup>。应用 behavior="listbox" 会告诉浏览器在点击 <selectmenu> 按钮时打开此元素,用户可以使用鼠标、箭头键和触摸在其中选择 <option>

以上代码段将生成以下样式:

A styled selectmenu where the list box is split into two columns.

扩展标记

除了可以用自己的标记替换默认部分外,如上所述,您还可以通过添加新元素来扩展控件的标记。这对于用额外信息增强列表框或按钮,或添加新功能非常有用。

考虑以下示例:

<style>
  .my-custom-select [slot='button'] {
    display: flex;
    align-items: center;
    gap: 1rem;
  }
  .my-custom-select button {
    border: none;
    margin: 0;
    padding: 0;
    width: 2rem;
    height: 2rem;
    border-radius: 50%;
    display: grid;
    place-content: center;
  }
  .my-custom-select button::before {
    content: '\25BC';
  }
  .my-custom-select [popup] {
    padding: 0;
  }
  .my-custom-select .section {
    padding: 1rem 0 0;
    background: radial-gradient(ellipse 60% 50px at center top, #000a 0%, transparent 130%);
  }
  .my-custom-select h3 {
    margin: 0 0 1rem 0;
    text-align: center;
    color: white;
  }
  .my-custom-select option {
    text-align: center;
    padding: 0.5rem;
  }
</style>
<selectmenu class="my-custom-select">
  <div slot="button">
    <span class="label">Choose a plant</span>
    <span behavior="selected-value" slot="selected-value"></span>
    <button behavior="button"></button>
  </div>
  <div slot="listbox">
    <div popup behavior="listbox">
      <div class="section">
        <h3>Flowers</h3>
        <option>Rose</option>
        <option>Lily</option>
        <option>Orchid</option>
        <option>Tulip</option>
      </div>
      <div class="section">
        <h3>Trees</h3>
        <option>Weeping willow</option>
        <option>Dragon tree</option>
        <option>Giant sequoia</option>
      </div>
    </div>
  </div>
</selectmenu>

在这里,我们使用自定义标记来包装选项列表并创建我们自己的内容,如下所示:

A styled selectmenu that contains options containing sub-options in the listbox.

替换整个影子 DOM

最后,如果以上内容还不够,您还可以通过调用 attachShadow() 来完全替换控件的默认影子 DOM。例如,上一节中的演示可以修改如下:

<selectmenu id="my-custom-select"></selectmenu>
<script>
  const myCustomSelect = document.querySelector('#my-custom-select')
  const shadow = myCustomSelect.attachShadow({ mode: 'closed' })
  shadow.innerHTML = `
    <style>
    .button-container {
      display: flex;
      align-items: center;
      gap: 1rem;
    }
    button {
      border: none;
      margin: 0;
      padding: 0;
      width: 2rem;
      height: 2rem;
      border-radius: 50%;
      display: grid;
      place-content: center;
    }
    button::before {
      content: '\\0025BC';
    }
    [popup] {
      padding: 0;
    }
    .section {
      padding: 1rem 0 0;
      background: radial-gradient(ellipse 60% 50px at center top, #000a 0%, transparent 130%);
    }
    h3 {
      margin: 0 0 1rem 0;
      text-align: center;
      color: white;
    }
    option {
      text-align: center;
      padding: 0.5rem;
    }
    option:hover {
      background-color: lightgrey;
    }
  </style>
  <div class="button-container">
    <span class="label">Choose a plant</span>
    <span behavior="selected-value" slot="selected-value"></span>
    <button behavior="button"></button>
  </div>
  <div popup behavior="listbox">
    <div class="section">
      <h3>Flowers</h3>
      <option>Rose</option>
      <option>Lily</option>
      <option>Orchid</option>
      <option>Tulip</option>
    </div>
    <div class="section">
      <h3>Trees</h3>
      <option>Weeping willow</option>
      <option>Dragon tree</option>
      <option>Giant sequoia</option>
    </div>
  </div>
  `
</script>

以这种方式编写,<selectmenu> 的自定义标记完全封装在其影子 DOM 中。因此,<selectmenu> 可以放到任何页面中,而不必担心周围内容的样式干扰。

结束语

如我们所见,新的实验性 <selectmenu> 控件在样式化甚至扩展传统 <select> 方面提供了很大的灵活性。而且它以所有正确的方式做到了这一点,因为它内置于浏览器中,在浏览器中,可访问性和视口感知定位会为您处理。

Open UI 有关于 <selectmenu>更多文档,如果您想查看更多展示如何使用 <selectmenu> 的代码,这里有一些演示

再说一次,这是一项正在进行的工作,肯定会随着 Open UI 小组收到的反馈而改变。

我迫不及待地想看到规范开始出现在 HTML 和 CSS 标准机构中,以及实现变得更加稳定,以及看到其他浏览器引擎对此产生兴趣。您可以帮助实现这一点!测试控件、报告问题参与其中 都是推动这项工作取得进展的好方法。