【问题标题】:How can I append DOM elements as they stream in from the network?当 DOM 元素从网络流入时,如何附加它们?
【发布时间】:2016-11-19 16:25:48
【问题描述】:

我有一个要填充为请求的 HTML 流的元素,而不是等待完整的响应。事实证明,这非常困难。

我尝试过的事情:

1。 milestoneNode.insertAdjacentHTML('beforebegin', text)

如果这行得通,那就可爱吧。不幸的是,具有古怪解析的元素破坏了它——例如<p><table>。生成的 DOM 可以被描述为 Dada。

2。使用虚拟 DOM/DOM 更新管理库

Google 的incremental-dom 似乎最有希望,但它的patch() 操作总是从容器节点的开头重新启动。不知道如何将其“冻结”到位。

这也带来了至少在 JavaScript 中进行 HTML 标记化的包袱,并且 一些 必须进行实际的树构建,除非提供格式良好的 XHTML5。 (没有人这样做。)重新实现 浏览器的 HTML 解析器似乎是我大错特错的迹象。

3。 document.write()

我很绝望。具有讽刺意味的是,这个古老的妖怪几乎具有我需要的行为,没有“丢弃现有页面”的事情。

4。附加到一个字符串,然后定期innerHTMLing

打败了流式传输的要点,因为最终整个响应都保存在内存中。也有重复的序列化开销。

从好的方面来说,它确实有效。但肯定有更好的方法吗?

【问题讨论】:

  • " as a request's HTML streams in" 当前如何发送和接收流?
  • @guest271314 作为真正的 Service Worker 流,用postMessage() 吐到页面上

标签: javascript html dom streaming service-worker


【解决方案1】:

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 = '';

虽然&lt;p&gt;Hello!&lt;/p&gt;被写入iframe,但它出现在父文档中!这是因为解析器维护了一堆打开的元素,新创建的元素被插入其中。我们移动 &lt;streaming-element&gt; 没关系,它可以正常工作。

【讨论】:

    【解决方案2】:

    您可以使用fetch(),处理Response.body,这是一个ReadableStreamTextDecoder()

    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)
      })
    });
    

    【讨论】:

    • 是的,到此为止。然后我将它附加到一个元素而不重新加载页面。
    • 如果您使用ServiceWorker,您可以使用.respondWith Response 而不是postMessage,尽管从fetch 处理Response 应该返回相同的结果
    • 我试图避免.respondWith 附带的整页导航。我知道这很奇怪,但这是为了科学。
    • 您可以使用fetch()Response.body.getReader()TextDecoder,查看更新后的帖子
    • 不幸的是,这会破坏 DOM 以获取类似于尝试 #1 的古怪解析元素,并使用画家算法在每次更新时重新序列化整个元素。不过,它肯定比我做的一些简单。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-24
    • 1970-01-01
    • 2019-09-07
    • 2019-09-09
    • 2020-11-25
    • 2017-12-29
    相关资源
    最近更新 更多