はじめに
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を参照)。

レンジを作成すると、そのレンジには次のようなプロパティが割り当てられます。
- startContainer
- startOffset
- endContainer
- endOffset
- commonAncestorContainer
これらのプロパティはすべて読み取り専用で、レンジについての補足情報を提供するために用意されています。
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に示します。

図を見てもわかるように、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)
- setStartAfter(refNode)
- setEndBefore(refNode)
- setEndAfter(refNode)
これらのメソッドを使用すると、前述のプロパティに値が自動的に割り当てられます。複雑なレンジ選択を行いたい場合には、各プロパティに値を直接指定することもできます。