Lately, I haven't been feeling terribly creative. Quite blocked, actually. And, as Ze Frank would recommend, I acknowledge that creativity is outside of my rational control. So, in order to get back to a good place, I need to just keep the machinery firing and hope that the creativity returns. So, today, I wanted to look at part of the browser Selection API. And, specifically, how I can add elements to the page in order to outline the currently-selected text.
When a user highlights text on the page, the browser exposes a Selection object. This Selection object composes zero or more Range objects, each of which represents a portion of the selected content. In addition to being able to report the text-value of the selected content, the Range object is also able to report the physical location of the selection within the current viewport. It does so with two methods:
- Range.getBoundingClientRect() - Returns the bounding box for the entire selection.
- Range.getClientRects() - Returns the bounding box for each element in the selection.
According to the Mozilla Developer Network, both of these methods are considered experimental (Working Draft). However, they appear to be supported in all the major browsers going back to IE9. As such, I'm going to use them to calculate the outlines that I inject into the page after the user is done selecting the text.
It should be noted that the DOMRect objects that are returned in each of the aforementioned elements define the position of the selection in relation to the browser's viewport - not in relation to the document. As such, things like scroll-offset and window-resizing may need to be taken into account, depending on how you use the reported values. In my case, to keep things simple, I'm just redrawing the outlines when the viewport changes.
In the following code, I'm listening for the "selectionchange" event, which fires every time the user changes the current selection. In order to cut down on processing overhead, I'm debouncing the event so that I am not constantly redrawing rectangles. I did run into some cross-browser inconsistencies in IE11, which doesn't appear to fire the "selectionchange" event on the first selection action. As such, to create a consistent experience in IE11, I am also binding to the "select" event.
At first, I tried to use the "right" and "bottom" properties reported by the DOMRect objects. However, I was getting some funky results - my outlines ended up being much wider than my actual text selection. As such, I switched over to using the "width" and "height" properties, respectively. This seemed to work much more consistently.
But, as you can see, I'm not doing anything particularly complex - I'm just gathering the rectangles and I'm painting DIV elements over the reported locations. And, when we run this page and select some text, we get the following browser output:
As you can see, the one DOMRect object reported by .getBoundingClientRect() is rendered in red. And, the multiple DOMRect objects reported by .Range.getClientRects() are rendered in a dashed-blue.
Anyway, this was just a fun little exploration of the browser's Selection API. There's actually a lot more to the API, which allows for programmatic control of which text segments are selected. But, for now, this is just a little something to exercise the creative juices. And, I can think of some fun things to do with the reported position information.
I did a follow-up post on using the Selection API to create a Medium-inspired directive that presents a social-share call-to-action above the selected text: