31

有没有办法在 W3C 兼容的浏览器中获取 JavaScript 范围对象的 html 字符串?

例如,假设用户选择以下内容:Hello <b>World</b>
可以使用该方法将“Hello World”作为字符串获取Range.toString()。(在 Firefox 中,也可以使用documentgetSelection方法。)

但我似乎无法找到获取内部 HTML 的方法。

经过一番搜索,我发现范围可以转换为DocumentFragment对象。

但是DocumentFragments没有innerHTML属性(至少在 Firefox 中;没有尝试过 Webkit 或 Opera)。
这对我来说似乎很奇怪:很明显应该有一些方法可以访问选定的项目。

我意识到我可以创建一个documentFragment,将文档片段附加到另一个元素,然后获取该innerHTML元素的。
但是该方法会自动关闭我选择的区域内的所有打开标签。
除此之外,肯定有一个明显的“更好的方法”,而不是将它附加到 dom 只是为了将它作为一个字符串。

那么,如何获取 Range 或 DocFrag 的 html 字符串呢?

4

6 回答 6

17

FWIW,jQuery 方式:

$('<div>').append(fragment).html()
于 2014-07-17T18:18:21.723 回答
15

从这里拼出一个例子:

//Example setup of a fragment 
var frag = document.createDocumentFragment(); //make your fragment 
var p = document.createElement('p'); //create <p>test</p> DOM node
p.textContent = 'test';
frag.appendChild( p  ); 

//Outputting the fragment content using a throwaway intermediary DOM element (div):
var div = document.createElement('div');
div.appendChild( frag.cloneNode(true) );
console.log(div.innerHTML); //output should be '<p>test</p>'
于 2014-07-10T19:20:44.787 回答
13

那么,如何获取 Range 或 DocFrag 的 html 字符串呢?

与其他响应相反,可以使用https://w3c.github.io/DOM-Parsing/#the-xmlserializer-interface中描述的方法直接将DocumentFragment对象转换为 a 。DOMStringXMLSerializer.prototype.serializeToString

要获取对象DOMStringRange,只需DocumentFragment使用Range.prototype.cloneContentsRange.prototype.extractContents方法将其转换为 a ,然后按照DocumentFragment对象的过程进行操作。

我附上了一个演示,但它的要点在于这两行:

const serializer = new XMLSerializer();
const document_fragment_string = serializer.serializeToString(document_fragment);

(() => {
	"use strict";
	const HTML_namespace = "http://www.w3.org/1999/xhtml";
	document.addEventListener("DOMContentLoaded", () => {
		/* Create Hypothetical User Range: */
		const selection = document.defaultView.getSelection();
		const user_range_paragraph = document.getElementById("paragraph");
		const user_range = document.createRange();
		user_range.setStart(user_range_paragraph.firstChild, 0);
		user_range.setEnd(user_range_paragraph.lastChild, user_range_paragraph.lastChild.length || user_range_paragraph.lastChild.childNodes.length);
		selection.addRange(user_range);

		/* Clone Hypothetical User Range: */
		user_range.setStart(selection.anchorNode, selection.anchorOffset);
		user_range.setEnd(selection.focusNode, selection.focusOffset);
		const document_fragment = user_range.cloneContents();

		/* Serialize the User Range to a String: */
		const serializer = new XMLSerializer();
		const document_fragment_string = serializer.serializeToString(document_fragment);

		/* Output the Serialized User Range: */
		const output_paragraph = document.createElementNS(HTML_namespace, "p");
		const output_paragraph_code = document.createElementNS(HTML_namespace, "code");
		output_paragraph_code.append(document_fragment_string);
		output_paragraph.append(output_paragraph_code);
		document.body.append(output_paragraph);
	}, { "once": true });
})();
<p id="paragraph">Hello <b>World</b></p>

于 2018-04-18T08:37:33.097 回答
5

不,这是唯一的方法。大约 10 年前的 DOM Level 2 规范几乎没有在 HTML 文本之间序列化和反序列化节点方面,所以你不得不依赖像innerHTML.

关于你的评论

但是该方法会自动关闭我选择的区域内的所有打开标签。

......它还能如何工作?DOM 由排列在树中的节点组成。从 DOM 复制内容只能创建另一个节点树。元素节点在 HTML 中由开始标签和有时结束标签分隔。需要结束标记的元素的 HTML 表示必须具有结束标记,否则它不是有效的 HTML。

于 2011-02-04T11:02:37.413 回答
5

另一种方法是迭代子节点:

Array.prototype.reduce.call(
    documentFragment.childNodes, 
    (result, node) => result + (node.outerHTML || node.nodeValue),
    ''
);

不适用于内联 SVG,但可以做一些事情来让它工作。如果您需要对节点进行一些链式操作并因此获得 html 字符串,它也会有所帮助。

于 2019-02-01T06:36:56.863 回答
-1

DocumentFragment.textContent 可以为您提供所需的东西吗?

var frag = document.createRange().createContextualFragment("Hello <b>World</b>.");

console.log(frag.textContent)

于 2018-08-31T13:14:16.693 回答