Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I highlight the line of text that is closest to the mouse?

I have a long text and I'd like to offer the user a reading help: The current line should be highlighted. To make it easier, I'll just use the Y coordinate of the mouse (this way, the mouse pointer isn't going to get in the way). I have a big DIV with the id content which fills the whole width and a small DIV with the class content for the text (see here for an example).

I'm using jQuery 1.4. How can I highlight the line of text that is closest to the current mouse position?

like image 729
Aaron Digulla Avatar asked Mar 16 '10 17:03

Aaron Digulla


2 Answers

Not sure if jQuery will help you out much here, but you could take a look at the element.getClientRects method, documented on MSDN and MDC. More specifically, this example at MSDN is sort of similar to what you want to achieve, highlighting lines using a cleverly z-indexed div element that goes behind the text at the co-ordinates returned by getClientRects().

You should be able to achieve the same thing by looping through the TextRectangle objects returned in the document's onmousemove and checking to see if the y value of the mouse cursor is > the top and < the bottom of each rectangle and moving the cleverly z-indexed div to the same position/height.

All the current major browsers support getClientRects().


http://jsbin.com/avuku/15

UPDATED - working in Chrome, IE6/7/8, Firefox, Opera, Safari. The initial problems I had in the other browsers were related to the DIV needing to be display: inline.
UPDATED AGAIN - I had to refer to this answer for some newer questions, so I took the time to update it to recalc the lines on window resize. It looks like others have been playing around too, it's now on revision 15.

like image 168
Andy E Avatar answered Sep 19 '22 15:09

Andy E


I don't see how you could feasibly do this without explicitly-wrapped text (i.e., newlines or <br> elements).

To the best of my knowledge, there's no way for the DOM to discover where a specific piece of text has wrapped, character-wise nor pixel-wise - including what I know of the Range API - not to mention the dynamic nature text can assume, such as with the text-zooming feature of browsers.

But if you could somehow manage to generate/inject explicit line-endings, then I think I have a solution for you.

EDIT

Thanks to the awesome information in Pekka's answer, I've cobbled together a functional prototype, but it has a significant caveat - works with plain-text content only. Any HTML present the body of the element will be stripped.

jQuery.fn.wrapLines = function( openTag, closeTag )
  {
    var dummy = this.clone().css({
            top: -9999,
            left: -9999,
            position: 'absolute',
            width: this.width()
        }).appendTo(this.parent())
      , text = dummy.text().match(/\S+\s+/g);

    var words = text.length
      , lastTopOffset = 0
      , lines = []
      , lineText = ''
    ;

    for ( var i = 0; i < words; ++i )
    {
      dummy.html(
          text.slice(0,i).join('') +
          text[i].replace(/(\S)/, '$1<span/>') +
          text.slice(i+1).join('')
      );

      var topOffset = jQuery( 'span', dummy ).offset().top;

      if ( topOffset !== lastTopOffset && i != 0 )
      {
        lines.push( lineText );
        lineText = text[i];
      } else {
        lineText += text[i];
      }

      lastTopOffset = topOffset;
    }
    lines.push( lineText );

    this.html( openTag + lines.join( closeTag + openTag ) + closeTag );
  };

  $(function()
  {
    $('p').wrapLines( '<span class="line">', '</span>' );
  });
span.line {
  display: inline;
}
span.line:hover {
  background-color: lightblue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p style="max-width:400px">
 one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty twenty-one twenty-two twenty-three
</p>
like image 20
Peter Bailey Avatar answered Sep 18 '22 15:09

Peter Bailey