性能优化(2)-渲染优化

news2024/11/18 21:44:20

一、渲染优化

如果把浏览器呈现页面的整个过程一分为二,前面所讲的主要是浏览器为呈现页面请求所需资源的部分;本章将主要关注浏览器获取到资源后,进行渲染部分的相关优化内容。

在前面的前端页面的生命周期课程中,介绍过关键渲染路径的概念,浏览器通过这个过程对HTML,CSS, JavaScript等资源文件进行解析,然后组织渲染出最终的页面。本章将以此为基础,对渲染过程进行更深入的讨论,不仅包括打开一个网站的首次渲染,还有用户与页面进行交互后导致页面更改的渲染,即所谓的重绘与重排。其中除了对渲染过程的充分介绍,更重要的是对提升渲染过程性能的优化手段的探讨。

浏览器从获取 HTML 到最终在屏幕上显示内容需要完成以下步骤

  1. 处理 HTML 标记并构建 DOM 树。
  2. 处理 CSS 标记并构建 CSSOM 树。
  3. 将 DOM 与 CSSOM 合并成一个 render tree。
  4. 根据渲染树来布局,以计算每个节点的几何信息。
  5. 将各个节点绘制到屏幕上。

经过以上整个流程我们才能看见屏幕上出现渲染的内容,优化关键渲染路径就是指最大限度缩短执行上述第1步至第5步耗费的总时间,让用户最快的看到首次渲染的内容。

不但网站页面要快速加载出来,而且运行过程也应更顺畅,在响应用户操作时也要更加及时,比如我们通常使用手机浏览网上商城时,指尖滑动屏幕与页面滚动应很流畅,拒绝卡顿。那么要达到怎样的性能指标,才能满足用户流畅的使用体验呢?

目前大部分设备的屏幕分辨率都在60fps左右,也就是每秒屏幕会刷新60次,所以要满足用户的体验期望,就需要浏览器在渲染页面动画或响应用户操作时,每一帧的生成速率尽量接近屏幕的刷新率。若按照60fps来算,则留给每一帧画面的时间不到17ms,再除去浏览器对资源的一些整理工作,一帧画面的渲染应尽量在10ms内完成,如果达不到要求而导致帧率下降,则屏幕上的内容会发生抖动或卡顿。

为了使每一帧页面渲染的开销都能在期望的时间范围内完成,就需要开发者了解渲染过程的每个阶段,以及各阶段中有哪些优化空间是我们力所能及的。经过分析根据开发者对优化渲染过程的控制力度,可以大体将其划分为五个部分: JavaScript处理、计算样式、页面布局、绘制与合成,下面先简要介绍各部分的功能与作用。

在这里插入图片描述

  • JavaScript处理:前端项目中经常会需要响应用户操作,通过JavaScript对数据集进行计算、操作DOM元素,并展示动画等视觉效果。当然对于动画的实现,除了JavaScript,也可以考虑使用如CSS Animations、Transitions等技术。
  • 计算样式:在解析 CSS 文件后,浏览器需要根据各种选择器去匹配所要应用 CSS 规则的元素节点,然后计算出每个元素的最终样式。
  • 页面布局:指的是浏览器在计算完成样式后,会对每个元素尺寸大小和屏幕位置进行计算。由于每个元素都可能会受到其他元素的影响,并且位于DOM树形结构中的子节点元素,总会受1到父级元素修改的影响,所以页面布局的计算会经常发生。
  • 绘制:在页面布局确定后,接下来便可以绘制元素的可视内容,包括颜色、边框、阴影及文本和图像。
  • 合成:通常由于页面中的不同部分可能被绘制在多个图层上,所以在绘制完成后需要将多个图层按照正确的顺序在屏幕上合成,以便最终正确地渲染出来。

二、优化渲染路径

