uhyohyo.net

JavaScript初級者から中級者になろう

八章第一回 Rangeとは

このページの最終更新日:

八章では、Rangeというものを解説します。今回は、そもそもRangeとはどういうものかを解説します。

Rangeとは

Rangeとは、文書中における「範囲」を表すものです。例えば、

あい<strong>うえ</strong>お。

というソースががあれば、

あいうえお。

のように表示されます。このとき、例えば


あいう  。
     
  ここから  ここまで
  

のような感じである範囲を表せます。

要するに「『え』から『お』まで」なので、案外単純なように見えます。しかし、DOMという観点からこれを見ていくと、上の木構造は

  • #text "あい"
  • strong
    • #text "う"
  • #text "。"

となっていて、この範囲は(木構造上で)離れた複数のノードにまたがっています。こうなると、一筋縄ではいかないのが想像がつくと思います。このようなものを扱うのが、Rangeなのです。

範囲の表し方

Rangeは「開始点」と「終了点」で表します。今回の例の場合は、開始点は「『え』の前」、終了点は「『。』の前」ということになりますね。

Rangeでは、開始点や終了点はコンテナオフセットを使って表されます。

これらの意味は、実は、文字列を表す、テキストノードなどの場合と、それ以外の場合に分けられます。まず、それ以外の場合について見ていきます。例えば、

AAA
  • BBB
  • CCC
  • DDD

という木構造があり、AAAの子ノードたちの間に開始点があったとします。このとき、開始点と成り得る場所は、

AAA
  • ←BBBの前
  • BBB
  • ←BBBとCCCの間
  • CCC
  • ←CCCとDDDの間
  • DDD
  • ←DDDの後

が考えられます。このように、文字列中の位置だけでなく、兄弟ノードにおいての、ノードとノードの間の位置も点と成り得ます。

さて、このとき、コンテナは、これらの兄弟の親ノードです。今回の場合、AAAですね。そして、オフセットは数値です。これは、最初の子ノードの前を0として、その次を1,その次を2,・・・と順番に番号をつけていったものです。つまり、

AAA ←これがコンテナ
  • ← 0
  • BBB
  • ← 1
  • CCC
  • ← 2
  • DDD
  • ← 3

ということです。ノードとノードの間の位置は必ずこのように表せます。

次に、文字列中の位置(文字と文字の間)の表し方を解説します。

EEE
  • #text "あいうえお"
  • #text "かきくけこ"

という木構造があって、例えば"あいうえお"のテキストノードの中に点をおきたいとします。このとき、コンテナそのテキストノードです。つまり、

EEE
  • #text "あいうえお" ←これ
  • #text "かきくけこ"

ですね。

このとき、その文字列の中でも、


⁡ あ い う え お
↑ ↑ ↑ ↑ ↑ ↑
0  1  2  3  4  5

これだけ可能性があります。数字が振ってありますが、これがそのままオフセットになります。つまり、一番最初の位置が0、その次が1,・・・という具合ですね。

これで開始点や終了点をコンテナ・オフセットを使って表す方法が分かりました。では、前の例のRangeを実際に表してみましょう。

  • #text "あい"
  • strong
    • #text "う"
  • #text "。"

この例では、Rangeの範囲は「えお」の部分でした。まず、開始点のコンテナは

  • #text "あい"
  • strong
    • #text "うえ" ←これ
  • #text "お。"

で、オフセットは1です。

と考えると、オフセットは1であることが分かります。

同様に、終了点は、コンテナが

  • #text "あい"
  • strong
    • #text "うえ"
  • #text "お。" ←これ

で、オフセットは1ですね。

Rangeの作り方

これで、Rangeの範囲の表し方が分かりました。いよいよ、JavaScriptでのRangeの作り方を解説します。

Rangeを作るには、documentの持つメソッドcreateRangeを使います。引数はなく、戻り値で新しくできたRangeが帰ってきます。

var r = document.createRange();

こうしてできたRangeは、初期状態では開始点と終了点が共に「文書の一番前」に設定されています。コンテナとオフセットで表すと、コンテナはdocumentで、オフセットは0ということになっています。つまり、

document ←コンテナ
  • ←開始点・終了点
  • html
    • head
    • body

ですね。

また、もうひとつのRangeの作り方としてnewを使う方法があります。つまり、var r = new Range();ということです。

ただRangeを作っても、開始点や終了点がまともでなければ使い物になりません。そこで、開始点・終了点を変更するメソッドがあります。それぞれ、setStartsetEndです。

