是否可以指示浏览器在异步 DOM 操作期间不要渲染/重排/更新视口?

Posted

技术标签:

【中文标题】是否可以指示浏览器在异步 DOM 操作期间不要渲染/重排/更新视口?【英文标题】:Is it possible to instruct the browser not to render/reflow/update the viewport during asynchronous DOM manipulations? 【发布时间】:2019-05-16 19:12:58 【问题描述】:

我有这样的事情:

async function async_DOM_manipulations()
    await DOM_manipulation_1() ;
    await DOM_manipulation_2() ;
    await DOM_manipulation_3() ;

浏览器(我使用的是 Chrome)坚持在异步 DOM_manipulation_x 函数之间呈现页面。

但这看起来不太好。用户所看到的只是 GUI 在几分之一秒内如何在几个步骤中发生变化。

是否可以在这个过程中以某种方式指示浏览器停止渲染页面?

【问题讨论】:

@Andy,很难给出这个问题的工作示例。我将不得不发布太多代码。问题很简单。浏览器是否提供了一种机制来控制何时呈现页面? 我只是将子元素添加到父容器并设置一些类名。 是的。在新的断开/隐藏元素中执行 DOM 操作,然后用新元素替换旧元素。从而避免中间回流。但我要问的问题不同而且简单得多。浏览器是否允许开发人员决定何时更新视口,或者我们被浏览器自动做出的决定所困扰,我们必须解决它们。我隐约记得读过一些关于 API 的东西来做到这一点。这就是我问这个问题的原因。 【参考方案1】:

我认为,根据我的测试,这是不可能的。我能看到的唯一一种方法是为某个内部结构准备一批更新,并在所有异步操作结束时立即更新 DOM。

所以(TypeScript)代码看起来像

interface DOMOperation 
   node: Node,
   operation: (...args: any) => void;
   args: any[];


export class DOMBatch 

   private queue: DOMOperation[] = [];
   private updateRunning: boolean = false;

   public async beginUpdate(): Promise<void> 

      return new Promise(

         (resolve) => 

            const checkUpdateRunning = () => 
               if (this.updateRunning) 
                  setTimeout(() =>  checkUpdateRunning() , 0); // each new javascript loop check if the update is running
                else 
                  this.queue = [];
                  resolve();
               
            

            checkUpdateRunning();

         

      )

   

   public endUpdate() 

      for(const op of this.queue) 
         op.operation.apply(op.node, op.args);
      

      this.updateRunning = false;

   

   public appendChild(target: Node, childNode: Node) 
      this.queue.push( node: target, operation: target.appendChild, args: [ childNode ] );
   

   public removeChild(target: Node, childNode: Node) 
      this.queue.push( node: target, operation: target.removeChild, args: [ childNode ]);
   

   public insertBefore(target: Node, toInsert: Node, before: Node) 
      this.queue.push( node: target, operation: target.insertBefore, args: [ toInsert, before ] );
   

   public setAttribute(target: Element, name: string, value: string) 
      this.queue.push( node: target, operation: target.setAttribute, args: [ name, value] );
   

   public removeAttribute(target: Element, name: string) 
      this.queue.push( node: target, operation: target.attributes.removeNamedItem, args: [ name ] );
   




...
const domBatch = new DOMBatch();
...

async function async_DOM_manipulations(): Promise<void> 
    await domBatch.beginUpdate(); // cleanup the queue and wait for other possible renders running
    await prepare_DOM_manipulation_1(); // fill the batch with updates
    await prepare_DOM_manipulation_2(); // fill the batch with updates
    await prepare_DOM_manipulation_3(); // fill the batch with updates
    domBatch.endUpdate(); // render the batch

必须确保在异步任务和最终 updateDOM 之间没有进行另一个异步 DOM 操作,因此对 beginUpdate() 的调用应该返回承诺

【讨论】:

以上是关于是否可以指示浏览器在异步 DOM 操作期间不要渲染/重排/更新视口?的主要内容,如果未能解决你的问题,请参考以下文章

javascript 异步解析

pdfjs获取渲染结束

script标签的async属性是用来异步加载,异步加载的作用是否同时下载,执行html代码和js代码

script标签的async属性是用来异步加载,异步加载的作用是否同时下载,执行html代码和js代码

最小化浏览器重排/重新渲染

为什么小程序不提供 DOM 接口