SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

japan.internet.com翻訳記事

JavaScriptでDOMレンジを扱う

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

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

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」に等しくなります。

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

会員登録無料すると、続きをお読みいただけます

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

次のページ
DOMレンジによる複雑な選択

この記事は参考になりましたか?

  • このエントリーをはてなブックマークに追加
japan.internet.com翻訳記事連載記事一覧

もっと読む

この記事の著者

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

japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.comEarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

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)の著者。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/1519 2007/08/10 11:43

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング