banner
DIYgod

Hi, DIYgod

写代码是热爱,写到世界充满爱!
github
twitter
follow
bilibili
telegram
email
steam
playstation
nintendo switch

Polymer 初體驗

作為開發者,我們都知道元件化、標準化和程式碼重用的重要性,前端也從未停止過對前端元件化的嘗試,產生了各式各樣的元件化技術,從 Vue React 等前端框架,到 webpack 這樣的全站打包工具

但前端一直缺乏這樣一個模組化標準和瀏覽器級別的原生元件化方案

Web Components 是 WHATWG 和 W3C 正在嘗試的 Web 元件化方案,為元件化的前端開發提供瀏覽器級別的支持。它由四項主要技術組成:Shadow DOM、Custom Elements、HTML Import、HTML Template

Polymer 專案是 Google 的基於 Web Components 機制的框架,定位於簡單的 Polyfill 和易用性封裝,包括資料綁定,模板聲明,事件系統等。Google 在去年就已經將其應用到了 YouTube 上

Polymer 3.0 在 20 天前剛剛發布,正好 B 站播放器近期需要重構所有 UI 元件,所以做了這樣的一個調研,下文所有 demo 托管在 polymer-demos,這些小 demo 只作為一些簡單體驗,想了解 Polymer 的完整功能建議閱讀官方文檔

瀏覽器支持#

目前使用 Web Components 的最大阻礙就是瀏覽器支持程度低,且 Polyfills 體積相對偏大(90+kb)

目前只有新版 Chrome Opera 和 Safari 可以提供完整的原生支持,具體支持情況可以參考 caniuse.com,使用 Polyfills 後可以支持到 Edge IE11+ Firefox Safari9+

Polyfills 有三個主要的文件:

  • webcomponents-bundle.js: 包含了所有 polyfills
  • webcomponents-loader.js: 可以檢測瀏覽器支持情況,然後去加載對應的 polyfills,對有原生支持的瀏覽器可以減少不必要的浪費
  • custom-elements-es5-adapter.js: 註冊 Custom Elements 時需要使用 ES6 語法,所以當瀏覽器不支持 ES6 時需要做額外的處理,再引用這個文件就好了

總的來說兼容最多瀏覽器的最佳實踐是這樣的:

<scirpt src="webcompoments-loader.js"></scirpt>
<scirpt src="custom-elements-es5-adapter.js"></scirpt>
<script src="index.js"></script>

其中 webcompoments-loader.js 必須單獨引用,custom-elements-es5-adapter.js 可以跟 polymer 和你的程式碼用 Webpack 合到一起,但注意 custom-elements-es5-adapter.js 不要做額外的編譯,其他程式碼用 babel 編譯成 ES5,完整實踐可以參考 polymer-demos

Custom elements#

下面嘗試定義一個最簡單的自定義元素,從 PolymerElement 繼承一個類,然後傳給 window.customElements.define

效果

{% raw %}

{% endraw %}

HTML 代碼

<demo-custom-elements></demo-custom-elements>

JS 代碼

import { PolymerElement } from '@polymer/polymer';

class DemoCustomElements extends PolymerElement {
    constructor() {
        super();
        this.textContent = `我是自定義元素。`;
    }
}

window.customElements.define('demo-custom-elements', DemoCustomElements);

Shadow dom#

Shadow dom 是一個隱藏、獨立的 DOM,它的 HTML CSS 和行為與常規的 DOM 樹分離,這樣不同的功能不會混在一起,內外的 CSS 也互不影響

Shadow dom 不是一個新事物,一直以來,瀏覽器用它來封裝一個元素的內部結構。以 <video> 元素為例。你所能看到的只是一個 <video> 標籤,實際上,在它的 Shadow dom 中包含一系列的按鈕和控制器

下面例子中,Shadow dom 裡的 p 標籤定義了 CSS 屬性 color,它不會洩漏到外部

效果

{% raw %}

我在 demo-shadow-dom 外面。因為封裝,demo-shadow-dom 的樣式不會洩漏到我這裡。

