HTMLのカスタム要素を使ってみた
文字数と読了時間を表示する要素を作ってみた。
成果物
情報源
カスタム要素
カスタム要素で自作の要素を作れる。JSで機能を実装する。
要素を追加するAPIは以下の2種類ある。ここでは新しいv1を使う。
v0
:document.registerElement()
v1
:window.customElements()
コード抜粋
<hello-element>
要素を作る。
HelloElement.js
export default class HelloElement extends HTMLElement { constructor() { super(); this._some_attr = null; } static get observedAttributes() { return ['some_attr']; } attributeChangedCallback(name, oldValue, newValue) { this._updateRendering(); } get some_attr() { return this._some_attr; } set some_attr(v) { this.setAttribute("some_attr", v); } connectedCallback() { this._updateRendering(); } _updateRendering() { this.innerHTML = `some_attr is ${this._some_attr}.`; } }
main.js
import HelloElement from './HelloElement.js'; window.addEventListener('load', (event) => { customElements.define("hello-element", HelloElement); }); customElements.define("hello-element", HelloElement);
index.html
<script src="./main.js" type="module"></script> <hello-element some_attr="some-value"></hello-element>
要素名
要素名は以下に従うこと。PotentialCustomElementName
[a-z] (PCENChar)* '-' (PCENChar)*
PCENChar ::= "-" | "." | [0-9] | "_" | [a-z] | #xB7 | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x203F-#x2040] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
- 最初の字が小文字の英字であること
- 以降が小文字の英字や数字や記号
-
,.
,_
などであること - 以降のうち
-
が一つ以上含まれていること
なので最小名は以下のようになるだろう。
単語-単語
事実上、以下のようになるだろう。
- 要素は名詞になるはず
- 区切文字は
-
で統一する - 字種は英小文字
文字数カウンタ要素
日本語の記事において規模をみつもるとき文字数を知りたくなる。英語なら単語数になったりするので特定の言語圏でのみ使うことになる。そんなものが標準仕様になるわけもないため自作するしかない。そこでカスタム要素で実装する。
DEMOやsource-codeを見てもらったほうが早い。
CharacterCounter.js
export default class CharacterCounter extends HTMLElement { constructor() { super(); this._selector = "body" this._unit = "字"; this._withWhiteSpace = false; this._count = this.count(this._selector); } count() { let target = document.querySelector(this._selector); let str = target.textContent if (!this._withWhiteSpace) { str = str.replace(/\s+/g, ""); console.log(`${str.length} this._withWhiteSpace=${this._withWhiteSpace}`); } return str.length; } static get observedAttributes() { return ['selector', 'unit', 'space']; } attributeChangedCallback(name, oldValue, newValue) { let isRecount = false; if ("selector" == name) { this._selector = newValue; isRecount = true; } if ("unit" == name) { this._unit = newValue; } if ("space" == name) { this._withWhiteSpace = true; const falseValues = ['0', 'off', 'false']; if (falseValues.includes(newValue)) { this._withWhiteSpace = false; } isRecount = true; } if (isRecount) { this._count = this.count(this._selector); } this._updateRendering(); } get unit() { return this._unit; } set unit(v) { this.setAttribute("unit", v); } connectedCallback() { this._updateRendering(); } _updateRendering() { this.innerHTML = `${this._count}<span class="character-counter-unit">${this._unit}</span>` } }
observedAttributes
とattributeChangedCallback
がスマートじゃないなぁ。各属性固有と共通のコードを分離して書きたい。でもそのたびにクラス化、モジュール化していたら通信時間がかかるようになってしまうなどパフォーマンス上の問題が大きくなるのかもしれない。ES Moduleを使っている時点ですでにパフォーマンスが犠牲になっているのだろうが、なるだけ増やしたくない。オフラインでも使えるようCache APIやServiceWorkerを使うべきか。
main.js
import CharacterCounter from './CharacterCounter.js'; window.addEventListener('load', (event) => { customElements.define("character-counter", CharacterCounter); });
index.html
<meta charset="utf-8"> <script src="./main.js" type="module"></script> <character-counter></character-counter>
空白文字を含めてカウントしたいときはspace
属性値を付与する。
<character-counter space></character-counter>
カウントしたい要素をbody
以外にしたいときはselector
にCSSセレクタ文字列を渡す。
<character-counter selector="article.character-counter"></character-counter>
字数の単位を字
以外にしたいときはunit
属性値をセットする。
<character-counter unit="もじ"></character-counter>
なお、読了時間はこれとほぼ同じ内容で実装してある。読了時間は400字/1分として算出する。最低値は1分。
所感
カスタム要素さえあれば足りない機能をいくらでも実装できる。ワクワクするね!
たぶんカスタム要素で色々作ったライブラリがelement-plusなんだと思う。
でも自分で作りたい。自由度が格段にあがるので楽しい。
文字数カウンタの他にも、外部ファイルからHTMLに変換するなど色々思いつく。
対象環境
- Raspbierry pi 4 Model B
- Raspberry Pi OS buster 10.0 2020-08-20 ※
- bash 5.0.3(1)-release
$ uname -a Linux raspberrypi 5.10.52-v7l+ #1441 SMP Tue Aug 3 18:11:56 BST 2021 armv7l GNU/Linux