使用 custom element 建立自定義元素

很早咱們就能夠在 HTML 文檔中寫 <custome-element></custom-element> 這樣的自定義名稱標籤。可是瀏覽器對於不認識的標籤一概當成一個普通的行內元素處理,沒有相關語義。雖然咱們能用 JavaScript 代碼給它添加一些功能,可是並無生命週期相關的函數供咱們作一些初始化和銷燬的處理。 javascript

經過瀏覽器提供的 Custom elements api 咱們能定義一個自定義元素,而且告知 HTML 解析器如何正確地構造一個元素,以及在該元素的屬性變化時執行相應的處理。html

定義新元素

好比咱們想要像 <date-string ln="zh"></data-string> 這樣使用一個顯示日期字符串的標籤,而且在 ln 屬性爲 zh 時顯示中文格式,en 時顯示英文格式。java

首先咱們定義一個類 DateString 派生自 HTMLElement。chrome

class DateString extends HTMLElement {
  constructor() {
    super()
    return
  }

  // 返回須要監聽的屬性,當屬性值改變的時候會調用 attributeChangedCallback 這個方法
  static get observedAttributes () {
    return ['ln']
  }

  attributeChangedCallback (name, oldValue, newValue) {
    this.updateRendering (newValue)
  }

  // 元素插入到文檔中時調用
  connectedCallback() {
    const ln = this.getAttribute('ln')
    this.updateRendering(ln)
  }

  // 元素從文檔中移除時調用
  disconnectedCallback () {
    window.clearInterval(this.interval)
  }

  updateRendering (ln = 'zh') {
    // 一個比較好的實踐就是在渲染時,檢查元素的 ownerDocument.defaultView, 若是不存在則什麼都不幹
    if (!this.ownerDocument.defaultView) {
      return
    }
    if (this.interval) {
      window.clearInterval(this.interval)
    }
    this.interval = setInterval(() => {
      if (ln === 'zh') {
        this.innerHTML = new Date().toLocaleString()
      } else {
        this.innerHTML = new Date().toString()
      }
    }, 1000)
  }
}

而後調用 customElements.define() 註冊這個自定義元素,設置屬性並插入到 body 中。api

customElements.define("date-string", DateString)
    const dateStr = document.createElement('date-string')
    dateStr.setAttribute('ln', 'zh')
    document.body.appendChild(dateStr)

也能夠用直接調用構造函數建立元素瀏覽器

const dateStr = new DateString()
  dateStr.setAttribute('ln', 'zh')
  document.body.appendChild(dateStr)

自定義元素可使用符合規範的任意屬性名,下面說的派生內置元素類型要自定義屬性,則要用 data-* app

上面代碼那樣設置屬性很繁瑣,咱們能夠作一個屬性映射,以指望 dateStr.ln = 'zh' 這樣賦值dom

class DateString extends HTMLElement {
  ...

  get ln () {
    return this.getAttribute('ln')
  }

  set ln (value) {
    this.setAttribute('ln', value)
  }
}

元素升級

除了像上面那樣用 JavaScript 代碼建立元素並添加到 body 下面,也能夠直接在 HTML 寫自定義元素函數

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <date-string ln="zh"></date-string>
  <script>
    class DateString extends HTMLElement {
      ...
    }
    customElements.define("date-string", DateString)
  </script>
</body>
</html>

上面的代碼依然正常工做。首先瀏覽器正常解析文檔,遇到 <date-string> 標籤時,把它當作一個普通的行內元素對待,其實是 HTMElement 類型(若是標籤的名稱中沒有中劃線,<unknow>,那麼則是 HTMLUnknownElement 類型實例)。當 <script> 標籤中的代碼執行後,註冊了名爲 date-string 的自定義元素,瀏覽器再對文檔中的 data-string 元素作升級處理,調用相應的生命週期函數。 ui

須要注意的是,只有插入到文檔中的元素纔會升級:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <date-string ln="zh" id="dateStr"></date-string>
  <script>
    const dateStr = document.getElementById('dateStr')
    const other = document.createElement('date-string')

    console.log(dateStr instanceof HTMLElement) // true
    console.log(other instanceof HTMLElement) // true

    class DateString extends HTMLElement {}
    customElements.define("date-string", DateString)

    console.log(dateStr instanceof DateString) // true
    console.log(other instanceof DateString) // false

    // 插入到文檔中後,other 元素升級爲自定義元素類型 DateString
    document.body.appendChild(other)
    console.log(other instanceof DateString) // true
  </script>