在这里插入图片描述
CSS 是关键资源,它会阻塞关键渲染路径也并不奇怪,但通常并不是所有的 CSS 资源都那么的『关键』。
举个例子:一些响应式CSS只在屏幕宽度符合条件时才会生效,还有一些CSS只在打印页面时才生效。这些CSS在不符合条件时,是不会生效的,所以我们为什么要让浏览器等待我们并不需要的 CSS 资源呢?
针对这种情况,我们应该让这些非关键的 CSS 资源不阻塞渲染。

避免在 CSS 中使用 @import

三、优化javascript使用

下面有注释。

<!DOCTYPE html>
<html>

<head>
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <!-- <link href="style.css" rel="stylesheet"> -->
  <!-- <link rel="stylesheet" href="main.css"> -->

  <!-- 阻塞渲染 -->
  <!-- <link href="style.css" rel="stylesheet"> -->

  <!-- 非阻塞的加载 CSS -->
  <!-- <link href="print.css" rel="stylesheet" media="print"> -->

  <!-- 拆分媒体查询相关 CSS 资源:可变阻塞加载 -->
  <!-- <link href="other.css" rel="stylesheet" media="(min-width: 40em)"> -->

  <!-- <link href="portrait.css" rel="stylesheet" media="orientation:portrait"> -->
  <title>Critical Path</title>
  <!-- <style></style> -->
  <!-- <script>
    document.addEventListener('DOMContentLoaded', () => alert("DOM ready after defer!"));
  </script> -->

  <!-- defer 特性告诉浏览器不要等待脚本。相反,浏览器将继续处理 HTML,构建 DOM。脚本会“在后台”下载,然后等 DOM 构建完成后,脚本才会执行。 -->
  <!-- <script defer src="index.js"></script>
  <script defer src="index2.js"></script> -->

  <!-- async 特性与 defer 有些类似。它也能够让脚本不阻塞页面。但是,在行为上二者有着重要的区别。 async是谁先加载完了 谁就执行,defer是即使后面先加载完 也要按顺序执行-->
  <!-- <script async src="index.js"></script>
  <script async src="index2.js"></script> -->

  <!-- 利用空闲时间预加载指定的资源 -->
  <!-- <link rel="preload" href="index.js">
  <link rel="preload" href="index2.js"> -->

  <!-- 预加载将来可能要用到的资源 -->
  <link rel="prefetch" href="index.js">
  <link rel="prefetch" href="index2.js">
</head>

<body>
  <p>Hello <span>web performance</span> students!</p>
  <!-- <div><img src="awesome-photo.jpg"></div> -->
  <script src="index.js"></script>
  <script src="index2.js"></script>
</body>

</html>

async是谁先加载完了 谁就执行
在这里插入图片描述
defer是即使后面先加载完 也要按顺序执行
在这里插入图片描述

四、使用requestAnimationFrame实现动画

实践经验告诉我们,使用定时器实现的动画会在一些低端机器上出现抖动或者卡顿的现象,这主要是因为浏览器无法确定定时器的回调函数的执行时机。以 setInterval 为例,其创建后回调任务会被放入异步队列,只有当主线程上的任务执行完成后,浏览器才会去检查队列中是否有等待需要执行的任务,如果有就从任务队列中取出执行,这样会使任务的实际执行时机比所设定的延迟时间要晚一些。
其次屏幕分辨率和尺寸也会影响刷新频率,不同设备的屏幕绘制频率可能会有所不同,而 setInterval 只能设置某个固定的时间间隔,这个间隔时间不一定与所有屏幕的刷新时间同步,那么导致动画出现随机丢帧也在所难免,如图所示。

在这里插入图片描述
为了避免这种动画实现方案中因丢帧而造成的卡顿现象,我们推荐使用 window. requestAnimationFrame 方法。与 setIntervall方法相比,其最大的优势是将回调函数的执行时机交由系统来决定,即如果屏幕刷新频率是 60Hz,则它的回调函数大约会每 16.7ms 执行一次,如果屏幕的刷新频率是 75Hz,则它回调函数大约会每 13.3ms执行一次,就是说 requestAnimationFrame方法的执行时机会与系统的刷l新频率同步。
这样就能保证回调函数在屏幕的每次刷新间隔中只被执行一次,从而避免因随机丢帧而造成的卡顿现象。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .box {
      width: 100px;
      height: 100px;
      position: absolute;
      background-color: skyblue;
    }
  </style>
