John Ernestの優れたコードを使用し、ニーズに合わせて少し作り直しました。
- TypeScriptの使用(Angularアプリケーションで);
- わずかに異なるデータ構造を使用します。
そして、それに取り組んでいる間、私はあまり知られていない(またはほとんど使用されていない)TreeWalkerに出くわし、再帰性を取り除くことができるので、コードをさらに単純化しました。
考えられる最適化は、ツリーを1回ウォークして、開始ノードと終了ノードの両方を見つけることですが、次のようになります。
- 巨大で複雑なページの最後であっても、速度の向上がユーザーに認識されるとは思えません。
- これにより、アルゴリズムがより複雑になり、読みにくくなります。
代わりに、開始が終了と同じである場合を扱いました(単なる注意、実際の選択はありません)。
[編集]範囲のノードは常にテキストタイプのようですので、コードをもう少し簡略化して、キャストせずにノードの長さを取得できるようにしました。
コードは次のとおりです。
export type CountingState = {
countBeforeNode: number;
offsetInNode: number;
node?: Text; // Always of Text type
};
export type RangeOffsets = {
start: CountingState;
end: CountingState;
offsets: { start: number; end: number; }
};
export function isTextNode(node: Node): node is Text {
return node.nodeType === Node.TEXT_NODE;
}
export function getCaretPosition(container: Node): RangeOffsets | undefined {
const selection = window.getSelection();
if (!selection || selection.rangeCount === 0) { return undefined; }
const range = selection.getRangeAt(0);
const start = countUntilEndNode(container, range.startContainer as Text, range.startOffset);
const end = range.collapsed ? start : countUntilEndNode(container, range.endContainer as Text, range.endOffset);
const offsets = { start: start.countBeforeNode + start.offsetInNode, end: end.countBeforeNode + end.offsetInNode };
const rangeOffsets: RangeOffsets = { start, end, offsets };
return rangeOffsets;
}
export function setCaretPosition(container: Node, start: number, end: number): boolean {
const selection = window.getSelection();
if (!selection) { return false; }
const startState = countUntilOffset(container, start);
const endState = start === end ? startState : countUntilOffset(container, end);
const range = document.createRange(); // new Range() doesn't work for me!
range.setStart(startState.node!, startState.offsetInNode);
range.setEnd(endState.node!, endState.offsetInNode);
selection.removeAllRanges();
selection.addRange(range);
return true;
}
function countUntilEndNode(
parent: Node,
endNode: Text,
offset: number,
countingState: CountingState = { countBeforeNode: 0, offsetInNode: 0 },
): CountingState {
const treeWalker = document.createTreeWalker(parent, NodeFilter.SHOW_TEXT);
while (treeWalker.nextNode()) {
const node = treeWalker.currentNode as Text;
if (node === endNode) {
// We found the target node, memorize it.
countingState.node = node;
countingState.offsetInNode = offset;
break;
}
// Add length of text nodes found in the way, until we find the target node.
countingState.countBeforeNode += node.length;
}
return countingState;
}
function countUntilOffset(
parent: Node,
offset: number,
countingState: CountingState = { countBeforeNode: 0, offsetInNode: 0 },
): CountingState {
const treeWalker = document.createTreeWalker(parent, NodeFilter.SHOW_TEXT);
while (treeWalker.nextNode()) {
const node = treeWalker.currentNode as Text;
if (countingState.countBeforeNode <= offset && offset < countingState.countBeforeNode + node.length) {
countingState.offsetInNode = offset - countingState.countBeforeNode;
countingState.node = node;
break;
}
countingState.countBeforeNode += node.length;
}
return countingState;
}