</body>
</html>

派生內置元素類型

除了從 HTMLElement 派生自定義元素,咱們還能夠從 HTMLButtonElement, HTMLDivElement 等內置元素類型派生自定義元素。這麼作的好處是,能夠保留內置元素的語義化功能。好比,HTMLButtonElement 有 active 狀態,經過按 tab 鍵可使 button 元素得到焦點,而後按回車鍵至關於點擊 button 元素。如今咱們從 HTMLButtonElement 派生一個自定義的按鈕,並在點擊的時候改變背景顏色。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <button is="colored-button">colored</button>
  <script>
    class ColoredButton extends HTMLButtonElement {
      constructor () {
        super();
        this.addEventListener('click', () => {
          let ox = Math.floor(Math.random()*255).toString(16)
          this.style.background = `#${ox.repeat(3)}`
        })
      }
    }

    customElements.define('colored-button', ColoredButton, {
      extends: 'button'
    })
  </script>
</body>
</html>

這個按鈕在行爲上與內置的 button 同樣, 能夠獲取焦點,提交表單,也有禁用屬性等。

派生內置元素與自定義元素略微不一樣,調用 customElements.define 時要傳入第三個參數代表是從那個元素派生,這裏使用的名稱是 'button' 即標籤名,由於瀏覽器是靠識別標籤名來提供語義和默認行爲,基於這一點,使用的時候也是用的本來的標籤名 button,而後再給一個 is 屬性指定自定義元素的名稱。

經過 document.createElement 建立元素時也有不一樣

const coloredButton = document.createElement('button', {
  is: 'colored-button'
})

也能夠直接調用構造函數建立

const coloredButton = new ColoredButton

console.log(coloredButton.localName) // => 'button'
console.log(coloredButton.getAttribute(is)) // => 'colored-button'

注:直到 chrome 61 版本,擴展內置元素依然在開發中。參見連接

構造函數的一些限制

自定義元素的構造函數必須遵循以下限制

  • 構造函數中不能調用 document.write() 和 document.open()
  • 不能訪問元素的屬性和子元素,由於在元素未升級的狀況下(元素還未插入文檔中),不存在屬性和子元素
  • 初始化工做要儘可能推遲到 connectedCallback 中,尤爲是涉及獲取資源或渲染的工做。可是 connectedCallback 可能會調用屢次(每次插入到文檔中時都會調用),一次性的初始化工做須要本身設置防禦措施。

命名限制

自定義元素的命名限制以下

  • 必須以小寫字母開頭
  • 必須有至少一箇中劃線
  • 容許小寫字母,中劃線,下劃線,點號,數字
  • 容許部分 Unicode 字符,因此 <h-?></h-?> 這樣也是能夠的.
  • 不容許下面這些名稱

    • annotation-xml
    • color-profile
    • font-face
    • font-face-src
    • font-face-uri
    • font-face-format
    • font-face-name
    • missing-glyph

若是名稱出現不容許的字符, customElements.define 會報錯。

生命週期函數

自定義元素能夠定義特殊生命週期鉤子,以便在其存續的特定時間內運行代碼。

  • constructor 建立或升級元素的一個實例。用於初始化狀態、設置事件偵聽器。
  • connectedCallback 元素每次插入到 DOM 時都會調用。可用於獲取資源或渲染。
  • disconnectedCallback 元素每次從 DOM 中移除時都會調用。用於運行清理代碼。
  • attributeChangedCallback 屬性添加、移除、更新或替換。僅對 observedAttributes 中返回的屬性有效。
  • adoptedCallback 自定義元素被移入新的 document 時調用。

響應回調是同步的。若是有人對您的元素調用 el.setAttribute(...),瀏覽器將當即調用 attributeChangedCallback()。 同理,從 DOM 中移除元素(例如用戶調用 el.remove())後,您會當即收到 disconnectedCallback()。

相關文章
相關標籤/搜索