js 如何给鼠标滑动选中的文字加样式

有一段文字,我希望鼠标滑动选中的文字可以变成红色(可选择多次,每次的文字都变红),
目前遇到的问题是,如果文字中有重复的内容,无法判断第几个文字应该变红,
尝试了两种方案,但是都失败了,请大神指点!
先上代码:

<!doctype html>
<html lang="zh">
<head>
      <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
</head>
<body>
  <div id="test">
    天气很晴朗, 今天真的很开心,今天真不错
  </div>
</body>
<script>
    $('#test').mouseup(function(){
        var selectObj = window.getSelection();
        var start = selectObj.anchorOffset;
        var end = selectObj.focusOffset;
        var changeText = selectObj.toString();
        var oldHtml= $('#test').html();
        // 方法一
        // var newHtml = oldHtml.replace(changeText, '<span style="color:red">'+changeText+'</span>');
        // $('#test').html(newHtml);
        // 方法二
        var startIndex = start < end ? start : end;
        var endIndex = end > start ? end : start;
        var newHtml = oldHtml.substring(0, startIndex) + '<span style="color:red">' + changeText + '</span>' + oldHtml.substring(endIndex);
        $('#test').html(newHtml);
    })
</script>
</html>

方法1缺陷:我鼠标划选的是第二个“今天”但是高亮的却是第一个"今天"
image.png

方法2缺陷:多次选择的时候,文字会错乱
image.png

阅读 3.8k
2 个回答

为了避免重复选中时无限嵌套的span,下面这种通过替换的实现,只有一层span标签

<!DOCTYPE html>
<html lang="zh">
  <head>
    <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
    <style>
      .selected {
        color: red;
      }
    </style>
  </head>
  <body>
    <div id="test">天气很晴朗,今天真的很开心,今天真不错</div>
  </body>
  <script>
    $('#test').mouseup(function () {
      var selectObj = window.getSelection();
      var start = selectObj.anchorOffset;
      var end = selectObj.focusOffset;
      var startNode = $(selectObj.anchorNode)
      var endNode = $(selectObj.focusNode);
      var left2right = findIndex(startNode[0]) < findIndex(endNode[0]);
      var changeText = selectObj.toString();
      // 寻找索引
      function findIndex(dom) {
        var nodes = $('#test')[0].childNodes
        for (var i = 0; i < nodes.length; i ++) {
          if (dom === nodes[i]) return i
        }
        return -1;
      }
      // 生成span
      function createSelectedSpan(str) {
        var changedSpan = document.createElement('span');
        changedSpan.className = 'selected';
        changedSpan.innerText = str;
        return changedSpan;
      }
      var startText = '';
      var endText = '';
      var span = createSelectedSpan(changeText);
      // 起始和结尾在同一个文本节点的情况
      if (startNode[0] === endNode[0]) {
        left2right = start < end;
        if (!startNode.parent().hasClass('selected')) {
          var text = startNode.text();
          startText = text.substring(0, start);
          endText = text.substring(end);
          if (!left2right) {
            startText = text.substring(0, end);
            endText = text.substring(start);
          }
          startNode.replaceWith(span)
          span.insertAdjacentText('beforebegin', startText)
          span.insertAdjacentText('afterend', endText)
        }
      } else if (startNode.parent().hasClass('selected') && endNode.parent().hasClass('selected')) {
        // 起始和结尾都是在span标签里的情况
          left2right = startNode.parent().index() < endNode.parent().index();
          startText = startNode.text().substring(0, start);
          endText = endNode.text().substring(end);
          if (!left2right) {
            startText = endNode.text().substring(0, end);
            endText = startNode.text().substring(start);
          }
          span = createSelectedSpan(startText + changeText + endText);
          selectObj.deleteFromDocument();
          startNode.parent().remove();
          endNode.parent().replaceWith(span);
      } else if (!startNode.parent().hasClass('selected') && !endNode.parent().hasClass('selected')) {
        // 起始和结尾在不同文本节点的情况
        startText = startNode.text().substring(0, start);
        endText = endNode.text().substring(end);
        if (!left2right) {
          startText = endNode.text().substring(0, end);
          endText = startNode.text().substring(start);
        }
        selectObj.deleteFromDocument();
        startNode.replaceWith(span)
        span.insertAdjacentText('beforebegin', startText)
      } else if (startNode.parent().hasClass('selected')) {
        // 起始在span标签里,结尾在文本里
        left2right = findIndex(startNode[0].parentNode) < findIndex(endNode[0]);
        if (!left2right) {
          startText = document.createTextNode(endNode.text().substring(0, end))
          endText = startNode.text().substring(start)
          selectObj.deleteFromDocument();
          endNode.replaceWith(startText);
          startNode.parent().text(changeText + endText);
        } else {
          startText = startNode.text().substring(0, start)
          endText = document.createTextNode(endNode.text().substring(end))
          selectObj.deleteFromDocument();
          startNode.parent().text(startText + changeText);
          endNode.replaceWith(endText);
        }
      } else if (endNode.parent().hasClass('selected')) {
        // 起始在文本里,结尾在span标签里
        left2right = findIndex(startNode[0]) < findIndex(endNode[0].parentNode);
        if (!left2right) {
          startText = endNode.text().substring(0, end)
          endText = document.createTextNode(startNode.text().substring(start))
          selectObj.deleteFromDocument();
          endNode.parent().text(startText + changeText);
          startNode.replaceWith(endText);
        } else {
          startText = document.createTextNode(startNode.text().substring(0, start))
          endText = endNode.text().substring(end)
          selectObj.deleteFromDocument();
          startNode.replaceWith(startText);
          endNode.parent().text(changeText + endText);
        }
      } else {
        console.log('未知场景')
      }
      // 取消选中
      selectObj.removeAllRanges();
    });
  </script>
