浏览器加载

在浏览器中,JavaScript 执行大量任务可能导致页面卡顿,但这与渲染帧的生成有关,不是直接阻止了渲染帧的渲染,而是由于 JavaScript 的执行阻塞了浏览器主线程,导致页面无法响应用户输入、渲染更新等操作,从而造成了卡顿现象。

浏览器的渲染是多进程的,其中渲染进程主要负责页面的渲染和绘制。但是,JavaScript 的执行是在浏览器的主线程上进行的,这意味着如果 JavaScript 执行时间过长,会阻塞主线程的执行,影响了页面的渲染和响应。

当浏览器遇到需要执行 JavaScript 任务时,会停止当前的渲染工作,转而执行 JavaScript 任务。一旦 JavaScript 任务执行完成,浏览器会继续渲染页面。因此,如果 JavaScript 任务过于耗时,会导致页面在此期间无法正常渲染,用户就会感觉到页面卡顿或者无响应。

为了避免页面卡顿,可以采取以下几种策略:

  1. 分片执行任务:将长时间执行的 JavaScript 任务分解成多个较小的任务,通过定时器或者异步操作分片执行,以避免长时间占用主线程。
  2. Web Worker:将耗时的任务放到 Web Worker 中执行,这样可以在单独的线程中运行 JavaScript 代码,不会阻塞主线程,从而保证页面的流畅度。
  3. 优化代码:优化 JavaScript 代码,减少不必要的计算和循环,尽量减少执行时间。
  4. 利用空闲时间:在页面空闲时执行耗时任务,比如使用 requestIdleCallback API。

通过以上方式,可以有效地减少 JavaScript 执行对页面渲染的影响,提升用户体验。

浏览器原理-渲染帧

在浏览器中,渲染帧(Rendering Frame)是指在屏幕上显示内容的基本单元。当浏览器渲染引擎处理网页内容并将其转换为可视化的图像时,会以帧为单位进行操作。每一帧代表了浏览器在屏幕上渲染一次的过程,包括绘制页面内容、执行 JavaScript、处理用户输入等。

在典型的浏览器渲染流程中,一个渲染帧的处理过程大致包括以下几个步骤:

  1. 构建文档树(DOM Tree)和样式树(CSSOM Tree):浏览器首先解析 HTML 和 CSS 文件,并构建对应的文档树和样式树,用于确定页面结构和样式信息。
  2. 生成渲染树(Render Tree):将文档树和样式树合并生成渲染树,渲染树中包含了需要在屏幕上绘制的所有元素及其样式信息。
  3. 布局(Layout):根据渲染树的信息,浏览器进行布局计算,确定每个元素在屏幕上的位置和大小。
  4. 绘制(Painting):将布局后的元素转换为实际的像素信息,即进行绘制操作。
  5. 合成(Composite):将绘制好的元素组合在一起,形成最终的渲染结果。

以上这些步骤构成了一次渲染帧的完整过程。浏览器通常会以每秒 60 帧的速度进行渲染,也就是每秒将页面渲染成 60 次。这样做是为了保证页面的流畅性和响应速度。

优化渲染帧的处理是提高网页性能的关键之一。通过减少页面重绘、优化布局计算、合理利用硬件加速等手段,可以提高渲染帧的处理效率,从而提升用户体验。

一般情况下,常见的显示器刷新率为 60Hz,也就是每秒刷新 60 次,因此每一帧的时间是大约 16.67 毫秒(1000 毫秒 / 60 帧 ≈ 16.67 毫秒

<!DOCTYPE html>
<html lang="">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=11,IE=10,IE=9,IE=8">
  <meta name="viewport"
        content="width=device-width, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0">
  <meta name="apple-mobile-web-app-title" content="">
  <link rel="stylesheet" id="wpsm_tabs_r_bootstrap-front-css" href="" type="text/css"/>
  <script type="text/javascript" src=""></script>
  <link rel="shortcut icon" href="favicon.ico">
  <meta name="keywords" content="">
  <meta name="description" content="">
  <title></title>
  <style>
    .btn{
      width: 200px;
      display: block;
      text-align: center;
      height: 20px;
      line-height: 20px;
      border-radius: 20px;
      color:white;
      background-color: #0a90eb;
    }
  </style>
</head>
<body>

<a href="javascript:;" class="btn">按钮</a>
<script>
  const datas = new Array(100000).fill(0).map((item, index) => index);

  const btn = document.querySelector('.btn');
  btn.onclick = function () {
    const consumer = (data, index) => {
      const div = document.createElement('div');
      div.textContent = index;
      document.body.appendChild(div);
    };
    const chunkSplitor = task => {
      setTimeout(() => {
        task(time => time < 16);
      }, 30);
    };
    performChunk(datas, consumer, chunkSplitor);
  };


  function performChunk(datas, consumer, chunkSplitor) {
    if (typeof datas === 'number') datas = new Array(datas);
    if (!datas.length) return;
    if (!chunkSplitor && globalThis.requestIdleCallback) {
      chunkSplitor = task => {
        requestIdleCallback(idle => {
          task(() => idle.timeRemaining() > 0);
        });
      };
    }
    let i = 0; // 目前应该取出的任务下标
    // 执行一块任务
    function _run() {
      if (i === datas.length) return;
      chunkSplitor(hasTime => {
        console.log(hasTime);
        const now = Date.now();
        while (hasTime(Date.now() - now) && i < datas.length) {
          // 在这一帧还有空闲时间,且任务还没执行完
          consumer(datas[i], i);
          i++;
        }
        _run();
      });
    }

    _run();
  }
  
</script>
</body>
</html>
Last modification:February 6, 2024
如果觉得我的文章对你有用,请随意赞赏