操作說明元件 - 操作核取方塊

摘要

<howto-checkbox> 代表表單中的布林值選項。最常見的核取方塊類型是雙重類型,可讓使用者在兩種選擇 (已勾選和未勾選) 之間切換。

元素會在首次建立時嘗試自行套用屬性 role="checkbox"tabindex="0"role 屬性可協助螢幕閱讀器等輔助技術,向使用者說明這是什麼類型的控制項。tabindex 屬性會將元素納入分頁順序,讓使用者可透過鍵盤聚焦及操作該元素。如要進一步瞭解這兩個主題,請參閱「ARIA 的功能」和「使用 tabindex」。

勾選核取方塊後,系統會新增 checked 布林值屬性,並將對應的 checked 屬性設為 true。此外,元素會根據其狀態,將 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') 或呼叫 new HowToCheckbox() 來建立例項。建構函式是建立陰影 DOM 的好地方,但請避免觸碰任何屬性或 light DOM 子項,因為這些項目可能尚未可用。

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

當元素插入 DOM 時,connectedCallback() 就會觸發。這是設定初始 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);
    }

屬性及其對應的屬性應彼此相同。checked 的屬性設定器會處理真值/假值,並將這些值反映至屬性的狀態。詳情請參閱「避免重複記錄」一節。

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

當 observedAttributes 陣列中的任何屬性變更時,系統會呼叫 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);
})();