CodeZine(コードジン)

特集ページ一覧

JavaScriptでDOMレンジを扱う

DOM Rangeを使ってドキュメントを細かく操作する

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2007/08/08 14:00

DOMレンジは非常に便利な機能です。通常のDOM操作に加えて使用することで、例えばテキストノード内の文字の1部分を選択し、操作することも可能です。

目次

はじめに

 DOMはWebページを処理するための非常に優れたAPIですが、一般的には標準のDOM機能ばかりが注目されています。多くの開発者は、DOMにただのcreateElement()appendChild()よりもっと便利な機能があることを知りません。DOMのRange(レンジ)は、Webページを動的に操作するための非常に強力なツールです。

 レンジを使用すると、ドキュメントのセクションをノード境界に関係なく選択できます(この選択は内部的に行われるため、ユーザーには見えません)。レンジは、通常のDOM操作では処理できないドキュメントの細部を変更したいときに役立ちます。

 DOM Level 2では、レンジを作成するcreateRange()というメソッドが定義されています。DOM対応のブラウザ(Internet ExplorerはDOM対応ではありません)では、このメソッドはdocumentオブジェクトに属しているので、次のようにして新しいレンジを作成できます。

var oRange = document.createRange();

 ノードと同様に、レンジはドキュメントに直接結び付いています。ドキュメントがDOMスタイルのレンジに対応しているかどうかを判断するには、hasFeature()メソッドを使用します。

var supportsDOMRanges
       = document.implementation.hasFeature("Range", "2.0");

 DOMのレンジを使用する場合には、まずこの点を確認し、次のようにコードをif文で囲んでおくことをお勧めします。

if (supportsDOMRange) {
   var oRange = document.createRange();
   //range code here
}

DOMレンジによる単純な選択

 DOMレンジを使ってドキュメントの一部を選択する最も単純な方法は、selectNode()またはselectNodeContents()を使用することです。これらのメソッドは、1つの引数(DOMノード)を受け取り、そのノードから取得した情報をレンジに割り当てます。selectNode()メソッドは指定されたノード全体(子を含む)を選択しますが、selectNodeContents()は指定されたノードのすべての子を選択します。たとえば、次のようなドキュメントがあるとします。

<p id="p1"><b>Hello</b> World</p>

 このドキュメントに対して、次のJavaScriptを実行したとしましょう。

var oRange1 = document.createRange();
var oRange2 = document.createRange();
var oP1 = document.getElementById("p1");
oRange1.selectNode(oP1);
oRange2.selectNodeContents(oP1);

 この例の2つのレンジには、ドキュメント内のそれぞれ異なるセクションが含まれます。oRange1には<p>要素とそのすべての子が含まれ、oRange2には<b/>要素とテキストノード「World」が含まれます(図1を参照)。

図1
図1

 レンジを作成すると、そのレンジには次のようなプロパティが割り当てられます。

  • startContainer
  • レンジの開始位置を含んでいるノード(選択内の最初のノードの親)。
  • startOffset
  • レンジの開始位置を含んでいるstartContainer内でのオフセット。startContainerがテキストノード、コメントノード、またはCDataノードである場合、このオフセットは、レンジを開始するまでにスキップする文字の数を表します。それ以外の場合は、レンジ内の最初の子ノードのインデックスを表します。
  • endContainer
  • レンジの終了位置を含んでいるノード(選択内の最後のノードの親)。
  • endOffset
  • レンジの終了位置を含んでいるendContainer内でのオフセット(startOffsetと同様のルールに従います)。
  • commonAncestorContainer
  • startContainerとendContainerの両方が含まれている最初のノード。

 これらのプロパティはすべて読み取り専用で、レンジについての補足情報を提供するために用意されています。

 selectNode()を使用した場合、startContainer、endContainer、commonAncestorContainerの各プロパティは、いずれも指定ノードの親ノードに等しくなります。また、startOffsetプロパティは指定ノードの親のchildNodesコレクションにおける指定ノードのインデックスに等しくなり、endOffsetプロパティは「startOffsetの値+1」に等しくなります(1つのノードだけが選択されるため)。

 selectNodeContents()を使用した場合、startContainer、endContainer、commonAncestorContainerの各プロパティは、いずれも指定ノードに等しくなります。また、startOffsetプロパティは0になり、endOffsetは子ノードの数(node.childNodes.length)に等しくなります。

 これらのプロパティの使用例を次に示します。

