0

Given a page like this:

 <p>
 <span class="1">Here's some text</span>
 <span class="2">that the user</span>
 <span class="3">could select.</span>
 </p>

If a user selects the whole sentence (from "Here's" to "select."), I want to return "1" and "3".

If a user selects part of the sentence (from "some" in span 1 to "the" in span 2), I want to return "1" and "2".

What's the best approach here?

*Edit - I'm looking for a solution that allows for highlighting multiple pieces of (non-overlapping) text concurrently.

For example: "Here's some text that" and "user could select." - In this case, [[1,2],[2,3]] would be returned.

Lumen
  • 139
  • 11

2 Answers2

1

Edit: I just found out there is actually a selection.containsNode method which sounds like it would be perfect for you but apparently it's still an experimental technology.

There is no extensive highlight event in javascript so the solution is not going to be straightforward, and the fact that the text is split up across multiple elements makes it more difficult. You could use document.selection as the top answer to this similar question suggests, but then you still need to parse the returned text against the innerHTML of the span elements which seems like it would be pretty fiddly.

I think your best bet in this case would be to basically recreate highlight functionality using the existing JS events. Here is a naive implementation missing some functionality like double clicking to select and keyboard selection but you get the idea.

const hLights = Array.from(document.querySelectorAll('.hlight'));
let active = false;
let hoveredEls = [];

window.addEventListener('mousedown', function() {
  active = true;
});

window.addEventListener('mouseup', function() {
  active = false;
  console.log(hoveredEls);
  hoveredEls = [];
});

hLights.forEach(el => {
  el.addEventListener('mousemove', function() {
    if (active && !hoveredEls.includes(el.id)) hoveredEls.push(el.id);
  });
  el.addEventListener('mouseenter', function() {
    if (active) {
      if(hoveredEls.includes(el.id)) {
        const idx = hoveredEls.findIndex(el => el === el.id);
        hoveredEls.splice(idx, 1);
      } else {
        hoveredEls.push(el.id);
      }
    }
  });
});
<p>
 <span id="1" class="hlight">Here's some text</span>
 <span id="2" class="hlight">that the user</span>
 <span id="3" class="hlight">could select.</span>
</p>
lawrence-witt
  • 8,094
  • 3
  • 13
  • 32
  • This is a great implementation. Question though; I intend to implement this functionality in a way where someone could make multiple concurrent highlights across a page of text. Would the idea be to simply increment and add a new "active" variable for each new selection to store additional highlights? – Lumen Jun 16 '20 at 03:38
  • 1
    You shouldn't need multiple active variables, as all that it signifies is that the user has their mouse held down (i.e. dragging). What you would probably want to do is create a new highlight object - something like `{id: 1, highlightedEls: [1, 2]}` - each time the user drags and store them in an array. I would recommend looking into the new [Selection API](https://developer.mozilla.org/en-US/docs/Web/API/Selection) which I wasn't aware of when I posted my answer, as it will probably be more reliable if the browsers you are developing for support it. – lawrence-witt Jun 16 '20 at 11:08
0

so here is the html

    <span class="class-1" 
    onmousedown="getElementBegin('1')" onmouseup="getElementEnd('1')" >
        algum texto 1
    </span> <br>
    <span class="class-2" 
    onmousedown="getElementBegin('2')" onmouseup="getElementEnd('2')">
        algum texto 2
    </span> <br>
    <span class="class-3" 
    onmousedown="getElementBegin('3')" onmouseup="getElementEnd('3')">
        algum texto 3
    </span> <br>

    <p id="selected"> nada!</p>

And here is the js:

let begin
let end
let selection

document.onmouseup = function () {
selection = window.getSelection().toString()
console.log(selection);

selected = document.getElementById('selected')
selected.innerHTML = `selection goes to: ${begin} until ${end} <br> selection: ${selection}`
}

function getElementBegin(beginElement) {
begin = beginElement
console.log(begin)
}

function getElementEnd(endElement) {
end = endElement
console.log(end)
}