JavaScriptのテキストの文字あたりの寸法


TL;DR: Create a Range, set proper start and end points up to the text node with proper offset, then use Range.getBoundingClientRect() to get the dimensions.


一部としてLyricova Jukebox , 我々はインラインカラオケ強打アニメーションをサポートしたい.データ内の時間タグを使用すると、アニメーションが特定の文字に到達する必要がある時間を把握するのは簡単です.それから、アニメーションのために文字次元あたりの方法寸法を理解する必要があります.
最初は、私は途中でBalanceText DOMコンテンツとCSSプロパティをDOMノード自体を測定するよう変更するか.Balancetextは最近読んだJavaScriptライブラリです<br> ターゲットのノードのDOM HTMLソースを直接オーバーライドすることによって、バランスのとれた線長を達成するために、テキストにタグを付けます.
これは実際には、特に純粋なHTMLソース内の“ブレーク機会”に基づいて動作するbalancedtextのために、特に動作することができます.しかし、それはそれらのDOM要素に頼るいくつかのアプリケーションに問題をもたらします.Balancetextとは異なり、特にそのDOM構造を変更する必要はありません.Balancetextは古いDOMサブツリーを破棄し、innerHTML サブツリーのプロパティ.これは、イベントのリスナーや要素への他の参照はすべて測定後に使用できません.これは確かに私たちが望んでいないものです.

範囲API
インターネットを通してのいくつかの掘削の後、私はこれを見つけましたRange Webページの領域を選択し、その寸法を測定するWebのAPI.このAPIはselections by users Webページでは、選択を変更する必要はありません.
このようにして、DOMツリーやUIに変更を加えることなく、ウェブページ上のテキストの文字あたりの幅を測定することができます.
私は、文字ごとのテキストの累積幅を測定することによってこの機能を実証します.
一般的な考えは以下の通りです.
  • 出発点を見つけます.この例ではノードの先頭から測定したいので、Range. __proto__.setStartBefore(el) メソッド.
  • サブDOMツリー内のすべてのテキストノードを再帰的に検索します.
  • 見つかった各テキストノード
  • 使用してノードの各文字を終了する範囲を設定するRange. __proto__.setEnd(el, count) .
  • 範囲の幅をRange. __proto__.getBoundingClientRect() .
  • 以下は、typescriptのプロセスのサンプルコードです.
    /**
     * A generator that recursively find all text nodes in a subtree rooted at el.
     */
    function* recursivelyFindTextNode(el: Node): Generator<Node> {
      if (el.nodeType === Node.TEXT_NODE) {
        yield el;
      } else if (el.nodeType === Node.ELEMENT_NODE) {
        for (let i = 0; i < el.childNodes.length; i++) {
          yield * recursivelyFindTextNode(el.childNodes[i]);
        }
      }
    }
    
    const result: number[] = [];
    const range = document.createRange();
    range.setStartBefore(el);
    
    // expand all text nodes found.
    const nodes = [...recursivelyFindTextNode(el)];
    
    for (const textNode of nodes) {
    const textLength = [...textNode.textContent].length;
      for (let i = 0; i < textLength; i++) {
        range.setEnd(textNode, i);
        const rect = range.getBoundingClientRect();
        if (rect.width !== 0) result.push(rect.width);
      }
    }
    
    // Get the total length of the text
    range.setEndAfter(nodes.length > 0 ? nodes[nodes.length - 1] : el);
    const rect = range.getBoundingClientRect();
    result.push(rect.width);
    

    警告
    このAPIは画面上で直接レンダリングされたものを測定しているので、何かを測定したい場合は、最初にレンダリングする必要があります.
    また、テキストの長い文字列の長さをピクセル単位で測定したい場合は、テキストを1行でレンダリングする必要があります.これはいくつかのCSSによって実現可能です.
    white-space: pre;
    display: inline;
    width: fit-content;
    
    最後に、シンプルなcodepenデモを行います.
    郵便Measure per-letter dimension of text in JavaScript 最初に現れた1A23 Blog .