【ご要望】Charm.jsのspanタグ解除

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

Charm.jsのご利用者さまから匿名でメッセージをいただきました。
丁寧なご感想とご連絡、ありがとうございます。
Lanamaのスクリプトをご利用いただけて本当に嬉しいです!

メッセージにありましたご要望について調査と方向性を考えました。

■ご要望
変換の有無にかかわらず、名前変換spanタグを外してプレーンテキスト化するモードがほしい

■ご要望の背景
spanタグがついていると、テキスト読み上げツールでspanタグ部分が区切られてしまう

【結論】Charm.jsでの対応内容としては、プラグイン対応を検討しています

調査に使ったデモページ

どの読み上げツールなのか詳細がわからなかったため、Charm.jsのver3.2.2を使ったページ用意して、いくつかのツールで読み上げてみました。こちらが検証したコンテンツ部分です。

<h1>白鳥の<span class="charmname1">ナマエ</span></h1>
<main class="story">
  <p>湖にいる白鳥の妖精は「<span class="charmname1">ナマエ</span>」と呼ばれて親しまれています。</p>
  <p><span class="charmname1">ナマエ</span>の歌声は人々の心を癒し、国王さえも存在を知っているほどです。<br>朝の光の中で翼を広げる様は息を呑むほど美しく、国中で<span class="charmname1">ナマエ</span>を知らない者はいません。</p>
  <p><span class="charmname1">ナマエ</span>を一目見ようと、今日も湖には多くの人が訪れています。</p>
</main>

Charm.js v3.2.2 テストページ

デモページURLのQRコード
テストページURLのQRコード

聴いた読み上げツール

  • Read Aloud: テキスト読み上げ音声リーダー(Google Chrome拡張)→問題なし?
  • 音声さん(Google Chrome拡張)→問題なし?
  • Edgeブラウザの読み上げ機能→問題なし?
  • Voicepaper(iOSアプリ)→問題なし?
  • ことせかい(iOSアプリ)→変換前のテキストのみ対象、区切り問題はなし?
  • Audify(iOS, Android)→問題なし?
  • 小説を聞こう(Android)→変換前のテキストのみ対象、区切り問題はなし?
  • 読み上げアプリ(Android)→変換前のテキストかつ名前読み込み順番崩れ

これらを一通り試聴してみましたが、私の環境ではspanタグの影響は感じられず…!
もし差し支えなければspanタグの影響のある読み上げツールをご一報いただけますと幸いです。

対応方針として

ご要望を踏まえると、これまでのCharm.jsの機能追加ようにフラグひとつでは難しいかなと考えています。
コード量も少し多めになるので、Charm.jsにプラグインを読み込む機能をつけて、必要に応じてCharm.jsの本体ファイルにプラグインを追記するという形にしたいと思いますが、いかがでしょうか。

課題1. 登録フォームのないページでの解除対象spanさがし

ご要望対応の課題は2つありまして、まず1つ目ですが、Charm.jsは「名前変換の対象spanタグを全て取得できるとは限らない仕組み」になっています。

Charm.jsの名前変換の仕組みは、名前登録時のinputタグのidと同じspanタグを探す、という作りになっています。ご利用者さんが自由に入力フォームのidを増やせるように作っている都合上、登録フォームのないページでは、「登録した名前のindex」だけを取得して置換する仕組みになっています。

つまり、登録していない項目があると、名前変換用のspanタグであっても処理対象として認識しません。

idの決め方に明確なルールがないので、もしspanタグを解除する機能を作るときは、解除対処のspanタグのクラスの共通部分をスクリプトに書く必要があります。
(例えば、charmnameという文字が含まれたclassをもつspanタグ、など)

課題2. spanタグすべてを一括解除する場合

処理をしたタグの親要素から見て、すべてのspanタグを一括解除する場合、名前変換以外のタグも解除されます。文字を強調したり、レイアウト調整に使用しているspanタグも解除されてしまうので、解除対象は絞り込んでおきたいと考えています。

プラグインの追加で出来ることと出来ないかもしれない点

■解決できないこと
Charm.jsがブラウザ上で動くスクリプトで出来ている仕様上、↑でお調べした読み上げツールの一部にあった「変換前のテキストしか読み上げ対象にならない」≒「Charm.jsの処理が動く前のテキストしか読み込んでもらえない」ことは変えられないかと思います。

ご要望の文面からはこの点が主旨ではないと私は思っていますが……もしこれが原因でしたらお力になれず申し訳ありません。

