プログラミング

Web Components のサンプルコード

投稿日:2019年12月6日 更新日:

1. Web Components

Web Components というのは、以下の仕様をまとめた仕様のことです。

  1. カスタム要素(の仕様)
  2. Shadow DOM(の仕様)
  3. HTML テンプレート(の仕様)
  4. ES Module(の仕様)

これらの仕様により、次の特徴を持った HTMLの部品を作成することができます。

  • テンプレート内の CSS の効果をカスタムタグ内に限定することができる(カプセル化)
  • 再利用しやすい

本記事では、Web Components を使ったサンプルコードを紹介します。

2. Web Components を使うための雛形

その1:時間を表示する

HTML

<style>
my-clock {
  color: blue;
}
</style>

<my-clock></my-clock>
  • <my-clock> というカスタムタグに色を設定しています。これは、たとえば <h2> に対して色を設定するのと同じです。<h2> は標準で用意されているタグであり、<my-clock> は独自に作成したタグであるという違いでしかありません。Shadow DOM の中でもCSSを記述することはできますが、Shadow DOM の外側での設定が優先されて適用されます。

JavaScript

customElements.define('my-clock',
  class extends HTMLElement {
    constructor() {
      super();
      const shadowRoot = this.attachShadow({ mode: 'open' });
      shadowRoot.innerHTML = `
<style>
:host {
  display: block;
  contain: content;
  font-size: 2em;
  font-weight: bold;
  font-family: monospace;
  background-color: #aaffcc;
  text-align: center;
}
</style>
<p id="dateOnScreen"></p>
`;
      this._elmDateOnPage = this.shadowRoot.querySelector('#dateOnScreen');
    }

    connectedCallback() {
      setInterval(() => {
        this._elmDateOnPage.textContent = moment().format("YYYY-MM-DD HH:mm:ss");
      }, 1000);
    }
})
  • テンプレート・リテラルを使って HTML と CSS を記述し、それを ShadowRoot にセットしています。
  • カスタムタグ内の要素を取得するには、ShadowRoot の querySelector() メソッドを使います。
  • カスタムタグがDOMに挿入されたときに、connectedCallback() メソッドが実行されます。DOM 上にカスタムタグが存在していないとできない初期化処理などは、ここに記述します。
  • :host は、カスタムタグ自身を指しています。

class を export する場合の書き方

class 定義を export した JavaScript ファイルを、HTML側から import して使う場合の書き方です。

HTML

<style>
my-clock {
  color: blue;
}
</style>

<my-clock></my-clock>

<script type="module">
import MyClock from './my-clock.js'
customElements.define('my-clock', MyClock);
</script>

JavaScript

export default class extends HTMLElement {
    constructor() {
      super();
      const shadowRoot = this.attachShadow({ mode: 'open' });
      shadowRoot.innerHTML = `
<style>
:host {
  display: block;
  contain: content;
  font-size: 2em;
  font-weight: bold;
  font-family: monospace;
  background-color: #aaffcc;
  text-align: center;
}
</style>
<p id="dateOnScreen"></p>
`;
      this._elmDateOnPage = this.shadowRoot.querySelector('#dateOnScreen');
    }

    connectedCallback() {
      setInterval(() => {
        this._elmDateOnPage.textContent = moment().format("YYYY-MM-DD HH:mm:ss");
      }, 1000);
    }
}

その2:数値を増減する

HTML

<my-elm interval="1"></my-elm>

JavaScript

class MyElm extends HTMLElement {
  //static get observedAttributes() {
  //  return ['interval'];
  //}