</head>

<body>
  <div class="box"></div>
  <script>
    const box = document.querySelector('.box')

    // const id = setInterval(() => {
    //   if (box.offsetLeft >= 200) {
    //     box.style.left = '200px'
    //     window.clearInterval(id)
    //     return
    //   }
    //   box.style.left = `${box.offsetLeft + 1}px`
    // }, 1000 / 60)

    function run () {
      if (box.offsetLeft >= 200) {
        box.style.left = '200px'
        return
      }
      box.style.left = `${box.offsetLeft + 1}px`
      window.requestAnimationFrame(run)
    }

    window.requestAnimationFrame(run)
  </script>
</body>

</html>

五、恰当使用 Web Worker

众所周知 JavaScript 是单线程执行的,所有任务放在一个线程上执行,只有当前一个任务执行完才能处理后一个任务,不然后面的任务只能等待,这就限制了多核计算机充分发挥它的计算能力。同时在浏览器上, JavaScript的执行通常位于主线程,这恰好与样式计算、页面布局及绘制一起,如果 JavaScript 运行时间过长,必然就会导致其他工作任务的阻塞而造成丢帧。
为此可将一些纯计算的工作迁移到Web Worker上处理,它为JavaScript的执行提供了多线程环境,主线程通过创建出Worker子线程,可以分担一部分自己的任务执行压力。在 Worker 子线程上执行的任务不会干扰主线程,待其上的任务执行完成后,会把结果返回给主线程,这样的好处是让主线程可以更专注地处理UI交互,保证页面的使用体验流程。需要注意的是,Worker子线程一旦创建成功就会始终执行,不会被主线程上的事件所打断,这就意味着Worker会比较耗费资源,所以不应当过度使用,一旦任务执行完毕就应及时关闭。除此之外,在使用中还有以下几点应当注意。

  • DOM限制: Worker无法读取主线程所处理网页的DOM对象,也就无法使用 document 、window 和 parent 等对象,只能访问navigator 和 location 对象
  • 文件读取限制:Worker 子线程无法访问本地文件系统,这就要求所加载的脚本来自网络。
  • 通信限制:主线程和Worker子线程不在同一个上下文内,所以它们无法直接进行通信,只能通过消息来完成。
  • 脚本执行限制:虽然Worker可以通过 XMLHTTPRequest对象发起ajax请求,但不能使用alert()方法和confirm()方法在页面弹出提示。
  • 同源限制:Worker子线程执行的代码文件需要与主线程的代码文件同源。

Web Worker的使用方法非常简单,在主线程中通过 new Worker()方法来创建一个Worker子线程,构造函数的入参是子线程执行的脚本路径,由于代码文件必须来自网络,所以如果代码文件没能下载成功,Worker就会失败。代码示例如下:

index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Web Worker</title>
</head>

<body>
  <input type="number" id="num1" value="1">+
  <input type="number" id="num2" value="2">
  <button id="btn">=</button>
  <strong id="result">0</strong>
  <script>
    const worker = new Worker('worker.js')

    const num1 = document.querySelector('#num1')
    const num2 = document.querySelector('#num2')
    const result = document.querySelector('#result')
    const btn = document.querySelector('#btn')

    btn.addEventListener('click', () => {
      worker.postMessage({
        type: 'add',
        data: {
          num1: num1.value - 0,
          num2: num2.value - 0
        }
      })
    })

    // 监听来自子线程的消息事件
    worker.addEventListener('message', e => {
      const { type, data } = e.data
      if (type === 'add') {
        result.textContent = data
      }
    })

  </script>
</body>

</html>

worker.js