■解決できること
Charm.jsの名前変換の処理が完了した後に、動的に一部のspanタグを外す、ということは可能です。
削除対象のspanタグに共通するclass名をプラグインの中で設定してもらい、そのclass名を持つspanタグを一括解除する、ということは実現できます。

まだ私の環境での動作確認の途中ですが、Charm.js v3.3.0にプラグイン読み取り機能を追加し、プラグインを実際に追記して動かしているデモページを作りました。

Charm.js v3.3.0 ベータ版動作確認

Charm.js v3.3.0 ベータ版テストURLのQRコード

プラグインの追加方法は2通りで、プラグインのコードを書いたファイルをcharm.jsの後に読み込むか、charm.jsのスクリプト本体の最後にプラグインを追記するか、で実装できます(予定)。

プラグインコードはこのようなものを予定しています。

/**
 * 指定されたクラス名を持つspanタグを削除し、その子要素のTextNodeを親要素に移動する
 * - リアルタイム保存の入力欄があるページでは動作しません
 * - spanタグが無いので、リロードなしでは復元系機能は基本的に動かなくなります
 */
class SpanRemover {
  // 削除対象のクラス名
  static targetClass = 'charmname';
 
  /**
   * プラグインの実行メソッド
   * @returns {SpanRemover} インスタンス
   */
  static run = () => {
    let instance = new SpanRemover();
    instance.start();
    return instance;
  }
  /**
   * コンストラクタ
   */
  constructor() {
    this.now = Charm.syncNow;
    this.session = Charm.syncNowSession;
  }
  /**
   * リアルタイム保存用のタグが存在するかどうかをチェックする
   * @returns {boolean} リアルタイム保存用タグが存在する場合はtrue、それ以外はfalse
   */
  hasSyncTags = () => {
    return document.getElementsByClassName(this.now).length > 0 || document.getElementsByClassName(this.session).length > 0;
  }
  /**
   * spanタグを削除し、子要素のTextNodeを親要素に移動する
   */
  start = () => {
    // リアルタイム保存用タグが存在する場合は処理をスキップ
    if (this.hasSyncTags()) return;
    // 削除対象のspanタグを取得
    const elms = this.getSpanElms(this.targetClass);
    elms.forEach(elm => {
      const parent = elm.parentElement;
      while (elm.firstChild) {
        const child = elm.firstChild;
        if (child.nodeType === Node.TEXT_NODE) {
          parent.insertBefore(document.createTextNode(child.nodeValue), elm);
        } else {
          parent.insertBefore(child, elm);
        }
        elm.removeChild(child);
      }
      // spanタグ削除
      parent.removeChild(elm);
    });
  }
  /**
   * 対象のspanタグを取得する
   * @returns {Array} 対象のspanタグの配列
   */
  getSpanElms = () => {
    const spans = document.getElementsByTagName('span');
    // 動的に正規表現を作成(部分一致)
    const regex = new RegExp(SpanRemover.targetClass, 'i');
    return Array.from(spans).filter(span => regex.test(span.className));
  }
}
// プラグインを追加
Charm.addPlugin("SpanRemover", SpanRemover);

いずれも開発・テスト中なので変更になるかもしれませんが、もしご意見があればいただけますと幸いです!

GitHubのarchiveで完全圧縮版のみとりあえず置いてありますが、このテストページのものも含めてベータ版なので変更点やバグはあるかもしれません。
未圧縮版は調整中ですが、圧縮版とLanamaで日頃から配布している軽量版をzipファイルにまとめました。
もし良ければこちらをダウンロードして、お手元の環境でテストしていただけたらと思います。

Charm.js v3.3.0 ベータ版 zip 配布ページ(6/16 改良版へしました)

せっかくのプラグイン読み取り機能追加なので、ちょっとしたデバッグモードのプラグインも用意しようかなと準備を進めています。

ご要望のメッセージを下さった方も、ほかの方もお気軽に使用感などのフィードバックをいただけると嬉しいです。(全てのご要望に対応できるというわけではありませんが…)いただいたご意見は今後の改善などに活かしたいと思っています!

今回のベータ版の使用感以外にも、使ってみた一言感想などでもとても励みになるのでお気軽にメッセージをいただければと思います。
メッセージは配布サイトのメールフォームでも、このラボのご連絡フォームでもどちらからでもOKです!

配布サイトのメールフォームはこちら

それでは、今後ともよろしくお願いいたします。

この記事の続きはこちら!

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