HowTo 组件 - howto 复选框

摘要

<howto-checkbox> 表示表单中的布尔选项。最常见的类型 是双重类型,让用户可以在 选中和取消选中。

该元素会尝试自行应用属性 role="checkbox"tabindex="0"role 属性提供辅助功能 屏幕阅读器等技术告诉用户这是什么类型的控制。 tabindex 属性用于为元素启用 Tab 键顺序,使其成为键盘布局 可聚焦且可操作如需详细了解这两个主题,请参阅 ARIA 可以做什么?使用 tabindex

勾选该复选框后,系统会添加一个 checked 布尔值属性,并将 与 true 对应的 checked 属性。此外, 元素设置了一个 aria-checked 属性设为 "true""false",具体取决于其 状态。使用鼠标或空格键点击该复选框即可切换这些状态 选中状态

该复选框还支持 disabled 状态。如果 disabled 属性 设置为 true 或应用 disabled 属性时,该复选框会将 aria-disabled="true",移除 tabindex 属性并返回焦点 如果复选框是当前的 activeElement,则应用于文档。

该复选框会与 howto-label 元素配对,以确保其具有 无障碍名称

参考

演示

在 GitHub 上观看实时演示

用法示例

<style>
  howto-checkbox {
    vertical-align: middle;
  }
  howto-label {
    vertical-align: middle;
    display: inline-block;
    font-weight: bold;
    font-family: sans-serif;
    font-size: 20px;
    margin-left: 8px;
  }
</style>

<howto-checkbox id="join-checkbox"></howto-checkbox>
<howto-label for="join-checkbox">Join Newsletter</howto-label>

代码

(function() {

定义按键代码以帮助处理键盘事件。

  const KEYCODE = {
    SPACE: 32,
  };

<template> 元素克隆内容比使用 innerHTML 的性能更高,因为这样可以避免额外的 HTML 解析开销。

  const template = document.createElement('template');

  template.innerHTML = `
    <style>
      :host {
        display: inline-block;
        background: url('../images/unchecked-checkbox.svg') no-repeat;
        background-size: contain;
        width: 24px;
        height: 24px;
      }
      :host([hidden]) {
        display: none;
      }
      :host([checked]) {
        background: url('../images/checked-checkbox.svg') no-repeat;
        background-size: contain;
      }
      :host([disabled]) {
        background:
          url('../images/unchecked-checkbox-disabled.svg') no-repeat;
        background-size: contain;
      }
      :host([checked][disabled]) {
        background:
          url('../images/checked-checkbox-disabled.svg') no-repeat;
        background-size: contain;
      }
    </style>
  `;


  class HowToCheckbox extends HTMLElement {
    static get observedAttributes() {
      return ['checked', 'disabled'];
    }

每次创建新实例时,都会运行元素的构造函数。可通过解析 HTML、调用 document.createElement('howto-checkbox') 或调用新的 HowToCheckbox(); 来创建实例。构造函数是创建 shadow DOM 的理想位置,但您应避免触摸任何属性或 light DOM 子项,因为它们可能还不可用。

    constructor() {
      super();
      this.attachShadow({mode: 'open'});
      this.shadowRoot.appendChild(template.content.cloneNode(true));
    }

connectedCallback(),在元素插入 DOM 时触发。它非常适合设置初始 roletabindex、内部状态和安装事件监听器。

    connectedCallback() {
      if (!this.hasAttribute('role'))
        this.setAttribute('role', 'checkbox');
      if (!this.hasAttribute('tabindex'))
        this.setAttribute('tabindex', 0);

用户可以在元素的原型连接到此类之前,在元素的实例上设置属性。_upgradeProperty() 方法将检查是否有任何实例属性,并通过适当的类 setter 运行这些属性。如需了解详情,请参阅延迟属性部分。

      this._upgradeProperty('checked');
      this._upgradeProperty('disabled');

      this.addEventListener('keyup', this._onKeyUp);
      this.addEventListener('click', this._onClick);
    }

    _upgradeProperty(prop) {
      if (this.hasOwnProperty(prop)) {
        let value = this[prop];
        delete this[prop];
        this[prop] = value;
      }
    }

disconnectedCallback(),在元素从 DOM 中移除时触发。非常适合执行清理工作,例如释放引用和移除事件监听器。

    disconnectedCallback() {
      this.removeEventListener('keyup', this._onKeyUp);
      this.removeEventListener('click', this._onClick);
    }

属性及其对应的属性应相互镜像。已检查的属性 setter 会处理 true/falsy 值,并将这些值反映到属性的状态。如需了解详情,请参阅避免再次进入部分。

    set checked(value) {
      const isChecked = Boolean(value);
      if (isChecked)
        this.setAttribute('checked', '');
      else
        this.removeAttribute('checked');
    }

    get checked() {
      return this.hasAttribute('checked');
    }

    set disabled(value) {
      const isDisabled = Boolean(value);
      if (isDisabled)
        this.setAttribute('disabled', '');
      else
        this.removeAttribute('disabled');
    }

    get disabled() {
      return this.hasAttribute('disabled');
    }

当 observeAttributes 数组中的任何属性发生更改时,系统会调用 attributeChangedCallback()。这是处理副作用(如设置 ARIA 属性)的绝佳位置。

    attributeChangedCallback(name, oldValue, newValue) {
      const hasValue = newValue !== null;
      switch (name) {
        case 'checked':
          this.setAttribute('aria-checked', hasValue);
          break;
        case 'disabled':
          this.setAttribute('aria-disabled', hasValue);

tabindex 属性未提供从元素完全移除可聚焦性的方法。具有 tabindex=-1 的元素仍然可以使用鼠标或通过调用 focus() 进行聚焦。若要确保某个元素已停用且不可聚焦,请移除 tabindex 属性。

          if (hasValue) {
            this.removeAttribute('tabindex');

如果焦点当前位于此元素上,请调用 HTMLElement.blur() 方法取消聚焦

            this.blur();
          } else {
            this.setAttribute('tabindex', '0');
          }
          break;
      }
    }

    _onKeyUp(event) {

不要处理辅助技术通常使用的修饰符快捷键。

      if (event.altKey)
        return;

      switch (event.keyCode) {
        case KEYCODE.SPACE:
          event.preventDefault();
          this._toggleChecked();
          break;

系统将忽略任何其他按键操作,并将其传递回浏览器。

        default:
          return;
      }
    }

    _onClick(event) {
      this._toggleChecked();
    }

_toggleChecked() 会调用选中的 setter 并翻转其状态。由于 _toggleChecked() 仅由用户操作引起,因此还会分派更改事件。此事件气泡是为了模拟 <input type=checkbox> 的原生行为。

    _toggleChecked() {
      if (this.disabled)
        return;
      this.checked = !this.checked;
      this.dispatchEvent(new CustomEvent('change', {
        detail: {
          checked: this.checked,
        },
        bubbles: true,
      }));
    }
  }

  customElements.define('howto-checkbox', HowToCheckbox);
})();