首页 > 解决方案 > 使用 .cloneContents() 时如何防止标签自动关闭

问题描述

语境

我正在尝试根据文本本身获取所选文本的 HTML。因此,如果将以下 HTML 实现到页面中:

<p>Random content <span>here and other</span> random content here.</p>

它会像这样显示:

Random content here and other random content here.

如果用户选择content here and,我不仅想要获取文本,还想要获取周围的 HTML。我找到了以下功能,它可以有效地做到这一点:

function getSelectionHtml() {

    var html = "";

    if (typeof window.getSelection != "undefined") {

        var sel = window.getSelection();

        if (sel.rangeCount) {

            var container = document.createElement("div");

            for (var i = 0, len = sel.rangeCount; i < len; ++i) {

                container.appendChild(sel.getRangeAt(i).cloneContents());

            }

            html = container.innerHTML;
        }

    } else if (typeof document.selection != "undefined") {

        if (document.selection.type == "Text") {

            html = document.selection.createRange().htmlText;

        }

    }

    return html;
    
}

这样做的结果是content <span>here and </span>。唯一的问题是<span>标签会自动关闭,即使我没有选择结束跨度标签之后的文本。在查看 MDN 文档时.cloneContents(),它说:

部分选择的节点包括使文档片段有效所必需的父标签。

我相信这解释了为什么标签会自行关闭。

问题

如何在没有结束标记的情况下获取选定的文本及其 HTML(除非我也选择了它)?在这种情况下,我如何获取文本content <span> here and而不是content <span>here and </span>?但是,如果我要选择跨度包含的整个语句,我希望将其包括在内。

我尝试过的事情

我尝试使用以下函数切断</span>字符串末尾的 (如果存在),但是当我跨越多个 HTML 标记时它会崩溃。如果我不突出显示它们包含的所有文本,我希望将spanstrongi标签截断。所以,当跨越一个元素时,它可以工作,但是当你跨越多个元素时,它就不行了。即使我将鼠标悬停在跨度包含的整个文本上,它也会切断跨度,因此此解决方案不起作用。

$("p").click(function() {

  if (window.getSelection) {

    sel = window.getSelection();

  } else if (document.selection && document.selection.type != "Control") { //for IE

    sel = document.selection.createRange();

  }     

  var selectedText = getSelectionHTML();

  checkTags(selectedText);

});

function checkTags(selectedText) {

  var prohibited = ['</span>', '</strong>', '</i>'];            

  for (var i = 0; i < prohibited.length; i++) {

    if (selectedText.indexOf(prohibited[i]) > -1) {

      var splitText = selectedText.split("</");
      splitText.pop();

    }

  }

  alert(splitText);

}
  
function getSelectionHTML() {

  var html = "";

  if (typeof window.getSelection != "undefined") {

      var sel = window.getSelection();

      if (sel.rangeCount) {

          var container = document.createElement("div");

          for (var i = 0, len = sel.rangeCount; i < len; ++i) {

              container.appendChild(sel.getRangeAt(i).cloneContents());

          }

          html = container.innerHTML;
      }

  } else if (typeof document.selection != "undefined") {

      if (document.selection.type == "Text") {

          html = document.selection.createRange().htmlText;

      }

  }

  return html;
  
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p>This random content is here <span> within the span <span> <strong>other random content</strong> is here.</p>

标签: javascriptjquery

解决方案


我发现您的问题非常有趣,并且已经进行了一些修改。使用您展示的苗条示例,一切似乎都有效。但是,我不能给出 100% 的保证,因为需要证明的案例太多了。但我希望我能举例说明如何解决这个问题。

也许您尝试我创建的插件并告诉我它是否有效。您可以在jsFiddle和下面的代码的第一个版本中找到小提琴here :

$.fn.selection = function(options) {

  var $el = this,
    lastSelections = [],
    $settings = $.extend({}, $.fn.selection.defaults, options);

  function removeCloseTags(range, selectionText) {
    if (range.endOffset < range.endContainer.length) {
      selectionText = selectionText.replace(
        new RegExp('<\/' + range.endContainer.parentElement.localName + '>(<\/[a-z]+>)*', 'gm'),
        ''
      );
    } else {
      var sibling = range.startContainer.nextSibling;

      if (sibling != null) {
        var siblingName = sibling.localName,
          contents = new RegExp('<' + siblingName + '>(.*)<\/' + siblingName + '>', 'gm').exec(selectionText);

        if (contents != null && typeof contents[1] != 'undefined' && sibling.innerHTML != contents[1]) {
          selectionText = selectionText.replace(
            new RegExp('<\/' + siblingName + '>(<\/[a-z]+>)*', 'gm'),
            ''
          );
        }
      }
    }

    return selectionText;
  }

  function removeOpenTags(element, selectionText) {
    var sibling = element.parentElement;

    if (selectionText.indexOf(sibling.innerHTML) != 0) {
      selectionText = selectionText.replace(
        new RegExp('^(<[a-z]+>)*<' + sibling.localName + '>', 'gm'),
        ''
      );
    }

    return selectionText;
  }

  function getSelectionText(container, selection, range) {
    var selectionText = container.innerHTML;

    container.innerHTML = '';
    container.appendChild(range.cloneContents());
    selectionText = container.innerHTML;

    if ($settings.removeCloseTags) {
      selectionText = removeCloseTags(range, selectionText);
    }

    if ($settings.removeOpenTags) {
      selectionText = removeOpenTags(range.startContainer, selectionText, 0);
    }

    return selectionText;
  }

  function getSelections() {
    lastSelections = [];

    if (typeof window.getSelection != 'undefined') {
      var selection = window.getSelection(),
        container = document.createElement('div');

      if (!selection.isCollapsed && selection.rangeCount) {
        var selectionRangeCount = selection.rangeCount;

        for (var i = 0; i < selectionRangeCount; ++i) {
          lastSelections.push(getSelectionText(container, selection, selection.getRangeAt(i)));
        }
      }
    }
    
    $settings.onSelection.call($el, lastSelections);
    return lastSelections;
  }

  var onMouseUp = function(event) {
    $settings.onMouseUp.call($el, event);
    getSelections();
  };

  var onSelectionChange = function(event) {
    $settings.onSelectionChange.call($el, event);
    getSelections();
  };

  $el.getLastSelections = function() {
    return lastSelections;
  };

  $el.on('mouseup', onMouseUp);
  $(document).on('selectionchange', onSelectionChange);

  var init = function() {
    return $el;
  };

  return init();

};

$.fn.selection.defaults = {
  removeCloseTags: true,
  removeOpenTags: true,
  onMouseUp: function(event) {},
  onSelectionChange: function(event) {},
  onSelection: function(selections) {},
};

$(document).ready(function() {
  var selection = $('body').selection({
    onSelection: function(selections) {
      var selectionString = selections.join()
        .replace(new RegExp('<', 'gm'), '&lt;')
        .replace(new RegExp('>', 'gm'), '&gt;');

      $('pre').html(selectionString);
    }
  });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<p>Random content <span>here <i>and</i> other</span> random content here.</p>
<p>This random content is here <span> within the span </span> <strong>other random content</strong> is here.</p>

<pre></pre>


推荐阅读