Skip to content

Instantly share code, notes, and snippets.

@Gozala
Last active June 8, 2023 07:31
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Gozala/c68204c83a4c71a3620fe21a465797db to your computer and use it in GitHub Desktop.
Save Gozala/c68204c83a4c71a3620fe21a465797db to your computer and use it in GitHub Desktop.
Range hilighting code using getClientRects API

Highlight Selection Ranges

Code here takes a DOM Selection Range instance and creates a highlighting for it by using getClientRects. Approach was inspired by marks although here function attempts to find nearest positioned parent element to the commonAncestorContainer and draw all the highilighting rectangles there, this avoids issues with an overflowing content.

Issues

  • Approach ignores z-index which isn't great as some element might be overlaying the selection in which case it should not appear, but it does if we use high z-index value. If we use low z-index value then some elements (possibly ones containing selection) might end up overlaying selection itself.
  • Rendered selections are scattered all over the DOM making it difficult to manage view code properly.
var getPositionedAncestor = (node, root=node.ownerDocument.body) => {
var element = node.nodeType === Node.ELEMENT_NODE
? node
: node.parentElement
while (element != root) {
if (getComputedStyle(element).position != 'static') {
return element
} else {
element = element.parentElement
}
}
return element || root
}
var setRect = ({style}, {top, left, height, width}) => {
style.top = `${top}px`;
style.left = `${left}px`;
style.height = `${height}px`;
style.width = `${width}px`;
}
var SVG = class SVG {
static createElement(document, name) {
return document.createElementNS('http://www.w3.org/2000/svg', name)
}
}
var mark = (container, range) => {
const document = container.ownerDocument
const selection = document.createElement('article')
selection.className = 'selection'
selection.style.position = 'absolute'
selection.style.pointerEvents = 'none'
selection.style.opacity = '0.3'
selection.style.width = '100%'
selection.style.height = '100%'
selection.style.zIndex = '99998'
selection.style.top = '0'
selection.style.left = '0'
var rects = range.getClientRects()
var offset = container.getBoundingClientRect()
for (let {left, top, height, width} of rects) {
const range = document.createElement('section')
range.style.position = 'absolute'
range.style.backgroundColor = 'yellow'
range.style.left = `${left - offset.left}px`
range.style.top = `${top - offset.top}px`
range.style.height = `${height}px`
range.style.width = `${width}px`
selection.appendChild(range)
}
container.appendChild(selection)
}
var getRect = (element) => {
const document = element.ownerDocument
const {body, documentElement} = document
const scrollTop
= 0
+ (body ? body.scrollTop : 0)
+ (documentElement ? documentElement.scrollTop : 0)
const scrollLeft
= 0
+ (body ? body.scrollLeft : 0)
+ (documentElement ? documentElement.scrollLeft : 0)
const {top, left, height, width} = element.getBoundingClientRect();
return {
top: top + scrollTop,
left: left + scrollLeft,
height,
width
}
}
var highlight = (range) =>
mark(getPositionedAncestor(range.commonAncestorContainer), range)
highlight(document.getSelection().getRangeAt(0))
@liaoleejun
Copy link

This is a good idea, but the highlights are a bit opaque. Is there a better way than wrapping tags, like tags?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment