Shadow DOM境界を跨いだ選択範囲の処理を可能にするgetComposedRanges
はじめに
Selection
オブジェクトのgetComposedRanges
メソッドがBaseline 2025入りを果たしました。このメソッドは、Shadow DOMを含む文書全体での選択範囲を取得できるようにします。
この記事では、Selection
オブジェクトとそのgetComposedRanges
メソッドについてサンプルを交えて解説します。
Selection
Selection
はユーザーが選択したテキストの範囲やキャレットの位置を扱うオブジェクトです。
window.getSelection()
を使用して、現在の選択範囲を取得できます。
const selection = window.getSelection();
// 選択範囲の文字列を取得
const text = selection.toString();
Selectionオブジェクトのプロパティの紹介
サンプルテキスト
あさ、眼をさますときの気持は、面白い。かくれんぼのとき、押入れの真っ暗い中に、じっと、しゃがんで隠れていて、突然、でこちゃんに、がらっと襖をあけられ、日の光がどっと来て、でこちゃんに、「見つけた!」と大声で言われて、まぶしさ、それから、へんな間の悪さ、それから、胸がどきどきして、着物のまえを合せたりして、ちょっと、てれくさく、押入れから出て来て、急にむかむか腹立たしく、あの感じ、いや、ちがう、あの感じでもない、なんだか、もっとやりきれない。
選択中のテキスト(selection.toString()
)
選択要素の開始位置の要素
(selection.anchorNode.textContent
, selection.anchorOffset
)
選択要素の終了位置の要素
(selection.focusNode.textContent
, selection.focusOffset
)
選択の種類(selection.type
)
上記の例では、Selection
オブジェクトが持つ選択中の文字列の表示と、いくつかのプロパティを紹介しています。
この他にも、Selection
オブジェクトは選択中の範囲の個数を返すrangeCount
プロパティや、テキストが選択されているかどうかを表すisCollapsed
プロパティなどを持ちます。
テキストの選択範囲やキャレットの変更は、selectionchange
イベントによって検知することができます。
document.addEventListener('selectionchange', () => {
const selection = window.getSelection();
...
});
document
に対して登録しているので、上の例はこのページ内のどこを選択しても動作します。
続いて、Selection
オブジェクトが持つメソッドの紹介です。
Selectionオブジェクトのメソッドの紹介
サンプルテキスト
あさ、眼をさますときの気持は、面白い。かくれんぼのとき、押入れの真っ暗い中に、じっと、しゃがんで隠れていて、突然、でこちゃんに、がらっと襖をあけられ、日の光がどっと来て、でこちゃんに、「見つけた!」と大声で言われて、まぶしさ、それから、へんな間の悪さ、それから、胸がどきどきして、着物のまえを合せたりして、ちょっと、てれくさく、押入れから出て来て、急にむかむか腹立たしく、あの感じ、いや、ちがう、あの感じでもない、なんだか、もっとやりきれない。
選択範囲の追加
(selection.addRange(Range)
)
選択範囲の削除
(selection.removeAllRanges()
, selection.empty()
)
要素の子を全て選択する
(selection.selectAllChildren(Node)
)
範囲の変更
(selection.extend(Node, ?offset)
)
addRange
はRange
オブジェクトを引数に取り、選択範囲に対象を追加します。
Firefox以外のブラウザでは複数の選択をサポートしていないです。addRange
を実行する前に、rangeCount
プロパティを確認して1
以上の場合は選択範囲をクリアする必要があるので注意してください。
extend
は選択範囲から指定したノードの先頭までを選択範囲に拡張します。先頭以外の位置を指定したい場合は、オフセットを第2引数に渡します。
メソッドについても、プロパティと同様に、ここで紹介したもの以外に多数存在します。
しかし、この章の目的はSelection
オブジェクトの概要と基本的な機能を理解するところなので、説明はここまでとします。
getComposedRanges
getComposedRanges()
はShadow DOM境界を跨いだ選択範囲の処理を可能にするメソッドです。
const ranges = getComposedRanges({ shadowRoots: [shadowRoot1, shadowRoot2] });
戻り値はStaticRange
オブジェクトの配列です。StaticRange
はRange
と似ていますが、重要な違いがあります。Range
はDOMの変更に動的に追従するのに対し、StaticRange
は作成時点のDOM状態を固定的に保持します。
オプションにはShadowRoot
オブジェクトの配列を渡します。渡したShadowRoot
オブジェクトに含まれる要素であれば、Shadow DOM内の要素であっても選択範囲として検出されます。
選択範囲にオプションで指定していないShadowRoot
オブジェクトが持つ要素が含まれる場合は、そのShadow DOMのホスト要素が返されます。
getComposedRangesメソッドの紹介
サンプルテキスト(テキスト全体が閉じたShadow Tree)
あさ、眼をさますときの気持は、面白い。かくれんぼのとき 、押入れの真っ暗い中に、じっと、しゃがんで隠れていて、突然、でこちゃんに、がらっと襖をあけられ、日の光がどっと来て、でこちゃんに、「見つけた!」と大声で言われて、まぶしさ、それから、へんな間の悪さ、それから、胸がどきどきして、着物のまえを合せたりして、ちょっと、てれくさく、押入れから出て来て、急にむかむか腹立たしく、あの感じ、いや、ちがう、あの感じでもない、なんだか、もっとやりきれない。
SafariやIOSのChrome等ではoptionsを含んだgetComposedRangesメソッドが正しく動作しない場合があります。
これまで使われていたgetRangeAt
メソッドと、getComposedRanges
メソッドを使った時の比較、およびgetComposedRanges
メソッドの引数にShadowRoot
を渡した場合と渡さなかった場合の違いを確認できます。
サンプルテキスト全体にattachShadow
しています(closed
)。
背景色があるテキスト部分を全て含めて選択した場合や、背景色があるテキストを一部含めた場合、サンプルテキスト以外も含めた選択を試してください。
getComposedRanges
メソッドにオプションを渡した場合はstartContainer
とendContainer
に正確な要素が渡りますが、それ以外はShadow Treeを含む要素全体が返されます(MacOSのChromeを用いた結果のため、他のブラウザでは異なる結果になるかもしれません)。
このように、適切なオプションを付与したgetComposedRanges
メソッドを用いることで、Shadow DOM境界を跨いだ選択が行われたとしても、他の要素と同じようにShadow DOM内の要素を正しく扱えるようになります。