</html>
<body>
  <div id="text">
    asndas1<span
      >ndasd
      <span>sdajsdnaksdn</span>
      amsndamnas</span
    >
    1dasdasdas
  </div>

  <script>
    const ele = document.getElementById("text");

    function useMouse() {
      let last = new Date().getTime();
      function mousedown() {
        last = new Date().getTime();
      }

      function getMouseEvent() {
        const d = new Date().getTime() - last;
        return d > 200 ? "select" : "click";
      }
      return {
        mousedown,
        getMouseEvent,
      };
    }

    const { mousedown, getMouseEvent } = useMouse();

    function getNodes(ele, count = { value: 0 }) {
      const list = [];
      const items = ele.childNodes;
      for (let index in items) {
        const _item = items[index];
        if (_item.nodeName === "#text") {
          const item = _item;
          const value = item.nodeValue || "";
          const len = value.length;
          list.push({
            item,
            index: +index,
            len,
            value,
          });
          count.value += len;
        } else {
          list.push(...getNodes(_item, count));
        }
      }
      return list;
    }

    function getSpan(text) {
      const span = document.createElement("span");
      span.style.color = "#f00";
      // span.style.display="inline-block";
      // span.innerText = text.replace(/\n/g, "-");
      /** 换行符处理 ? */
      span.innerText = text.replace(/\n/g, "");
      return span;
    }

    function getText(text) {
      return document.createTextNode(text);
    }

    function mouseup() {
      if (getMouseEvent() !== "select") {
        return;
      }
      const e = window.getSelection();
      if (e && e.type === "Range") {
        try {
          const { endContainer, startContainer, startOffset, endOffset } =
            e.getRangeAt(0);
          const nodes = getNodes(ele);

          const start = nodes.findIndex((x) => x.item === startContainer);
          const end = nodes.findIndex((x) => x.item === endContainer);

          if (start > -1 && end > -1) {
            if (start === end) {
              const { item, value } = nodes[start];
              const left = getText(value.slice(0, startOffset));
              const center = getSpan(value.slice(startOffset, endOffset));
              const right = getText(value.slice(endOffset));
              item.parentNode?.insertBefore(left, item);
              item.parentNode?.insertBefore(center, item);
              item.parentNode?.insertBefore(right, item);
              item.parentNode?.removeChild(item);
            } else {
              for (let i = start; i <= end; i++) {
                const { item, value } = nodes[i];
                if (i === start) {
                  const left = getText(value.slice(0, startOffset));
                  const right = getSpan(value.slice(startOffset));
                  item.parentNode?.insertBefore(left, item);
                  item.parentNode?.insertBefore(right, item);
                  item.parentNode?.removeChild(item);
                } else if (i === end) {
                  const left = getSpan(value.slice(0, endOffset));
                  const right = getText(value.slice(endOffset));
                  item.parentNode?.insertBefore(left, item);
                  item.parentNode?.insertBefore(right, item);
                  item.parentNode?.removeChild(item);
                } else {
                  item.parentNode?.replaceChild(getSpan(value), item);
                }
              }
            }
          }
        } catch (error) {
          console.error(error);
        } finally {
          e.removeAllRanges();
        }
      }
    }

    ele.addEventListener("mousedown", mousedown);
    ele.addEventListener("mouseup", mouseup);
  </script>
</body>
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题