やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

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]
  • 最初の字が小文字の英字であること
  • 以降が小文字の英字や数字や記号-,.,_などであること
  • 以降のうち-が一つ以上含まれていること

 なので最小名は以下のようになるだろう。

単語-単語

 事実上、以下のようになるだろう。 

  • 要素は名詞になるはず
  • 区切文字は-で統一する
  • 字種は英小文字

文字数カウンタ要素

 日本語の記事において規模をみつもるとき文字数を知りたくなる。英語なら単語数になったりするので特定の言語圏でのみ使うことになる。そんなものが標準仕様になるわけもないため自作するしかない。そこでカスタム要素で実装する。

 DEMOsource-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>`
    }
}

 observedAttributesattributeChangedCallbackがスマートじゃないなぁ。各属性固有と共通のコードを分離して書きたい。でもそのたびにクラス化、モジュール化していたら通信時間がかかるようになってしまうなどパフォーマンス上の問題が大きくなるのかもしれない。ES Moduleを使っている時点ですでにパフォーマンスが犠牲になっているのだろうが、なるだけ増やしたくない。オフラインでも使えるようCache APIServiceWorkerを使うべきか。

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以外にしたいときはselectorCSSセレクタ文字列を渡す。

<character-counter selector="article.character-counter"></character-counter>

 字数の単位を以外にしたいときはunit属性値をセットする。

<character-counter unit="もじ"></character-counter>

 なお、読了時間はこれとほぼ同じ内容で実装してある。読了時間は400字/1分として算出する。最低値は1分。

所感

 カスタム要素さえあれば足りない機能をいくらでも実装できる。ワクワクするね!

 たぶんカスタム要素で色々作ったライブラリがelement-plusなんだと思う。

 でも自分で作りたい。自由度が格段にあがるので楽しい。

 文字数カウンタの他にも、外部ファイルからHTMLに変換するなど色々思いつく。

対象環境

$ uname -a
Linux raspberrypi 5.10.52-v7l+ #1441 SMP Tue Aug 3 18:11:56 BST 2021 armv7l GNU/Linux