{% endraw %}

HTML 代碼

<style>
    html {
        --my-background: #eee;
    }
</style>
<demo-shadow-dom></demo-shadow-dom>
<p>我在 demo-shadow-dom 外面。因為封裝,demo-shadow-dom 的樣式不會洩漏到我這裡。</p>

JS 代碼

import { PolymerElement, html } from '@polymer/polymer';

export class DemoShadowDom extends PolymerElement {
    static get template () {
        return html`
            <style>
                p {
                    color: #F5712C;
                    background-color: var(--my-background);
                }
            </style>
            <p>我是 DOM 元素。</p>
            <p>這是我的 shadow DOM!</p>
        `;
      }
}

window.customElements.define('demo-shadow-dom', DemoShadowDom);

HTML templates#

使用 <template><slot> 組成 shadow DOM

效果

{% raw %}

我是自定義插槽。



{% endraw %}

HTML 代碼

<demo-html-template>
    <p>我是自定義插槽。</p>
</demo-html-template>

JS 代碼

import { PolymerElement, html } from '@polymer/polymer';
import '@polymer/polymer/lib/elements/dom-repeat.js'
import { DemoShadowDom } from './demo-shadow-dom';

class DemoHTMLTemplate extends DemoShadowDom {
    constructor() {
        super();

        this.employees = [
            {
                name: 'Blog',
                link: 'https://diygod.me'
            },
            {
                name: 'GitHub',
                link: 'https://github.com/DIYgod'
            },
        ];
    }
    static get template () {
        return html`
            <strong>模板:</strong>
            <template is="dom-repeat" items="{{employees}}">
                <p><a href="{{item.link}}">{{item.name}}</a></p>
            </template>
            <strong>插槽:</strong>
            <slot></slot>
            <strong>超級模板:</strong>
            ${super.template}
        `;
      }
}

window.customElements.define('demo-html-template', DemoHTMLTemplate);

資料綁定#

支持雙向的資料綁定,你可以嘗試編輯下面的輸入框,或者直接在控制台修改屬性 document.querySelector('demo-data').owner1 = 'DIYgay',屬性改變會即時反映到 DOM 裡

效果

{% raw %}

{% endraw %}

HTML 代碼

<demo-data owner1="DIYgod1"></demo-data>

JS 代碼

import { PolymerElement, html } from '@polymer/polymer';
import '@polymer/iron-input';

class DemoData extends PolymerElement {
    constructor() {
        super();
        this.owner3 = 'DIYgod3';
    }

    static get properties () {
        return {
            owner1: {
                type: String,
                value: 'DIYgod',
            },
            owner2: {
                type: String,
                value: 'DIYgod2',
            }
        };
    }
    
    static get template () {
        return html`
            <p>這是 <b>[[owner1]]</b> 的元素。</p>
            <p>這是 <b>[[owner2]]</b> 的元素。</p>
            <p>這是 <b>{{owner3}}</b> 的元素。</p>
            <iron-input bind-value="{{owner1}}">
                <input is="iron-input" placeholder="在這裡輸入你的名字...">
            </iron-input>
        `;
    }
}

window.customElements.define('demo-data', DemoData);

自定義事件#

下面我們來給我們的自定義元素定義一個名為 diygod 的事件,綁定事件回調的方法跟正常事件一樣

效果

{% raw %}

{% endraw %}

HTML 代碼

<demo-events></demo-events>
<script>
    document.querySelector('demo-events').addEventListener('diygod', function (e) {
        alert(e.detail.msg);
    });
</script>

JS 代碼

import { PolymerElement, html } from '@polymer/polymer';

export class DemoEvents extends PolymerElement {
    static get template () {
        return html`
            <button on-click="handleClick">踢我</button>
        `;
    }

    handleClick(e) {
        this.dispatchEvent(new CustomEvent('diygod', {
            detail: {
                msg: 'diygod 事件觸發'
            }
        }));
    }
}

window.customElements.define('demo-events', DemoEvents);

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。