// 监听来自主线程的消息事件
onmessage = function (e) {
  const { type, data } = e.data
  if (type === 'add') {
    const ret = data.num1 + data.num2

    // 给主线程发布事件
    postMessage({
      type: 'add',
      data: ret
    })

    // 关闭线程自己
    self.close()
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/388770.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

<学习笔记>从零开始自学Python-之-web应用框架Django( 十二)上下文处理器

1.在模板中处理上下文处理 上下文就是一系列模板变量和相应的值。模板使用上下文填充变量&#xff0c;放到标签里显示在页面。在 Django 中&#xff0c;上下文使用 django.template 模块中的 Context 类表示。 它的构造方法接受一个可选参数&#xff1a;一个字典&#xff0…

HCIE-Cloud Computing LAB备考第二步:逐题攻破--第二题:FusionAccess-思维导图+题目=建立逻辑

第二题 FusionAccess markmap思维导图1 将上述思维导图跟下述题目结合,以题目顺序辅助记忆思维导图,有了思维大纲,做起实验,也就有逻辑线路,必定手掐把拿。 2.1 搭建FA实验环境(随机二考一) FA1、FA2两台服务器,请通过VNC登陆,按照题目要求根据服务器参数选择安装对…

推荐几款主流好用的markdown编辑器

介绍 随着技术的不断发展和人们对效率的追求&#xff0c;Markdown 编辑器已经成为了许多人写作的首选工具。Markdown 是一种轻量级的标记语言&#xff0c;使用简单&#xff0c;方便快捷&#xff0c;且可以方便地转换成各种格式的文件。在这篇文章中&#xff0c;我们将介绍几款…

移动端适配之动态 rem 方案

代码 就是设置浏览器字体&#xff0c;从而实现根据屏幕动态计算大小 <script>const WIDTH 750; // 设计图尺寸const setView () > {document.documentElement.style.fontSize screen.width / WIDTH "px";};window.onorientationchange setView;setVi…

JUC入门 | 黑马

一、进程和线程 进程 程序由指令和数据组成&#xff0c;但这些指令要运行&#xff0c;数据要读写&#xff0c;就必须将指令加载至CPU&#xff0c;数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理I0的 当一个程序被运行&a…

OpenGL中的坐标系

1、2D笛卡尔坐标系2D笛卡尔坐标系跟我们高中的时候学习的坐标系一样&#xff0c;是由x、y决定的。2、3D笛卡尔坐标系3D笛卡尔坐标系坐标由x、y、z决定&#xff0c;满足右手定则。3、视口glViewport(GLint x,GLint y,GLsizei width,GLsizei height)窗口和视口大小可以相同&#…

手敲Mybatis-反射工具天花板

历时漫长的岁月&#xff0c;终于鼓起勇气继续研究Mybatis的反射工具类们&#xff0c;简直就是把反射玩出花&#xff0c;但是理解起来还是很有难度的&#xff0c;涉及的内容代码也颇多&#xff0c;所以花费时间也比较浩大&#xff0c;不过当了解套路每个类的功能也好&#xff0c…

@mixin与@include介绍

目录mixin与include介绍定义一个mixin使用mixin传递变量如何引入mixinmixin与include介绍 在Sass里面&#xff0c;我们经常会见到mixin与include。 其中 mixin允许定义一个可以在整个样式表中重复使用的样式 include就是将我们定义的mixin引入到文档中 定义一个mixin mixin…

【春招面经】视源股份前端一面

前言 本次主要记录一下视源股份CVTE前端一面 &#xff08;3.3下午4点15&#xff09; 文章目录前言本次主要记录一下视源股份CVTE前端一面 &#xff08;3.3下午4点15&#xff09;问题总结介绍一下项目的来源以及做这个项目的初衷一直监听滚动&#xff0c;有没有对性能产生影响&a…

大数据技术之——zeppelin数据清洗

一、zeppelin的安装zeppelin解压后进入到conf配置文件界面。修改zeppelin-site.xml[roothadoop02 conf]# cp zeppelin-site.xml.template zeppelin-site.xml[roothadoop02 conf]# vim zeppelin-site.xml将IP地址和端口号设置成自己的修改 zeppelin-env.shexport JAVA HOME/opt/…

Linux小黑板(10):信号

我们写在linux系统环境下写一个程序&#xff0c;唔&#xff0c;"它的功能是每隔1s向屏幕打印hello world。"这时&#xff0c;我们在键盘上按出"Ctrl C"后,进程会发生什么&#xff1f;&#xff1f;我们清晰地看到&#xff0c;进程已经在我们按出"Ctrl…

UML2——行为图

目录 一、前言 二、活动图 三、交互图 3.1 一般序列图 3.2 时间约束序列图 3.3 协作图 四、用例图 五、状态图 一、前言 UML 是由视图&#xff08;View&#xff09;、图&#xff08;Diagrams&#xff09;、模型元素&#xff08;Model elements&#xff09;和通用机制等几…

(图像分割)基于图论的归一化分割

解释&#xff1a;将图像映射成图&#xff0c;以图为研究对象&#xff0c;利用图的理论知识获得图像的分割。 下面介绍&#xff1a;图的基本理论&#xff0c;基于图论的归一化分割算法 一、图的基本理论 图G&#xff1d;&#xff08;V&#xff0c;E&#xff0c;&#xff09;&…

《管理世界》数据复现:国有资本参股如何影响民营企业?——基于债务融资视角的研究

摘要&#xff1a; 本文以债务融资为切入点&#xff0c;从“未阐明的规则”和“阐明的规则”两个层面探讨了国有资本参股的“反向混改”是否以及如何影响民营企业。研究发现&#xff1a;国有资本参股可以显著降低民营企业的债务融资成本&#xff0c;扩大债务融资规模。…

性能测试——LoadRunner: Controller的使用

Controller Controller是用来创建测试环境&#xff0c;执行在VUG中编写的测试脚本 可以直接点击Controller的快捷方式打开,也可以在VUG中打开 这里将虚拟用户数设置为3,比较适合自己的电脑性能 整个controller分为下面几个模块 这里先设置左下角的目标计划 设置初始化:双击…

PHP 8.1.14升级低版本openssl扩展的操作方法

问题背景&#xff1a; PHP8.1.4内嵌openssl源码编译出来的openssl库版本号是1.0.2.x系列&#xff0c;低版本的openssl扩展存在安全漏洞&#xff0c;需要将该扩展升级openssl 社区最新版本3.0.8 操作步骤&#xff1a; 安装最新版本的openssl wget https://github.com/openssl…

Java面试总结(四)

synchroize的实例、静态、代码块的锁对象 修饰实例方法 修饰静态方法 修饰代码块 1、修饰实例方法 &#xff08;锁当前对象实例&#xff09; 给当前对象实例加锁&#xff0c;进入同步代码前要获得 当前对象实例的锁 。 synchronized void method() {//业务代码 }2、修饰静…

在vue中如果computed属性是一个异步操作怎么办?

在计算属性中使用异步方法时&#xff0c;可以使用async/await来处理异步操作。由于计算属性是基于它们的依赖缓存的&#xff0c;所以我们需要使用一个返回Promise的异步方法来确保计算属性能够正常运行。 下面是一个简单的示例&#xff0c;演示如何在计算属性中使用异步方法&am…

P6入门:P6 Professional常用快捷键/热键

目录 一 引言 Primavera P6 专业版 Primavera P6 EPPM&#xff08;网络客户端&#xff09; Primavera P6 Alt 键 Primavera P6 功能键 一 引言 在 Oracle Primavera P6 中&#xff0c;有热键命令可以节省宝贵的时间。尤其是作为一个与 Primavera P6 长打交道人熟练掌握这…

苹果手机备份的文件在电脑什么地方 苹果备份文件怎么查看

在这个网络信息时代&#xff0c;为手机进行定期备份已经成为了家常便饭。在使用备份软件对苹果手机进行备份后&#xff0c;苹果手机备份的文件在什么地方&#xff0c;苹果备份文件怎么查看呢&#xff1f;本文就带大家来了解一下。 一、苹果手机备份的文件在电脑什么地方 大家…