7

I'm looking to do something whenever a user finishes making a selection —essentially, on the first mouseup event after every selectstart event, I think— on the page. I want to take that selection and wrap it in an element to be styled via CSS. I presumed the Selection API offered an event for this; however, it doesn't seem to.

I don't simply listen for mouseup 'cause I'm especially looking for this to work with the selection that results from the browser's find functionality ("Find in This Page…"; +f).

let selContainer = document.createElement('span')
span.classList.add('user-selection')

const wrapSelection = () => {
  window.getSelection().getRangeAt(0).surroundContent(selContainer)
}


/*                   ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓
                     ┃                          ┃
                     ┃  The Selection API only  ┃
                     ┃  affords these events:   ┃
                     ┃                          ┃
                     ┃  - selectionchange       ┃
                     ┃  - selectstart     ┏━━━━━┫
                     ┃                    ┃issue┃
                     ┗━━━━━━━━━━━━━━━━━━━━┻━━━━━┛

*/document.addEventListener('selectfinish', wrapSelection)/*
                             ┗━━━━┳━━━━━┛
                                  ┃
                                  ┃
                               no such
                                event                                                                                                                                                                                                        */
tjfwalker
  • 494
  • 3
  • 18

4 Answers4

3

This is trickier than it should be. I’ve used a combination of element.onselectstart, element.onmouseup, and document.onselectionchange. Take a look at this demo.

function onSelect(element, callback) {
    // console.log(element, callback);
    let isSelecting = false;
    let selection = null;

    function handleSelectStart(event) {
        // console.log(event);
        isSelecting = true; 
    }

    function handleMouseUp(event) {
        // console.log(event, isSelecting);
        if (isSelecting && !document.getSelection().isCollapsed) {
            callback((selection = document.getSelection()));
            isSelecting = false;
        }
    }

    function handleSelectionChange(event) {
        // console.log('change', isSelecting);
        if (document.getSelection().isCollapsed && null !== selection) {
            callback((selection = null));
        }
    }

    element.addEventListener('selectstart', handleSelectStart);
    element.addEventListener('mouseup', handleMouseUp);
    document.addEventListener('selectionchange', handleSelectionChange);

    return function destroy() {
        element.removeEventListener('selectstart', handleSelectStart);
        element.removeEventListener('mouseup', handleMouseUp);
        document.removeEventListener('selectionchange', handleSelectionChange);
    };
}

This won’t handle non-mouse interactions. Accommodating pointer and keyboard events would be a nice enhancement. I think the overall pattern holds, though.

Justin Makeig
  • 2,097
  • 15
  • 29
2

One way that I've gotten it to work is to track the selectionchange event and when the selection doesn't change for over 500ms, I consider that a select end. It's not perfect, but it works and correctly triggers for any kind of selection, be it mouse, keyboard, or CTRL+F.

    let selectionDelay = null, selection = '';

    document.addEventListener('selectionchange', () => {
        const currentSelection = document.getSelection().toString();
        if (currentSelection != selection) {
            selection = currentSelection;
            if (selectionDelay) {
                window.clearTimeout(selectionDelay);
            }
            selectionDelay = window.setTimeout(() => {
                wrapSelection();
                selection = '';
                selectionDelay = null;
            }, 500);
        }
    });
IceMetalPunk
  • 5,476
  • 3
  • 19
  • 26
1

I picked through the source code of the hypothes.is web annotation client in an effort to understand how they get their toolbar to appear on user select action end. It seems to be a matter of employing an observer via zen-observable.

Game T. S.
  • 11
  • 3
  • Is their solution the best/simplest/only one? If so, how exactly does it work, is it setup; if not, what else is there? – tjfwalker Mar 03 '18 at 18:24
0

simpler alternative to the accepted answer.

 document.addEventListener('mouseup', e => {
     var s = document.getSelection();
     if (!s.isCollapsed) {
          // do sth with selection
     }
 });

or maybe better?

 document.addEventListener('selectstart', e => {
     document.addEventListener('mouseup', somefunction);
 });
 function somefunction(e) {
     // do sth
     document.removeEventListener('mouseup', somefunction);
 }
chuan
  • 194
  • 2
  • 14