【ベータ版改良】Charm.jsのspanタグ解除(7/4追記)

予定・ご報告・お返事など

この記事の前回のお話はこちらです。

ベータ版のCharm.js v3.3.0とプラグインをお試しいただいた方からメッセージをいただきました!
ご協力ありがとうございます。

前回記事の内容で、Windowsのナレーターで読み上げテストをしてくださったとのことで、プラグイン修正とテストページを作成しました!

「協力してもいいよ」という方がいらっしゃいましたら使用感をお寄せいただけますと嬉しいです。

修正したプラグイン(まだベータ版です)

前回のコードは素直にspanタグを外しただけの処理でしたので、今回は処理を変更しました。

新しいプラグインコード ※記事最後に追記あり

/**
 * 指定されたクラス名を持つspanタグを削除し、その子要素のTextNodeを親要素に移動する
 * - 再登録を反映できるようにするため、登録のための入力欄があるページでは動作しません
 * - spanタグが無いので、リロードなしでは復元系機能は基本的に動かなくなります
 */
class ElementRemover {
  // 削除対象のクラス名共通文字
  static targetClass = 'charmname';
   
  /**
   * プラグインの実行メソッド
   * @returns {ElementRemover} インスタンス
   */
  static run = () => {
    let instance = new ElementRemover();
    instance.start();
    return instance;
  }
  /**
   * コンストラクタ
   */
  constructor() {
    this.nameClass = Charm.nameClass;
  }
  /**
   * 入力タグが存在するかどうかをチェック
   * @returns {boolean} 入力タグが存在する場合はtrue、それ以外はfalse
   */
  hasSyncTags = () => {
    return document.getElementsByClassName(this.nameClass).length > 0;
  }
     
  /**
   * spanタグを削除し、親要素のテキストノードを一つに結合する処理を開始
   */
  start = () => {
    // 再登録をできるようにするため、入力タグが存在する場合は処理をしない
    if (this.hasSyncTags()) return;
    // 削除対象のspanタグを取得
    const elms = this.getSpanElms();
    // 親要素をセットにして一意に保持
    const allParents = new Set(elms.map(elm => elm.parentElement));
    // 各spanタグを処理
    elms.forEach(elm => {
      this.removeSpan(elm);
    });
    // 各親要素のテキストノードを結合
    allParents.forEach(parent => {
      this.combineTextNodes(parent);
    });
  }
   
  /**
   * spanタグを削除し、その子要素を親要素に移動
   * @param {HTMLElement} elm - 削除対象のspanタグ
   */
  removeSpan = (elm) => {
    const parent = elm.parentElement;
    // spanタグ内のテキストを取得
    const text = elm.textContent;
    // 親要素にテキストノードを追加
    parent.insertBefore(document.createTextNode(text), elm);
    // spanタグを削除
    parent.removeChild(elm);
  }
   
  /**
   * 親要素のすべてのテキストノードを一つに結合
   * @param {HTMLElement} parent - 親要素
   */
  combineTextNodes = (parent) => {
    let combinedText = '';
    const nodes = Array.from(parent.childNodes);
    const fragment = document.createDocumentFragment();
    nodes.forEach(node => {
      if (node.nodeType === Node.TEXT_NODE) {
        // テキストノードを結合
        combinedText += node.nodeValue;
      } else {
        if (combinedText) {
          // 結合されたテキストをフラグメントに追加
          fragment.appendChild(document.createTextNode(combinedText));
          combinedText = '';
        }
        // その他のノードをフラグメントに追加
        fragment.appendChild(node);
      }
    });
    // 残った結合テキストをフラグメントに追加
    if (combinedText) {
      fragment.appendChild(document.createTextNode(combinedText));
    }
    // 親要素の内容を新しいフラグメントで置き換え
    while (parent.firstChild) {
      parent.removeChild(parent.firstChild);
    }
    parent.appendChild(fragment);
  }
   
  /**
   * 対象のspanタグを取得
   * @returns {Array} 対象のspanタグの配列
   */
  getSpanElms = () => {
    const spans = document.getElementsByTagName('span');
    // 動的に正規表現を作成(部分一致)
    const regex = new RegExp(ElementRemover.targetClass, 'i');
    return Array.from(spans).filter(span => regex.test(span.className));
  }
}
// プラグインを追加
Charm.addPlugin("ElementRemover", ElementRemover);

前回のプラグインの主要処理

spanタグの中身のテキストノード()を親要素(pタグとか)に移動する。
→spanタグは取れているものの、文字としては親要素の中で分割されている

※テキストノードとは
テキストノードは、HTML文書内の文字列データを表すオブジェクトです。
ブラウザはこれを利用してテキストを表示します。

今回のプラグインの主要処理

spanタグの中身のテキストノードを親要素に移動し、親要素の中の文字をまとめる。

そのほかの修正点:
リアルタイム入力に限らず、入力フォームがあるページではこのプラグインを動作させないように判定を追加
→再登録したときに対象のspanタグがないと反映されなくなってしまうので(登録自体はできています)

