javascript - 使用 .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 标记时它会崩溃。如果我不突出显示它们包含的所有文本,我希望将span
、strong
和i
标签截断。所以,当跨越一个元素时,它可以工作,但是当你跨越多个元素时,它就不行了。即使我将鼠标悬停在跨度包含的整个文本上,它也会切断跨度,因此此解决方案不起作用。
$("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>
解决方案
我发现您的问题非常有趣,并且已经进行了一些修改。使用您展示的苗条示例,一切似乎都有效。但是,我不能给出 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'), '<')
.replace(new RegExp('>', 'gm'), '>');
$('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>
推荐阅读
- mysql - 如果密钥对存在于另一个表中,则选择列为真/假
- c++ - Boost 1.69 中的某些库是否与 MacOS 不兼容?
- python-3.x - PyQt5 & PySide2 / 无法在“”中加载 Qt 平台插件“windows”,即使它被发现
- python - 在 Matplotlib 中为 Boxplot 提供自定义四分位数范围
- couchdb - 自定义 CouchDb SSL 证书验证
- reactjs - 理解组件反应
- python - 如何在 Keras 中实现 CRelu?
- c# - 使用 html 敏捷包提取 html 元素属性值
- amazon-web-services - 根据数据类型将来自 AWS Kinesis 的数据放入不同的存储桶中
- laravel - 将数据传递给 laravel 自定义验证规则