<html>
  <head>
    <title>DOM Range Example</title>
    <script type="text/javascript">
      function useRanges() {
          var oRange1 = document.createRange();
          var oRange2 = document.createRange();
          var oP1 = document.getElementById("p1");
          oRange1.selectNode(oP1);
          oRange2.selectNodeContents(oP1);

          document.getElementById("txtStartContainer1").value
              = oRange1.startContainer.tagName;
          document.getElementById("txtStartOffset1").value =
              oRange1.startOffset;
          document.getElementById("txtEndContainer1").value =
              oRange1.endContainer.tagName;
          document.getElementById("txtEndOffset1").value =
              oRange1.endOffset;
          document.getElementById("txtCommonAncestor1").value =
              oRange1.commonAncestorContainer.tagName;
          document.getElementById("txtStartContainer2").value =
              oRange2.startContainer.tagName;
          document.getElementById("txtStartOffset2").value =
              oRange2.startOffset;
          document.getElementById("txtEndContainer2").value =
              oRange2.endContainer.tagName;
          document.getElementById("txtEndOffset2").value =
              oRange2.endOffset;
          document.getElementById("txtCommonAncestor2").value =
              oRange2.commonAncestorContainer.tagName;
        }
    </script>
  </head>
  <body><p id="p1"><b>Hello</b> World</p>
    <input type="button" value="Use Ranges" onclick="useRanges()" />
    <table border="0">
    <tr>
        <td>
          <fieldset>
              <legend>oRange1</legend>
              Start Container:
                  <input type="text" id="txtStartContainer1" /><br />
              Start Offset:
                  <input type="text" id="txtStartOffset1" /><br />
              End Container:
                  <input type="text" id="txtEndContainer1" /><br />
              End Offset:
                  <input type="text" id="txtEndOffset1" /><br />
              Common Ancestor:
                  <input type="text" id="txtCommonAncestor1" /><br />
          </fieldset>
        </td>
        <td>
          <fieldset>
              <legend>oRange2</legend>
              Start Container:
                  <input type="text" id="txtStartContainer2" /><br />
              Start Offset:
                  <input type="text" id="txtStartOffset2" /><br />
              End Container:
                  <input type="text" id="txtEndContainer2" /><br />
              End Offset:
                  <input type="text" id="txtEndOffset2" /><br />
              Common Ancestor:
                  <input type="text" id="txtCommonAncestor2" /><br />
          </fieldset>
        </td>
    </tr>
    </table>
  </body>
</html>

 この例をFirefoxなどのDOM対応ブラウザで実行した結果を図2に示します。

図2
図2

 図を見てもわかるように、oRange1のstartContainer、endContainer、commonAncestorContainerの各プロパティはいずれも<body/>要素になります。これは、この<p/>要素は完全に<body/>要素に含まれているからです。また、この<p/>要素の最初の子は<p/>要素なのでstartOffsetプロパティは0になり、このレンジは2つ目の子ノード(インデックス1)の前で終わるのでendOffsetプロパティは1になります。

 一方、selectNodeContents()メソッドで取得したoRange2の情報では、startContainer、endContainer、commonAncestorContainerの各プロパティはいずれも<p/>要素になります。これは、このレンジでは<p/>要素の子を選択しているからです。また、このレンジは<p/>要素の最初の子ノードから始まっているのでstartOffsetプロパティは0になり、このレンジには<p/>要素の2つの子ノード(<b/>とテキストノード「World」)が含まれているのでendOffsetプロパティは2になります。

 さらに、選択レンジをより細かく指定するために、次のようなメソッドが用意されています。これらのメソッドを使用すると、前述のプロパティには自動的に値が割り当てられます。

  • setStartBefore(refNode)
  • レンジの開始位置をrefNodeの前に設定します(したがって、refNodeは選択内の最初のノードになります)。startContainerプロパティはrefNodeの親に等しくなり、startOffsetプロパティはrefNodeの親のchildNodesコレクションにおけるrefNodeのインデックスに等しくなります。
  • setStartAfter(refNode)
  • レンジの開始位置をrefNodeの後ろに設定します(したがって、refNodeは選択内に含まれなくなり、その次の兄弟が選択内の最初のノードになります)。startContainerプロパティはrefNodeの親に等しくなり、startOffsetプロパティは「refNodeの親のchildNodesコレクションにおけるrefNodeのインデックス+1」に等しくなります。
  • setEndBefore(refNode)
  • レンジの終了位置をrefNodeの前に設定します(したがって、refNodeは選択内に含まれなくなり、その前の兄弟が選択内の最後のノードになります)。endContainerプロパティはrefNodeの親に等しくなり、endOffsetプロパティはrefNodeの親のchildNodesコレクションにおけるrefNodeのインデックスに等しくなります。
  • setEndAfter(refNode)
  • レンジの終了位置をrefNodeの後ろに設定します(したがって、refNodeは選択内の最後のノードになります)。endContainerプロパティはrefNodeの親に等しくなり、endOffsetプロパティは「refNodeの親のchildNodesコレクションにおけるrefNodeのインデックス+1」に等しくなります。

 これらのメソッドを使用すると、前述のプロパティに値が自動的に割り当てられます。複雑なレンジ選択を行いたい場合には、各プロパティに値を直接指定することもできます。


  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

あなたにオススメ

著者プロフィール

  • Nicholas C. Zakas(Nicholas C. Zakas)

    『Professional Ajax』(WROX, ISBN: 0-471-77778-1)および『Professional JavaScript for Web Developers』(WROX, ISBN: 0-7645-7908-8)の著者。

  • japan.internet.com(ジャパンインターネットコム)

    japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.com や EarthWeb.c...

バックナンバー

連載:japan.internet.com翻訳記事

もっと読む

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5