  constructor() {
    super();
    // メソッド内で this を使う場合は必要
    this._onIncClick = this._onIncClick.bind(this);
    this._onDecClick = this._onDecClick.bind(this);
    this._onIntervalIncClick = this._onIntervalIncClick.bind(this);
    this._onIntervalDecClick = this._onIntervalDecClick.bind(this);

    const shadowRoot = this.attachShadow({mode: 'open'});
    shadowRoot.innerHTML = `
<style>
:host {
  display: block;
  contain: content;
}
p.title {
  font-size: 1.2em;
  font-weight: bold;
  margin-top: 0;
}
p[name=output] {
  color: blue;
  margin-left: 1.5em;
}
span[name=interval] {
  display: inline-block;
  margin-right: 1em;
}
button {
  cursor: pointer;
}
</style>
<p class="title">数字を増減させる</p>
<p name="output"></p>
<button name="inc">増</button> <button name="dec">減</button>
<hr>
Interval 属性値: <span name="interval"></span><button name="incInterval">増</button> <button name="decInterval">減</bu
tton>
`;

    this._count = 0;
    this._output = this.shadowRoot.querySelector('p[name=output]');
    this._btnInc = this.shadowRoot.querySelector('button[name=inc]');
    this._btnDec = this.shadowRoot.querySelector('button[name=dec]');
    this._intervalElm = this.shadowRoot.querySelector('span[name=interval]');
    this._btnIntervalInc = this.shadowRoot.querySelector('button[name=incInterval]');
    this._btnIntervalDec = this.shadowRoot.querySelector('button[name=decInterval]');

    this._btnInc.addEventListener('click', this._onIncClick);
    this._btnDec.addEventListener('click', this._onDecClick);
    this._btnIntervalInc.addEventListener('click', this._onIntervalIncClick);
    this._btnIntervalDec.addEventListener('click', this._onIntervalDecClick);
  }

  connectedCallback() {
    this._output.textContent = `0`;
    if (!Number.isInteger(this.interval)) {
      this.interval = 1;
    }
    this._intervalElm.textContent = this.interval;
  }

  disconnectedCallback() {
    if (this._btnInc) {
      this._btnInc.removeEventListener('click', this._onIncClick);
    }
    if (this._btnDec) {
      this._btnDec.removeEventListener('click', this._onDecClick);
    }
    if (this._btnIntervalInc) {
      this._btnIntervalInc.removeEventListener('click', this._onIntervalIncClick);
    }
    if (this._btnIntervalDec) {
      this._btnIntervalDec.removeEventListener('click', this._onIntervalDecClick);
    }
  }

  set interval(value) {
    this.setAttribute('interval', value);
    this._intervalElm.textContent = this.interval;
  }

  get interval() {
    return parseInt(this.getAttribute('interval'));
  }

  _onIncClick() {
    this._count += this.interval;
    this._output.textContent = `${this._count}`;
  }

  _onDecClick() {
    this._count -= this.interval;
    this._output.textContent = `${this._count}`;
  }

  _onIntervalIncClick() {
    this.interval += 1;
  }

  _onIntervalIncClick() {
    this.interval += 1;
  }

  _onIntervalDecClick() {
    this.interval -= 1;
  }

  //attributeChangedCallback(name, oldValue, newValue) {
  //  console.log('attributeChangedCallback()', name, oldValue, newValue);
  //  //this[name] = newValue;
  //}
}
window.customElements.define('my-elm', MyElm);

3. メモ

  • Web Components のサンプルコードをいろいろ探してみましたが、「<template> タグを使って Shadow DOM に HTML と CSS をセットする」サンプルはあまり見付かりませんでした。Web Components から HTML Import の仕様が削られました(?)が、<template> タグを使うことすら推奨されていない印象を受けます。ということで、本記事では素直に JavaScript の テンプレート・リテラル を使っています。

4. おわりに

他のサンプルも追加していく予定です。

5. 参考

📂-プログラミング

執筆者:labo


comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

関連記事

Web Programming

ソフトウェアにおける日付・時刻フォーマット

目次ソフトウェアにおける日付・時刻フォーマット参考情報ISO 8601RFC 5322RFC 7231Common Log Format ソフトウェアにおける日付・時刻フォーマット ソフトウェア(特に …

WordPress

WordPressのソースコード。HTMLにPHPを埋め込むスタイル。

WordPress本体のPHPソースコードや、テーマに含まれているPHPのソースコードを見ると、やたらと PHPの開始タグ(<?php)と終了タグ(?>)が埋め込まれています。 例えば、こ …

Milkcocoa のチュートリアルを試す

Milkcocoa のチュートリアルをやってみました。 目次Milkcocoa とは?チュートリアル1. アカウント登録する2. ログインする3. アプリを作成する4. スマートフォン側のWebページ …

no image

ウェブプログラミングの知識があるとできること(その1)

先日、あるブログを見ていたら最新の記事だけが表示されない仕組みになっていました。 ウェブプログラミングの知識があるとこんなことができますという例として、その仕組を調べた時の過程を紹介します。 目次きっ …

Webページ上に問題と解答を記載し、解答はボタンで表示する方法

ブログも含め Webページ上で、問題と解答を記載したいのだけれど、解答はすぐに見せたくない場合の方法です。用意したボタンをクリックすると、JavaScriptのプログラムが実行されて解答を表示させます …