如何在 DOM 元素从网络流入时附加它们?
How can I append DOM elements as they stream in from the network?
我有一个元素,我想将其填充为请求的 HTML 流,而不是等待完整的响应。事实证明这非常困难。
我尝试过的事情:
1。 milestoneNode.insertAdjacentHTML('beforebegin', text)
如果这有效,那就太可爱了。不幸的是,具有古怪解析的元素破坏了它——例如 <p>
和 <table>
。由此产生的 DOM 可以仁慈地描述为达达。
2。使用虚拟 DOM/DOM 更新管理库
Google的incremental-dom
看起来最有希望,但它的patch()
操作总是从容器节点的开头重新启动。不确定如何 "freeze" 它就位。
这也有在 JavaScript 中至少进行 HTML 标记化的包袱,并且 一些 必须进行实际的树构建,除非有人服务格式正确的 XHTML5。 (没有人这样做。)重新实现 浏览器的 HTML 解析器似乎表明我犯了严重错误。
3。 document.write()
我很绝望。具有讽刺意味的是,这个古老的恶魔 几乎 我需要的行为,没有 "throwing away the existing page" 的东西。
4。追加到一个字符串,然后 innerHTML
定期
击败流式传输点,因为最终整个响应都保存在内存中。也有重复的序列化开销。
从好的方面来说,它确实有效。但肯定有更好的方法吗?
您可以使用 fetch()
,处理 Response.body
这是一个 ReadableStream
; TextDecoder()
let decoder = new TextDecoder();
function handleResponse(result) {
element.innerHTML += decoder.decode(result.value);
return result
}
fetch("/path/to/resource/")
.then(response => response.body.getReader())
.then(reader => {
return reader.read().then(function process(result) {
if (result.done) {
console.log("stream done");
return reader.closed;
}
return reader.read().then(handleResponse).then(process)
})
.then(function() {
console.log("stream complete", element.innerHTML);
})
.catch(function(err) {
console.log(err)
})
});
Jake Archibald figured out a silly hack to get this behavior in browsers today。他的示例代码比我说的更好:
// Create an iframe:
const iframe = document.createElement('iframe');
// Put it in the document (but hidden):
iframe.style.display = 'none';
document.body.appendChild(iframe);
// Wait for the iframe to be ready:
iframe.onload = () => {
// Ignore further load events:
iframe.onload = null;
// Write a dummy tag:
iframe.contentDocument.write('<streaming-element>');
// Get a reference to that element:
const streamingElement = iframe.contentDocument.querySelector('streaming-element');
// Pull it out of the iframe & into the parent document:
document.body.appendChild(streamingElement);
// Write some more content - this should be done async:
iframe.contentDocument.write('<p>Hello!</p>');
// Keep writing content like above, and then when we're done:
iframe.contentDocument.write('</streaming-element>');
iframe.contentDocument.close();
};
// Initialise the iframe
iframe.src = '';
Although <p>Hello!</p>
is written to the iframe, it appears in the parent document! This is because the parser maintains a stack of open elements, which newly created elements are inserted into. It doesn't matter that we moved <streaming-element>
, it just works.
我有一个元素,我想将其填充为请求的 HTML 流,而不是等待完整的响应。事实证明这非常困难。
我尝试过的事情:
1。 milestoneNode.insertAdjacentHTML('beforebegin', text)
如果这有效,那就太可爱了。不幸的是,具有古怪解析的元素破坏了它——例如 <p>
和 <table>
。由此产生的 DOM 可以仁慈地描述为达达。
2。使用虚拟 DOM/DOM 更新管理库
Google的incremental-dom
看起来最有希望,但它的patch()
操作总是从容器节点的开头重新启动。不确定如何 "freeze" 它就位。
这也有在 JavaScript 中至少进行 HTML 标记化的包袱,并且 一些 必须进行实际的树构建,除非有人服务格式正确的 XHTML5。 (没有人这样做。)重新实现 浏览器的 HTML 解析器似乎表明我犯了严重错误。
3。 document.write()
我很绝望。具有讽刺意味的是,这个古老的恶魔 几乎 我需要的行为,没有 "throwing away the existing page" 的东西。
4。追加到一个字符串,然后 innerHTML
定期
击败流式传输点,因为最终整个响应都保存在内存中。也有重复的序列化开销。
从好的方面来说,它确实有效。但肯定有更好的方法吗?
您可以使用 fetch()
,处理 Response.body
这是一个 ReadableStream
; TextDecoder()
let decoder = new TextDecoder();
function handleResponse(result) {
element.innerHTML += decoder.decode(result.value);
return result
}
fetch("/path/to/resource/")
.then(response => response.body.getReader())
.then(reader => {
return reader.read().then(function process(result) {
if (result.done) {
console.log("stream done");
return reader.closed;
}
return reader.read().then(handleResponse).then(process)
})
.then(function() {
console.log("stream complete", element.innerHTML);
})
.catch(function(err) {
console.log(err)
})
});
Jake Archibald figured out a silly hack to get this behavior in browsers today。他的示例代码比我说的更好:
// Create an iframe:
const iframe = document.createElement('iframe');
// Put it in the document (but hidden):
iframe.style.display = 'none';
document.body.appendChild(iframe);
// Wait for the iframe to be ready:
iframe.onload = () => {
// Ignore further load events:
iframe.onload = null;
// Write a dummy tag:
iframe.contentDocument.write('<streaming-element>');
// Get a reference to that element:
const streamingElement = iframe.contentDocument.querySelector('streaming-element');
// Pull it out of the iframe & into the parent document:
document.body.appendChild(streamingElement);
// Write some more content - this should be done async:
iframe.contentDocument.write('<p>Hello!</p>');
// Keep writing content like above, and then when we're done:
iframe.contentDocument.write('</streaming-element>');
iframe.contentDocument.close();
};
// Initialise the iframe
iframe.src = '';
Although
<p>Hello!</p>
is written to the iframe, it appears in the parent document! This is because the parser maintains a stack of open elements, which newly created elements are inserted into. It doesn't matter that we moved<streaming-element>
, it just works.