ユーザー選択に含まれるすべての要素を取得したい(DOM 2範囲/ MS TextRangesなど)。テキスト選択で要素を取得する
/** @return {Array.<Element>} */
function getSelectedElements() {
var elements = [];
// get elements in the user selection somehow
return elements;
}
私はティム・ダウンの優れたsolution to a similar question、そしていくつかのMOZとMSドキュメント、およびいくつかのPPKのもの以下でこれを実行しようとしました。
アプローチは、基本的には次のとおりです。
は、DOMの選択やIEの選択としてSelectionLikeObjectを定義します。
RangeLikeObjectをDOM RangeまたはIE TextRangeとして定義します。
containerNode
をノードとします。containerElement
を要素とします。containedElements
をNodeListとします。elementRange
をRangeLikeObjectとします。selectedRange
をRangeLikeObjectとします。selectedElements
を要素の配列とします。element
を要素とします。selection
をSelectionLikeObjectとします。selection
をユーザーが選択します。selectedElements
を新しい配列に設定します。各selectedRange
selection
中の場合:
selectedRange
の共通の先祖コンテナへ設定し
containerNode
。containerElement
を最も近い要素祖先からcontainerNode
に設定します。containerElement
の子孫のリストにcontainedElements
を設定します。containedElements
の各element
に対して:
設定
elementRange
element
から。selectedRange
の 境界内elementRange
秋の境界線の場合:- プッシュ
element
selectedElements
へ。
- プッシュ
DOMブランチは次のようになります。
/**
@param {Document} doc
@return {Array.<Element>}
*/
getSelectedElements.fromDOM = function (doc) {
/** @type {Range} */
var selectedRange;
/** @type {Array.<Element>} */
var selectedElements = [];
/** @type {Node} */
var containerNode;
/** @type {Element} */
var containerElement;
/** @type {NodeList} */
var containedElements;
/** @type {Range} */
var elementRange;
/** @type {Element} */
var element;
/** @type {Selection} */
var selection = doc.defaultView.getSelection();
/** @type {number} */
var rangeCount = selection.rangeCount;
/** @type {number} */
var elementCount;
/** @type {number} */
var i;
// hack for browsers without getRangeAt
// see http://www.quirksmode.org/dom/range_intro.html
if (!selection.getRangeAt) {
selection.getRangeAt = function (i) {
/** @type {Range} */
var range = doc.createRange();
if (i || !selection.anchorNode) {
return range;
}
range.setStart(selection.anchorNode, selection.anchorOffset);
range.setEnd(selection.focusNode, selection.focusOffset);
return range;
};
selection.rangeCount = 1;
}
elementRange = doc.createRange();
for (i = 0; i < rangeCount; ++i) {
selectedRange = selection.getRangeAt(i);
containerNode = selectedRange.commonAncestorContainer;
while (containerNode && containerNode.nodeType != 1) {
containerNode = containerNode.parentNode;
}
if (!containerNode) {
return selectedElements; // something went wrong...
}
containerElement = /** @type {Element} */ containerNode;
containedElements = containerElement.getElementsByTagName('*');
elementCount = containedElements.length;
for (var i = 0; i < elementCount; ++i) {
element = containedElements[i];
elementRange.selectNodeContents(element);
if (elementRange.compareBoundaryPoints(selectedRange.END_TO_START, selectedRange) < 1 &&
elementRange.compareBoundaryPoints(selectedRange.START_TO_END, selectedRange) > -1) {
selectedElements.push(element);
}
}
}
elementRange.detach();
return selectedElements;
};
IEの分岐は次のようになります。
/**
@param {Document} doc
@return {Array.<Element>}
*/
getSelectedElements.fromIE = function (doc) {
// Selection - http://msdn.microsoft.com/en-us/library/ie/dd347133(v=vs.85).aspx
// TextRange - http://msdn.microsoft.com/en-us/library/dd347140(v=vs.85).aspx
// ControlRange - http://msdn.microsoft.com/en-us/library/ie/ms537447(v=vs.85).aspx
/** @type {TextRange|ControlRange} */
var ieRange = doc.selection && doc.selection.createRange();
/** @type {Array.<Element>} */
var selectedElements = [];
/** @type {TextRange} */
var selectedRange;
/** @type {Element} */
var containerElement;
/** @type {NodeList} */
var containedElements;
/** @type {TextRange} */
var elementRange;
/** @type {Element} */
var element;
/** @type {Selection} */
var selection;
/** @type {number} */
var i = -1;
if (ieRange.text === void 0) {
return []; // FIXME: It's a ControlRange, give up.
}
selectedRange = /** @type {TextRange} */ ieRange;
containerElement = selectedRange.parentElement();
containedElements = containerElement.getElementsByTagName('*');
elementRange = doc.body.createTextRange();
while ((element = containedElements[++i])) {
elementRange.moveToElementText(element);
if (elementRange.compareEndPoints("StartToEnd", selectedRange) > -1 &&
elementRange.compareEndPoints("EndToStart", selectedRange) < 1) {
selectedElements.push(element);
}
}
return /** @type {Array.<Element>} */ selectedElements;
};
ここで私が解決したい問題は、要素内のテキストの一部だけが選択されていれば、返された配列に表示されますが、それはpartly selectedです。
完全に選択された要素のみを含むように動作を変更するパラメータを追加したいと思います。私は答えがcompareBoundaryPointsにあると感じている、私はまだそれを理解するのに十分理解していない。
また、IEコードはまだテストされていませんが、何か問題があるかどうかを教えてください。
「含む」を定義するのが難しい場合があります。 1つのアプローチは、選択されたテキストからすべての空白を削除し、極端な要素のテキストコンテンツを取得し、その空白を減らしたテキストコンテンツを取得し、選択肢にwholyが含まれているかどうかを確認することです(test、match、トリックをやる)。そうであれば、要素全体が選択されます。そうでない場合、そうではありません。 – RobG
@RobGこれは 'anchorOffset'と' focusOffset'をチェックすることで可能でしょうか?要素のコンテンツ全体が選択されている場合は、「完全に選択された」とみなしたい、そうでない場合は「部分的にしか選択しない」と考えます。ほとんどの場合、開始要素と終了要素の一部が選択されます。実際には、私はいつもそれらを部分的に選択されたものとして扱うことができるかもしれません。 –
@RobG:それは必要ありません。すべての主要なブラウザでは選択範囲を合理的に正確に取得できます。 –