どちらも、引数は2つです。一つ目はコンテナ、二つ目がオフセットです。


var r = document.createRange();
r.setStart(document.body,0);
r.setEnd(document.body,document.body.childNodes.length);

という感じで使います。ちなみに、このソースでやっていることは分かりますか? 開始点は、body要素の一番最初に設定しています。終了点のコンテナも同じbody要素ですが、オフセットがdocument.body.childNodes.lengthとなっています。

これはどういうことかというと、あるノードの最初の子ノードを0番目の子ノードとしたとき、

AAA
  • ←オフセット: 0
  • BBB …0番目
  • ←オフセット: 1
  • CCC …1番目
  • ←オフセット: 2
  • DDD …2番目
  • ←オフセット: 3

となっていて、オフセットがnの位置は、n番目の子ノードの直前になっています。ここで、childNodes.lengthは子ノードの数なので、一番最後の子ノードは(length-1)番目ということになります。つまり、childNodes.lengthを指定することで、その要素の一番後ろに位置を設定していたのです。

結局、このRangeは、body要素の中身全体を包んでいるということになります。

なお、開始点が終了点より後ろになるように設定したり、終了点が開始点より前になるように設定することもできません。もしそのように設定しようとした場合、例えば開始点が終了点より後ろになるようにしようとした場合は、終了点もその場所まで押し下げられます。逆も然りです。ちなみに、このように、開始点と終了点が一致しているRangeを「つぶれている」(collapsed)というそうです。

だから、上のコードでは、createRangeで作ったノードは開始点と終了点が共に文書の一番前だからつぶれているRangeで、そのあとsetStartで開始点をbody要素の最初に設定しようとしたとき、終了点より開始点が後になってしまうから、一旦終了点もbody要素の最初の位置にきていたのです。その後setEndで終了点のみbody要素の最後の位置になります。

また、現在の開始点や終了点を知る方法もあります。これらは、Rangeのプロパティとして参照できます。startContainer,startOffset,endContainer,endOffsetの4つのプロパティをRangeは持っており、それぞれ開始点のコンテナ、開始点のオフセット、終了点のコンテナ、終了点のオフセットです。

ただし、これらは書き換えできません。これを直接いじって開始点・終了点を変えることはできないのです。

その他のメソッド・プロパティ

他にもいろいろRangeをいじるメソッドがあります。

setStartBefore,setEndBeforeは、それぞれ開始点・終了点を、あるノードの直前に移動します。引数はそのノードです。例えば、


var r = document.createRange();
r.setStartBefore(document.body);

とした場合、body要素の前だから、開始点は

html
  • html ←コンテナ
    • head
    • ←開始点
    • body

ということになります。即ち、コンテナはhtml要素で、オフセットは1ですね。

また、setStartAfter,setEndAfterというメソッドもあります。これは、上の2つは逆にあるノードの直後に移動します。

さらに、使う機会があるかは知りませんが、collapseというメソッドがあります。これは、そのRangeをつぶれた状態にする、つまり開始点と終了点が同じ状態にするものです。このとき、開始点に合わせるか終了点に合わせるかという問題があるので、それを1つの引数で指定します。これは真偽値で、trueなら開始点、falseなら終了点に合わされます。

また、そのRangeが今つぶれているかどうかは、collapsedというプロパティに真偽値で入っています。

さらにさらに、selectNodeselectNodeContentsという2つのメソッドがあります。どちらも引数はノード一つです。これらのメソッドでは、開始点と終了点が同時に変更されます。

selectNodeは、Rangeを、そのノードを囲む(選択した)状態にするというものです。例えば、


var r = document.createRange();
r.selectNode(document.body);

としたとき、

html
  • html
    • head
    • ←開始点
    • body
    • ←終了点

となります。body要素全体が範囲になっていることがわかります。開始点と終了点のコンテナはいずれもhtml要素で、開始点はオフセット1、終了点はオフセット2となります。

selectNodeContentsは、そのノードの外側から囲むのではなく、内側から囲みます。つまり、例えば


var r = document.createRange();
r.selectNodeContents(document.body);

としたときは、

html
  • html
    • head
    • body ←コンテナ
      • ←開始点
      • ←終了点

という感じになります。コンテナはどちらもbody要素になります。

今回は、Rangeの作り方を解説しました。次回から、Rangeの使い方を解説していきます。