前回試作したプラグインと比較すると、親要素に対する操作が追加された分、前回の物よりは少し処理に時間がかかります。
変換箇所が多いページをスペックの低い端末でアクセスしない限り、それほど気にするほどのものではないと思いますが、念のため……!

ベータ版テスト

ナレーター起動方法:【Windows】キーと【Ctrl】キーを押しながら【Enter】キー
テスト環境:Windows11 GoogleChrome
テキストの詳細:詳細レベル5
ナレーターの音声:Microsoft Nanami(Natural) – Japanese
テストページ:
【1】現在の最新リリースCharm.jsを使用+名前変換
【2】ベータ版Charm.js + spanタグ外しプラグイン
【3】ベータ版Charm.js + spanタグ外しプラグイン改良版
テストページではテキストのspanタグの背景に色が付くようにしてあります。

テストしてみて

ブラウザの開発者ツールで確認すると3ページとも構成が違うのですが、私の環境では設定が足りないのか、プラグインなしのv3.2.2のプラグイン無しでも変換箇所の読み上げは違いがないような……?

▼開発者ツールでチェック

【1】プラグイン無し
【2】spanタグを外すだけのプラグインあり
【3】修正したプラグインあり

私の環境でテキストの区切りがもともと再現できなかったので、今回の修正でナレーターの区切り現象が解消できているかちょっと確認ができていません……!
ご協力いただける方で「まだ区切り現象が続くよ」という方はご一報いただけますと幸いです!

Charm.js v3.3.0 リリースに向けて

プラグイン読み込み機能に加えて、今まで厳密に処理していなかったところの補強的な修正を試みています。
本体スクリプトがデフォルト設定の場合はほぼほぼ問題なく動く(はず)ですが、本体スクリプトで設定を変えているケースの動作確認などを進めています。

Charm.js v3.3.0 ベータ版のカスタムコード動作確認

スクリプトの設定を変更しても動けば正式に配布できるのですが、Lanama本館での公開は何かと準備が必要になるため、時間がとれなそうなときはLaboで先行公開みたいな形になるかもしれません。

今の段階で試してみたい!という方はこちらからダウンロードしてください。

Charm.js v3.3.0 ベータ版 zip 配布ページ

zipファイルの内容
【1】charm.js – v3.3.0 ベータ版のCharm.js本体の軽量版
【2】charm_and_plugin.js – Charm.js本体の後にプラグインを追記したもの
【3】charm_plugins.js – プラグインだけ

1はいつも本館で配布しているタイプの、軽量版のCharm.js本体です。
2は全てのページにプラグインを反映させたい人向けのセットです。
3はプラグインを分けて管理したい人向けの、プラグインのみのファイルです。

2は小説ページの読み込みファイル数を増やしたくない場合に使います。charm.jsと名前を変更しても問題ないですが、更新のときにプラグインを追記するのを忘れないようにお願いします。

3の場合、こんな感じで運用するので読み込みコードは増えますが、Charm.jsのアップデート時に本体スクリプトを差し替えるだけなのでアップデートが楽です。

<script src="charm.js"></script>
<script src="charm_plugins.js"></script>

とはいえ、ベータ版のテスト結果次第ではこの運用方法もまだ変わるかもしれないです。
変わっても怒らないでください…!

今回の対応につきまして

Charm.jsの主な使い方が名前変換テキストという点を考えると、読み上げ機能が自然に読んでくれるようにする、というのはとても重要と考えたので対応させていただきました。
しかしこの点で私が素人でしたので、メッセージをくださった方にはご丁寧に共有いただけて本当に感謝しています。

今回の対応で直っていない、または別の不具合がある、その他別の感想(カスタム変換でこんなことしてみたよ、など)でも嬉しいので、匿名でも良いので多くの方に今後ともお気軽にメッセージをいただけたらと思います!

Charm.js v3.3.0 と プラグインの正式公開までもうしばらくお待ちくださいませ。
(ベータ版でもいいよ、という方は今の時点の物を使ってもOKです)

追記

プラグインのパフォーマンスを少しだけ改善しました。
サンプルページ

こちらで【4】として置いておきます。
Charm.js v3.3.0 ベータ版 zip 配布ページ

6/18 追記

Charm.jsのv3.3.0とプラグインのベータ版をお試しいただいた方からご連絡をいただきました。
検証、ご共有いただきありがとうございます。
修正したプラグインで区切り現象が解消したとのことで、安心いたしました。

Charm.js本体の細かな修正や動作確認、ページの準備がおわり次第、正式に公開いたします!
本業がただいま繁忙期なのでお待たせするかもしれませんが、気長にお待ちいただけますと幸いです。

7/4 追記

Charm.js v3.3.0を公開しました。
スクリプト本体も、spanタグ削除として配布を開始したコードも、ベータ版とは多少処理が異なっています。
また、今回の機能追加の呼び方を「プラグイン」から「拡張機能」に変更しました。

拡張機能のコード入手はこちらからどうぞ!
拡張コードの入手はこちら(本館サイト)

説明はこちらの記事をご覧ください。

タイトルとURLをコピーしました