How can I highlight a word/term quicker and smarter?

后端 未结 6 751
误落风尘
误落风尘 2021-02-05 06:27

I have some text:

Hello world, Attack on Titan season two!

Currently, if a user wants to highlight a word/ter

相关标签:
6条回答
  • 2021-02-05 06:52

    You can do that with pure JS using Range and selectionRange objects.

    HTML:

    <div id="selectable">
      <p>Hello world, <b>Attack on Titan</b> season two!</p>
      <p>Another paragraph with sample text.</p>
    </div>
    <div id="notSelectable">
      <p>The selection will behave normally on this div.</p>
    </div>
    

    JS:

    (function(el){
        el.addEventListener('mouseup',function(evt){
            if (document.createRange) { // Works on all browsers, including IE 9+
                var selected = window.getSelection();
                /* if(selected.toString().length){ */
                    var d = document,
                        nA = selected.anchorNode,
                        oA = selected.anchorOffset,
                        nF = selected.focusNode,
                        oF = selected.focusOffset,
                        range = d.createRange();
    
                    range.setStart(nA,oA);
                    range.setEnd(nF,oF);
    
                    // Check if direction of selection is right to left
                    if(range.startContainer !== nA || (nA === nF && oF < oA)){
                        range.setStart(nF,oF);
                        range.setEnd(nA,oA);
                    }
    
                    // Extend range to the next space or end of node
                    while(range.endOffset < range.endContainer.textContent.length && !/\s$/.test(range.toString())){
                        range.setEnd(range.endContainer, range.endOffset + 1);
                    }
                    // Extend range to the previous space or start of node
                    while(range.startOffset > 0 && !/^\s/.test(range.toString())){
                        range.setStart(range.startContainer, range.startOffset - 1);
                    }
    
                    // Remove spaces
                    if(/\s$/.test(range.toString()) && range.endOffset > 0)
                        range.setEnd(range.endContainer, range.endOffset - 1);
                    if(/^\s/.test(range.toString()))
                        range.setStart(range.startContainer, range.startOffset + 1);
    
                    // Assign range to selection
                    selected.addRange(range);
                /* } */
            } else { 
               // Fallback for Internet Explorer 8 and earlier
               // (if you think it still is worth the effort of course)
            }
    
            // Stop Moz user select
            el.style.MozUserSelect = '-moz-none';
        });
    
        /* This part is added to eliminate a FF specific dragging behavior */
        el.addEventListener('mousedown',function(){
            if (window.getSelection) {  // Works on all browsers, including IE 9+
               var selection = window.getSelection ();
               selection.collapse (selection.anchorNode, selection.anchorOffset);
            } else {
               // Fallback for Internet Explorer 8 and earlier
               // (if you think it still is worth the effort of course)
            }
    
            // Add Moz user select back
            el.style.MozUserSelect = 'text';
        });
    })(document.getElementById('selectable'));
    

    Please check the working example here.

    UPDATES:

    • Whole word selection on click added
    • Fix for Firefox specific drag behavior added

    Updated JSFiddle here.

    0 讨论(0)
  • 2021-02-05 07:01

    You cannot set events to Text, but can set events to Html Elements. Put each word inside a div element and add an event onmouseover that changes div to a new highlighted state using css.

    Steps:

    1. Use split to get words into array.
    2. Iterate words and put it into a div.
    3. Iterate divs and set it an event that's change the div to a css class .highlight.

    That's it.

    0 讨论(0)
  • 2021-02-05 07:05

    So you are going to have to deal with text ranges and such. I've dealt with this, and it's extremely painful, especially if you have DOM contents like:

    <p>New season of <span class="redtext">Attack on Titan!</span></p>
    

    i.e. text nodes mixed with other DOM elements, such as a span in this case. With this in mind, I'd like to highly recommend the library rangy.js: https://github.com/timdown/rangy

    It's saved me several days of headache when I was making a hashtag highlighting system.

    0 讨论(0)
  • 2021-02-05 07:08

    Using node.textContent we can find spaces and jump our selection to whole words without the need for creating new elements.

    Mainly for my potential future use I've written this fairly modular, it also doesn't require a mouseup on the watched element, can deal with an element collection and also makes the selection changes if the user makes their selection using their keyboard.

    var WordJumpSelection = (function() {
        var watchList = [];
        var WordJumpSelection = {
            stopWatching: function(elem) {
                var wlIdx = watchList.indexOf(elem);
                if(wlIdx > -1) watchList.splice(wlIdx,1);
            },
            watch: function(elem) {
                var elems = Array.prototype.slice.call(typeof elem.length === "number" ? elem : arguments);
                if(watchList.length === 0)
                {
                    WordJumpSelection.init();
                }
                elems.forEach(function(elem) {
                    if(watchList.indexOf(elem) === -1)
                    {
                        watchList.push(elem);
                    }
                });
            },
            init: function() {
                function handleSelectionChange() {
                    if(watchList.length === 0) return;
                    var selection = window.getSelection();
                    var selDir = getSelectionDir(selection);
                    var startNode,endNode,startPos,endPos;
                    if(selDir === 1)
                    {
                        startNode = selection.anchorNode;
                        endNode = selection.focusNode;
                        startPos = selection.anchorOffset;
                        endPos = selection.focusOffset;
                    }
                    else
                    {
                        startNode = selection.focusNode;
                        endNode = selection.anchorNode;
                        startPos = selection.focusOffset;
                        endPos = selection.anchorOffset;
                    }
                    var rangeStart = textNodeIsWatched(startNode) ? roundSelectionIndex(startNode,0,startPos) : startPos-1;
                    var rangeEnd = textNodeIsWatched(endNode) ? roundSelectionIndex(endNode,1,endPos) : endPos;
                    var r = document.createRange();
                    r.setStart(startNode,rangeStart+1)
                    r.setEnd(endNode,rangeEnd)
                    selection.removeAllRanges();
                    selection.addRange(r);
                }
                document.documentElement.addEventListener('mouseup', handleSelectionChange);
                document.documentElement.addEventListener('keyup', function(e) {
                    if(e.keyCode === 16)
                    {
                        handleSelectionChange();
                    }
                });
                WordJumpSelection.init = function(){};
            }
        };
        return WordJumpSelection;
    
        function getSelectionDir(sel) {
            var range = document.createRange();
            range.setStart(sel.anchorNode,sel.anchorOffset);
            range.setEnd(sel.focusNode,sel.focusOffset);
            if(range.startContainer !== sel.anchorNode || (sel.anchorNode === sel.focusNode && sel.focusOffset < sel.anchorOffset)) return -1;
            else return 1;
        }
        function roundSelectionIndex(textNode,nodeId,idx) {
            var isStart = nodeId === 0;
            var contents = textNode.textContent;
            var nearestSpaceIdx = -1;
            if(isStart)
            {
                nearestSpaceIdx = contents.lastIndexOf(' ',idx);
                if(nearestSpaceIdx === -1) nearestSpaceIdx = -1;
            }
            else
            {
                nearestSpaceIdx = contents.indexOf(' ',idx);
                if(nearestSpaceIdx === -1) nearestSpaceIdx = contents.length;
            }
            return nearestSpaceIdx;
        }
        function textNodeIsWatched(textNode) {
            return watchList.indexOf(textNode.parentElement) > -1;
        }
    })();
    

    An example jsFiddle

    I am yet to test how this works on mobile, and haven't got it working live yet - but it might be a good start.

    Update: Now selects word with a single click

    0 讨论(0)
  • 2021-02-05 07:08

    This text is a text node, and text nodes simply don't fire most events. But they can fire DOM mutation events, for example DOMCharacterDataModified, which is used to detect change to a text node's text:

    var textNode = document.getElementsByClassName("drag")[0].firstChild;
    
    textNode.addEventListener("DOMCharacterDataModified", function(e) {
        console.log("Text changed from '" + e.prevValue + "' to '" + evt.newValue +"'");
    }, false);
    

    However, the text in <p class="drag">Hello world, Attack on Titan season two!</p> is a single text node and you need every word to be a separate node.

    The only solution I see is to put every word in a span tag. You can't do this with pure text.

    Edit

    Here's an example how to do this with span tags (I'm using jQuery here just to reduce the code amount, it's not necessary):

    $(function() {
        $('.drag').on('click', 'span', function() {
            var range;
            if (document.selection) {
                range = document.body.createTextRange();
                range.moveToElementText($(this)[0]);
                range.select();
            } 
            else if (window.getSelection) {
                range = document.createRange();
                range.selectNode($(this)[0]);
                window.getSelection().addRange(range);
            }
        });  
    });
    

    Here's an example on JS Bin

    Update

    I edited the code snippet so the selection behaves like you asked (currently it's only works for selections from left to right, not reverse selections):

    $(function(){
    
      var range = document.createRange();
      var selectionMode = false;
    
      $(document).on('mouseup', function() {
        selectionMode = false;
      })
      .on('mousedown', '.drag', function(e) {
        selectionMode = true;
      })
      .on('dragstart', '.drag span', function(e) {
        return false;
      });
    
      $('.drag').on('mousedown', 'span', function() {
        range.setStartBefore($(this)[0]);
        range.setEndAfter($(this)[0]);
        window.getSelection().addRange(range);
      })
      .on('mousemove', 'span', function() {
        if (!selectionMode) {
          return;
        }
        range.setEndAfter($(this)[0]);
        window.getSelection().addRange(range);
      })
      .on('mouseup', 'span', function() {
        setTimeout(function(){
          window.getSelection().addRange(range);
        }, 1);
      });  
    
    });
    

    You can read more about HTML Range API here: https://developer.mozilla.org/en-US/docs/Web/API/Range

    0 讨论(0)
  • 2021-02-05 07:15

    Concepts

    To select each word, there are something you must keep in mind first:

    1. textNode is a single sting contains all the words, you won't be able to select each "word", since it's not a DOM node.

    2. There is no specific event triggered in browser when you "drag and select" a word. However, when you drag & select, there are 2 events get fired: mouseover is triggered when you move your mouse, click is triggered when you release your mouse button. (This is true even in Mac's touchpad).

    3. There are different implementation on "highlight" when you select a word.

    Steps

    Based on the concepts, you have to do the following steps sequentially to achieve your goal:

    1. Get the words in paragraph and wrapped them with tag (e.g. <span>) for DOM selection
    2. When the click event is triggered (which indicate your select has ended), highlight the word you just select.

    The implementation would be something likes this (with jQuery). And you may see the live demo here:

    $(function() {
    
      // 1. When mouseover the paragraph, wrapped each word with <span>
      $('p').one('mouseover', function(event) {
        $('p').html(function(index, text) {
          var wordsArray = text.split(' ');
    
          var wrappedArray = wordsArray.map(function(val, index) {
            val = '<span class="chunk-' + index + '">' + val + '</span>';
            return val;
          });
    
          var wrappedString = wrappedArray.join(' ');
    
          // 2. Replace the paragraph with wrapped text
          $(this).html(wrappedString);
    
          // 3. When the word is select, highlight the word
          $(this).children('span').on('click', function() {
            var selector = '.' + $(this).attr('class');
            SelectText(selector);
          });
        });
      });
    });
    
    
    function SelectText(element) {
      var doc = document,
        text = doc.querySelector(element),
        range, selection;
      if (doc.body.createTextRange) {
        range = document.body.createTextRange();
        range.moveToElementText(text);
        range.select();
      } else if (window.getSelection) {
        selection = window.getSelection();
        range = document.createRange();
        range.selectNodeContents(text);
        selection.removeAllRanges();
        selection.addRange(range);
      }
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Autem amet suscipit incidunt placeat dicta iure, perspiciatis libero nobis dolore, temporibus et! Quae fugiat necessitatibus ut, molestias aut. Sequi rerum earum facilis voluptates ratione architecto
      officia quod aut unde voluptas? Dignissimos ducimus exercitationem perspiciatis nam numquam minima accusamus quod necessitatibus amet illo vel vero placeat voluptate eos iste ratione veniam quisquam atque non voluptatum sint hic sed, suscipit. Doloremque
      officiis rerum sunt delectus unde odit eos quod earum aspernatur, tempora neque modi tempore minima maiores fuga eaque dolore quos minus veritatis aliquid, vel suscipit dolores. Voluptatem eius obcaecati, laborum ipsa a!</p>

    SelectText function should attribute to @Jason in this post on SO: Selecting text in an element: akin to highlighting with your mouse

    0 讨论(0)
提交回复
热议问题