[JavaScript] Web Componentsで明暗モード切替ボタンを作成する
概要
この記事について
かんたんな概要と結論
Componentのカプセル化を用いることで、外部に影響されないスタイル、動作を設計することができる。
JavaScriptの標準APIであるWeb Componentsを学習している際に、
一度自分でもわかりやすいようにデモを作成してみようと思った。
今回は、画面あるいは指定ボックス内の明るいモード、暗いモードを切り替えるトグルボタンを、
Web Componentsを用いて作成した。
Web Componentsとは?
ABOUT
Web Components は、再利用可能なカスタム要素を作成し、ウェブアプリの中で利用するための、一連のテクノロジーです。コードの他の部分から独立した、カプセル化された機能を使って実現します。 https://developer.mozilla.org/ja/docs/Web/Web_Components
Reactのような外部ライブラリを用いなくても、
標準APIのみで、DOMの一部とデザイン、イベントなどを一つのコンポーネントにまとめて使い回すことができる技術。
サンプル
下記「サンプルを作成する」で作成したサンプル(説明は後述)。
'🌞'のマークのトグルボタンをクリックすると、画面やボックスの色を切り替える。
PROS
- コンポーネントの使い回しを可能とする
DRYなコーディングを助ける。 - styleをカプセル化することができる
これは、あるセレクタに該当する要素に対する外部で指定されたstyleが、
コンポーネント内(正確にはコンポーネント内のShadow DOM)に影響を与えず、
また、コンポーネント内で記述されたstyleが外部の要素に影響を与えることはないということを意味している。 - 動作のカプセル化をすることができる
styleと同様に、clickイベントなども独自の挙動を定義することが可能。
CONS
- コンポーネントの外部と内部とのデータのやり取りが苦手
Shadow DOMを用いてカプセル化する場合は特に制限が強く、
Reactのように自由気ままにイベント移譲をしたりstate管理したりすることは困難。 - サポートしているブラウザに制限がある
今後の開発に期待。
サンプルを作成する(明暗モード切替ボタン)
ABOUT
画面、あるいは指定ボックス内の明るいモード、暗いモードを切り替えるトグルボタンを作成したい。
要件
- スタイルやクリック時のイベントを内部で設定したい
- スタイルの一部(ボタンの大きさ)は外部で定義した値を使えるようにする
- 切り替え対象の要素も、外部で定義した値を使えるようにする
コード
toggle-color-button-component.ts
1const style = `
2:host {
3 --toggle-color-button-criteria-len:var(--toggle-color-button-len,75px);
4 width: var(--toggle-color-button-criteria-len);
5 height: calc(var(--toggle-color-button-criteria-len) / 1.78);
6}
7div{
8 position: relative;
9 width: var(--toggle-color-button-criteria-len);
10 height: calc(var(--toggle-color-button-criteria-len) / 1.78);
11 margin: auto;
12 display: inline-block;
13}
14input {
15 position: absolute;
16 left: 0;
17 top: 0;
18 width: 100%;
19 height: 100%;
20 margin:0;
21 z-index: 5;
22 opacity: 0;
23 cursor: pointer;
24}
25label {
26 width: 100%;
27 height: 100%;
28 background: #ffeb3b;
29 position: relative;
30 display: inline-block;
31 border-radius: calc(var(--toggle-color-button-criteria-len)/1.63);
32 transition: 0.4s;
33 box-sizing: border-box;
34}
35label:after {
36 content: '🌞';
37 position: absolute;
38 left: 0;
39 top: 0;
40 z-index: 2;
41 transition: 0.4s;
42 font-size: calc(var(--toggle-color-button-criteria-len)/2.5);
43 line-height: calc(var(--toggle-color-button-criteria-len)/1.70);
44}
45input:checked + label {
46 background-color: #3d00a9;
47}
48input:checked + label:after {
49 content: '🌙';
50 left: calc(var(--toggle-color-button-criteria-len) / 2.14);
51}
52`;
53
54const template = `
55<div>
56 <input id="toggle" type='checkbox' />
57 <label for="toggle" />
58</div>
59`;
60
61const tmpl = document.createElement("template");
62tmpl.innerHTML = `<style>${style}</style>${template}`;
63
64customElements.define("toggle-color-button", class extends HTMLElement {
65 connectedCallback() {
66 // shadowDOMの設定
67 const shadowRoot = this.attachShadow({ mode: "closed" });
68 shadowRoot.append(tmpl.content.cloneNode(true));
69 // トグルする対象の要素
70 const toggledElem = document.querySelector<HTMLElement>(this.dataset.toggled || "html");
71 if (!toggledElem) return;
72 // ボタンクリック時にトグル
73 shadowRoot.querySelector("input")?.addEventListener("click", () => {
74 toggledElem.dataset.mode =
75 toggledElem.dataset.mode !== 'dark' ?
76 'dark'
77 : 'light';
78 })
79 }
80});
template
要素にコンポーネントのDOM構造を記述する。
内部で使われるstyleも文字列として記述。
なお、ここではstyleを文字列としてベタ書きしたけれども、
Sass用のライブラリやCSS-in-JSのライブラリを用いてうまいことトランスパイルしてあげれば
保守性を高めることができるだろう(Web Componetns内部に記述するstyleなどそこまで多くはないので、だいたいは通常のCSSで事足りるだろうけど)
customElements.defineメソッド実行によって
スクリプトを読み込んだHTML側で<toggle-color-button></toggle-color-button>
の形でカスタムタグを使用することができるようになる。
index.html
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta http-equiv="X-UA-Compatible" content="IE=edge">
7 <meta name="viewport" content="width=device-width, initial-scale=1.0">
8 <title>Document</title>
9 <script src="./dist/toggle-color-button-component.js"></script>
10 <style type="text/css">
11 #box-one {
12 --toggle-color-button-len: 50px;
13 margin: 60px auto;
14 max-width: 400px;
15 border: solid 2px;
16 }
17
18 #box-one[data-mode="dark"] {
19 background-color: darkblue;
20 color: lightblue;
21 }
22
23 #box-two {
24 --toggle-color-button-len: 40px;
25 margin: 60px auto;
26 max-width: 600px;
27 border: solid 2px;
28 text-align: center;
29 }
30
31 #box-two input {
32 display: none;
33 }
34
35 #box-two label:before {
36 font-family: FontAwesome;
37 display: inline-block;
38 content: "□";
39 color: blue;
40 letter-spacing: 10px;
41
42 }
43
44 #box-two input:checked+label:before {
45 content: "✔";
46 }
47
48 html[data-mode="dark"] * {
49 background-color: darkslategrey;
50 color: rgb(255, 230, 0);
51
52 }
53
54 label,
55 input {
56 cursor: pointer;
57 }
58 </style>
59</head>
60
61<body>
62 <div id="box-one">
63 <p>ボックス内のダークモード切り替えボタン:<toggle-color-button data-toggled="#box-one"></toggle-color-button>
64 </p>
65
66 </div>
67 <div id="box-two">
68 <div>
69 画面全体のダークモード切り替えボタン:<toggle-color-button></toggle-color-button>
70 </div>
71 <p><input id="switchA" type="checkbox" /><label for="switchA">スイッチA</label></p>
72 <p><input id="switchB" type="checkbox" /><label for="switchB">スイッチB</label></p>
73 <p><input id="switchC" type="checkbox" /><label for="switchC">スイッチC</label></p>
74 <p><input id="switchD" type="checkbox" /><label for="switchD">スイッチD</label></p>
75 <hr>
76 <div>
77 <p>スイッチ稼働状態</p>
78 <p>スイッチA:<span id="result-switchA">OFF</span></p>
79 <p>スイッチB:<span id="result-switchB">OFF</span></p>
80 <p>スイッチC:<span id="result-switchC">OFF</span></p>
81 <p>スイッチD:<span id="result-switchD">OFF</span></p>
82 </div>
83
84 </div>
85 <script>
86 for (const input of document.querySelectorAll("#box-two input")) {
87 input.addEventListener("click", (event) => {
88 const state = event.currentTarget.checked;
89 document.querySelector("#result-" + event.target.id).textContent = state ? "ON" : "OFF";
90 })
91 }
92
93 </script>
94</body>
95
96</html>
切り替え対象の要素の指定
box-one
要素では、
data-toggled
属性として#box-one
を指定することで、
明暗モードを切り替える対象をbox-one
内に限定している。
これを指定しない場合、画面全体(ルート要素=html要素)が切り替えられることになる
styleの独立
box-two
要素内で、
チェックボックスやlabelなどに色や大きさなどのstyleを指定しているが、
box-two
内部にあるはずのWeb Componentsには影響しない。
また、Web Componentsでグローバルに宣言したチェックボックスなどのstyleも、
box-two
内のほかのチェックボックスに影響しない。
styleの一部指定
ボタンの大きさは、
--toggle-color-button-len
というCSS変数で指定可能になっている。
上記の例では、box-one
とbox-two
で別々の大きさのトグルボタンを実装している。
※Shadow Domのスタイルの値を指定するためには、
このようにCSS変数の形で外部から注入する方法が許されている。
動作の独立
box-two
内のチェックボックスは、
それぞれ対応する稼働状態表示欄にON/OFFを報告するイベントを設定している。
もしShadow DOMによるカプセル化が行われていなければ、
Web Components内部のチェックボックスもまた同様のイベントが設定され、対応する表示欄がないためにエラーでスクリプトが落ちる。
しかし、Shadow DOMのおかげで、ここではコンポーネントはそのイベントが設定されず、エラーが起こらない。
デモ
関連記事
- [Javascript] IE11でRadioボタングループのvalueを取得できない事象&対策
- ご意見送信フォームを作成する③ [SPA by React Router]
- ご意見送信フォームを作成する② [React]
- ご意見送信フォームを作成する① [プレーンTypeScript]