🔚

【JavaScript】選択範囲の終点座標の変更を監視する

2021/08/17に公開

目的

input, textarea 以外の要素において、キャレットの位置に相当する座標を変更されるたびに取得したい。

方法

document.addEventListener('selectionchange', () => {
  const selection = getSelection()
  const rangeCount = selection.rangeCount
  if (rangeCount === 0) {
    return
  }
  // 複数選択を防ぐ – Firefox (Gecko)
  if (rangeCount > 1) {
    for (let i = 1; i < rangeCount; i++) {
      selection.removeRange(selection.getRangeAt(i))
    }
  }
  const caretRange = document.createRange()
  const focusNode = selection.focusNode
  const focusOffset = selection.focusOffset
  caretRange.setStart(focusNode, focusOffset)
  caretRange.setEnd(focusNode, focusOffset)
  const caretRect = caretRange.getBoundingClientRect()
  console.log(caretRect.x, caretRect.y)
})

解説

selectionfocus は選択範囲の終点を含む node の情報を返します。

選択方向 (->, <-)
選択範囲 (||)
           |------>|
TextNode ( | h | e | l | l | o | )
           0   1   2   3   4   5

focusNode: TextNode
focusOffset: 2
               |<------|
TextNode ( | h | e | l | l | o | )
           0   1   2   3   4   5

focusNode: TextNode
focusOffset: 1

その情報を基にキャレットの範囲となる range を作ることで終点座標を取得できます。

// range 作成
const caretRange = document.createRange()

// selection の focus を取得
const focusNode = selection.focusNode
const focusOffset = selection.focusOffset

// focus の情報を range にセット
caretRange.setStart(focusNode, focusOffset)
caretRange.setEnd(focusNode, focusOffset)

// range の座標を取得 🎉
const caretRect = caretRange.getBoundingClientRect()
console.log(caretRect.x, caretRect.y)

注意事項

  • 取得した座標に常に張り付くような要素を作ると範囲選択に影響が出てしまう。
  • 全選択時、body の直下の最初か最後に input, textarea が存在する場合に始点か終点の対象要素が body になってしまい正しい座標が取得できない。
  • 全選択時、body の最初か最後に contenteditable="true" の要素が存在する場合に位置が最初なら anchor の値が focus に、最後なら focus の値が anchor と同じものになってしまう。

end

GitHubで編集を提案

Discussion