How to use scrollIntoView from text node

136 Views Asked by At

I can't figure out how to get the element of a text node but I can get the parent element.

I've written a javascript routine to make a selection using a range. So I have a start node and end node which have been obtained with document.createTreeWalker(rootnode, NodeFilter.SHOW_TEXT, null, false).

I want to use scrollIntoView() but it seems I can only call this from an element. So at the moment I'm using startNode.parentElement.scrollIntoView.

let range = new Range()
range.setStart(startNode, startPos)
range.setEnd(endNode, endPos)
document.getSelection().removeAllRanges()
document.getSelection().addRange(range)
startNode.parentElement.scrollIntoView({ behavior: 'instant', block: 'start', inline: 'start' })

All good when the tags are small but if it were a large paragraph of text then the parent position could be a fair way off the actual elements position. Is there a way to get the element of a text node so I can call scrollIntoView?

As a work around I'm using range.getBoundingClientRect() and then setting the scrollTop of the page.

let bb=range.getBoundingClientRect()
pagetop.parentElement.scrollTop = bb.y+pagetop.parentElement.scrollTop-130 // 130 fudge value

It works :), but I'd rather use scrollIntoView or at least have a better understanding of what I'm doing wrong.

2

There are 2 best solutions below

2
Iman Hedeshy On BEST ANSWER

To scroll to a text node in JavaScript, you can't use scrollIntoView() directly on the text node. But, you can use a workaround by creating a temporary element.
Like creating a <span> element right where you want, and scroll to that and then remove it from the DOM.

let range = new Range();
range.setStart(startNode, startPos);
range.setEnd(endNode, endPos);

let tempSpan = document.createElement('span');
range.insertNode(tempSpan);

tempSpan.scrollIntoView({ behavior: 'instant', block: 'start' });
tempSpan.remove();

document.getSelection().addRange(range);
2
jsejcksn On

You can use the x and y coordinates of the DOMRect returned by the Range.prototype.getBoundingClientRect() method as arguments to window.scroll() like this:

const nodeStart = new Text("nee");
const nodeEnd = new Text("dle");

function createHaystack() {
  const paragraph = document.querySelector("p.haystack");

  for (let i = 0; i < 100; i += 1) {
    paragraph.append(new Text("hay ".repeat(50)));
  }

  paragraph.append(nodeStart, nodeEnd, " ");

  for (let i = 0; i < 100; i += 1) {
    paragraph.append(new Text("hay ".repeat(100)));
  }
}

function findNeedle() {
  const range = new Range();
  range.setStart(nodeStart, 0);
  range.setEnd(nodeEnd, nodeEnd.length);
  document.getSelection().removeAllRanges();
  document.getSelection().addRange(range);

  const rangeRect = range.getBoundingClientRect();
  window.scroll(rangeRect.x, rangeRect.y);
}

createHaystack();
document.querySelector("button").addEventListener("click", findNeedle);
body { font-family: sans-serif; }
<button>Find needle</button>
<p class="haystack"></p>