news 2026/7/21 20:14:59

前端性能优化之首屏加载【懒加载,监听元素视口可见,骨架屏优化白屏时长,长列表虚拟滚动,压缩图片,Tree shaking,base64】

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
前端性能优化之首屏加载【懒加载,监听元素视口可见,骨架屏优化白屏时长,长列表虚拟滚动,压缩图片,Tree shaking,base64】

目录

路由懒加载

SPA :一个路由对应一个页面

把所有页面打包成一个文件

懒加载前提:ES6动态地加载模块——import()

webpackChunkName:分离到单独的 chunk

组件懒加载

webpackChunkName: "dialogInfo"

适用场景

体积大

非首屏

复用性高

Tree shaking:消除无用的 JS 代码,减少代码体积

原理:ES6模块静态分析

只适用于函数式编程

骨架屏优化白屏时长

虚拟列表:大量数据

长列表虚拟滚动:只渲染可视区,非可见区域的不渲染

虚拟滚动插件

Web Worker 优化长任务

适用:当任务的运算时长 - 通信时长 > 50ms

requestAnimationFrame 制作动画:刷新频率与显示器的频率保持一致

优先级

setTimeout/setInterval 属于 JS引擎

requestAnimationFrame 属于 GUI引擎

JS引擎与GUI引擎是互斥的: GUI 引擎在渲染时会阻塞 JS 引擎的计算

时间

requestAnimationFrame 刷新频率是固定且准确的

setTimeout/setInterval 是宏任务

性能

setTimeout/setInterval 定时器仍会在后台执行动画任务,

requestAnimationFrame 当页面处于未激活的状态下,屏幕刷新任务会被系统暂停

列表(以图片为例)

src属性

src :自动发送请求、下载

data-src :暂存 src 的值

动态裁剪:在图片url地址上动态添加参数

懒加载/视口可见元素

A.浏览器原生监听器

a.scroll +视口顶部距离

b.IntersectionObserver交叉观察器:目标元素与父/视口产生交叉区

常用函数

使用:this.$nextTick(()=>{DOM更新后观察})

参数

B.框架插件

a.vue-lazyload 插件

小图片转 base64 编码:无需请求

原理

图片的 二进制数据 的 字符串 表示形式,图片大小->原文件的 4/3

类似html中嵌入的文字,因此无需单独文件(无法缓存),从而无需请求

举例

小图片(小于 100KB),如小图标、图像按钮、装饰性背景图;

经常用到(如头像)

Data URL

格式

HTML

CSS

方式

a.FileReader 的 readAsDataURL()

b.canvas.toDataURL

c.url-loader 将图片转 base64


懒/动态加载:按需/运动加载

路由懒加载

SPA :一个路由对应一个页面

把所有页面打包成一个文件

如果不做处理,项目打包后,会把所有页面打包成一个文件当用户打开首页时,会一次性加载所有的资源,造成首页加载很慢,降低用户体验

懒加载前提:ES6动态地加载模块——import()

调用 import() 之处,被作为分离的模块起点,意思是,被请求的模块和它引用的所有子模块,会分离到一个单独的 chunk 中
——摘自《webpack——模块方法》的import()小节

webpackChunkName:分离到单独的 chunk

要实现懒加载,就得先将进行懒加载的子模块分离出来,打包成一个单独的文件

webpackChunkName 作用是 webpack 在打包的时候,对异步引入的库代码(lodash)进行代码分割时,设置代码块的名字。webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中

// 通过webpackChunkName设置分割后代码块的名字 const Home = () => import(/* webpackChunkName: "home" */ "@/views/home/index.vue"); const MetricGroup = () => import(/* webpackChunkName: "metricGroup" */ "@/views/metricGroup/index.vue"); ………… const routes = [ { path: "/", name: "home", component: Home }, { path: "/metricGroup", name: "metricGroup", component: MetricGroup }, ………… ]

组件懒加载

webpackChunkName: "dialogInfo"

home 页面 和 about 页面,都引入了 dialogInfo 弹框组件,该弹框不是一进入页面就加载,而是需要用户手动触发后才展示出来

<script> const dialogInfo = () => import(/* webpackChunkName: "dialogInfo" */ '@/components/dialogInfo'); export default { name: 'homeView', components: { dialogInfo } } </script>

适用场景

体积大

该页面的 JS 文件体积大,导致页面打开慢,可以通过组件懒加载进行资源拆分,利用浏览器并行下载资源,提升下载速度(比如首页)

非首屏

该组件不是一进入页面就展示,需要一定条件下才触发(比如弹框组件)

复用性高

该组件复用性高,很多页面都有引入,利用组件懒加载抽离出该组件,一方面可以很好利用缓存,同时也可以减少页面的 JS 文件大小(比如表格组件、图形组件等)

Tree shaking:消除无用的 JS 代码,减少代码体积

项目中只使用了 targetType 方法,但未使用 deepClone 方法,项目打包后,deepClone 方法不会被打包到项目里

// util.js export function targetType(target) { return Object.prototype.toString.call(target).slice(8, -1).toLowerCase(); } export function deepClone(target) { return JSON.parse(JSON.stringify(target)); }

原理:ES6模块静态分析

静态分析就是不需要执行代码,就可以从字面量上对代码进行分析。ES6之前的模块化,比如 CommonJS 是动态加载,只有执行后才知道引用的什么模块,就不能通过静态分析去做优化

只适用于函数式编程

无法通过静态分析判断出一个对象的哪些变量未被使用,所以 tree-shaking 只对使用 export 导出的变量生效

// util.js export default { targetType(target) { return Object.prototype.toString.call(target).slice(8, -1).toLowerCase(); }, deepClone(target) { return JSON.parse(JSON.stringify(target)); } }; // 引入并使用 import util from '../util'; util.targetType(null)

骨架屏优化白屏时长

虚拟列表:大量数据

长列表虚拟滚动:只渲染可视区,非可见区域的不渲染

计算出 totalHeight 列表总高度,并在触发时滚动事件时根据 scrollTop 值不断更新 startIndex 以及 endIndex ,以此从列表数据 listData 中截取对应元素

虚拟滚动插件

虚拟滚动的插件有很多,比如 vue-virtual-scroller、vue-virtual-scroll-list、react-tiny-virtual-list、react-virtualized

Web Worker 优化长任务

由于浏览器 GUI 渲染线程与 JS 引擎线程是互斥的关系,当页面中有很多长任务时,会造成页面 UI 阻塞,出现界面卡顿、掉帧等情况

查看页面的长任务:

打开控制台,选择 Performance 工具,点击 Start 按钮,展开 Main 选项,会发现有很多红色的三角,这些就属于长任务(长任务:执行时间超过50ms的任务)

适用:当任务的运算时长 - 通信时长 > 50ms

Time 是这个资源的通信时长(也叫加载时长)

requestAnimationFrame 制作动画:刷新频率与显示器的频率保持一致

可以解决用 setTimeout/setInterval 制作动画卡顿的情况

优先级

setTimeout/setInterval 属于JS引擎

requestAnimationFrame 属于GUI引擎

JS引擎与GUI引擎是互斥的: GUI 引擎在渲染时会阻塞 JS 引擎的计算

时间

requestAnimationFrame 刷新频率是固定且准确的

setTimeout/setInterval 是宏任务

根据事件轮询机制,其他任务会阻塞或延迟js任务的执行,会出现定时器不准的情况

性能

当页面被隐藏或最小化时,

setTimeout/setInterval 定时器仍会在后台执行动画任务,

requestAnimationFrame 当页面处于未激活的状态下,屏幕刷新任务会被系统暂停

列表(以图片为例)

src属性

src :自动发送请求、下载

data-src :暂存 src 的值

HTML5自定义属data-xxx主要用于在 DOM 元素中存储额外的信息,而不会自动请求资源

在图片出现在屏幕可视区域的时候,再将><img src="" alt="">动态裁剪:在图片url地址上动态添加参数

比如阿里云或七牛云,都提供了图片的动态裁剪功

懒加载/视口可见元素

(Load On Demand)延迟加载、按需加载

可视化区域之外的图片不会进行加载,在滚动屏幕时才加载。这样使得网页的加载速度更快,减少了服务器的负载。懒加载适用于图片较多,页面列表较长(长列表)的场景中。

A.浏览器原生监听器

节流和防抖能减少执行次数,但不能消除scroll频繁触发的本质

IntersectionObserver在浏览器的层面处理可见性检测,能够批量处理多个观察者的状态变化,减少计算和触发的频率

IntersectionObserver2016 年( Chrome 51 和 Firefox 55),因此scroll更兼容

a.scroll +视口顶部距离

使用场景:检测滚动位置

存储初始边界值,滚动会更改相应距离位置

scroll回调放在requestAnimationFrame中,以在浏览器下次重绘前执行

<!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> .scrollBox { width: 100%; height: 300px; background-color: #ccc; overflow-y: auto; } .target { width: 60px; height: 60px; background-color: red; } </style> </head> <body> <h1>孙悟空</h1> <h1>孙悟空</h1> <div class="scrollBox"> <h1>孙悟空</h1> <h1>孙悟空</h1> <h1>孙悟空</h1> <h1>孙悟空</h1> <h1>孙悟空</h1> <h1>孙悟空</h1> <h1>孙悟空</h1> <h1>孙悟空</h1> <h1>孙悟空</h1> <h1>孙悟空</h1> <div class="target"></div> </div> <h1>孙悟空</h1> <h1>孙悟空</h1> <h1>孙悟空</h1> <h1>孙悟空</h1> <h1>孙悟空</h1> <h1>孙悟空</h1> <h1>孙悟空</h1> <h1>孙悟空</h1> <script> const scrollBoxDom = document.querySelector('.scrollBox'); const targetDom = document.querySelector('.target'); let lastKnownScrollPosition = 0; let ticking = false; // 计算边界值 const boundaryValue = targetDom.getBoundingClientRect().top - scrollBoxDom.getBoundingClientRect().top - scrollBoxDom.offsetHeight; function handleScroll(scrollPos) { if (scrollPos > boundaryValue) { console.log('在可视区域'); scrollBoxDom.style.backgroundColor = 'pink'; } else { console.log('不在可视区域'); scrollBoxDom.style.backgroundColor = '#ccc'; } } // 防抖函数 function debounce(func, wait) { let timeout; return function (...args) { const context = this; clearTimeout(timeout); timeout = setTimeout(() => func.apply(context, args), wait); }; } // 创建防抖后的处理函数 const debouncedScroll = debounce(function () { if (!ticking) { window.requestAnimationFrame(function () { handleScroll(lastKnownScrollPosition); ticking = false; }); ticking = true; } }, 100); // 100ms 的防抖时间 scrollBoxDom.addEventListener('scroll', function () { lastKnownScrollPosition = scrollBoxDom.scrollTop; debouncedScroll(); }); </script> </body> </html>

图片的 src 属性设为默认图片

  1. 图片的真实路径则设置在data-src属性中,
  2. 绑定 window 的scroll事件,对其进行事件监听。
  3. //节流 window.addEventListener('scroll', throttle(lazyload, 200))
  4. 在scroll事件的回调中,判断我们的懒加载的图片是否进入可视区域,
  5. 如果图片在可视区内将图片的 src 属性设置为data-src的值
<html lang="en"> <head> <meta charset="UTF-8"> <title>Lazyload</title> <style> .image-item { display: block; margin-bottom: 50px; height: 200px;//一定记得设置图片高度 } </style> </head> <body> <img src="./img/default.png">b.IntersectionObserver交叉观察器目标元素与父/视口产生交叉区

使用场景:懒加载图片、实现无限滚动、检测元素可见性等

常用函数
//创建观察器new IntersectionObserver(callback, options) var io = new IntersectionObserver(callback, option) // 绑定观察元素 observer.observe(dom) io.observe(document.getElementById('example')) // 停止观察 io.unobserve(element) // 关闭观察器 io.disconnect()
使用:this.$nextTick(()=>{DOM更新后观察})
//列表发生变化时调用handleObserver() handleObserver(){ if (!this.observer) { this.observer = new IntersectionObserver((entries, self) => { entries.forEach((entry) => { if (entry.isIntersecting) { let img = entry.target let src = img.dataset.src if (src) { img.src = src img.removeAttribute('data-src') } // 解除观察 self.unobserve(entry.target) } }) }) } // DOM 更新后执行 this.$nextTick(() => { imgs.forEach((image) => { let dom = document.getElementById(image.id); if (dom) { observer.observe(dom); } }) }); }
参数

callback 是可见性变化时的回调函数,option 是配置对象(可选)

callback触发时机:刚进入视口(开始可见),完全离开视口(开始不可见)

var observer = new IntersectionObserver(function(entries,observer/self){ //回调函数参数entries 被观察IntersectionObserverEntry对象数组;观察器 }, options)

IntersectionObserverEntry 对象的属性

常用:

  • isIntersecting: 目标是否可见
  • target:被观察的目标元素,是一个 DOM 节点对象

其他:

  • time:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒
  • rootBounds:根元素的矩形区域的信息,getBoundingClientRect()方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回 null
  • boundingClientRect:目标元素的矩形区域的信息
  • intersectionRect:目标元素与视口(或根元素)的交叉区域的信息
  • intersectionRatio:目标元素的可见比例,即intersectionRectboundingClientRect的比例,完全可见时为 1,完全不可见时小于等于 0

options配置对象

//默认值 const options = { root: null, // 默认为整个文档视口;若设置祖先级对象,主要的是针对局部的滚动效果 rootMargin: '0px',//视口外延像素 threshold: 0 // 目标元素可见度的阈值,范围0(刚进入)到 1(完全可见) }; const observer = new IntersectionObserver(callback, options);

B.框架插件
a.vue-lazyload插件
// 安装 npm install vue-lazyload // main.js 注册 import VueLazyload from 'vue-lazyload' Vue.use(VueLazyload) // 配置项 Vue.use(VueLazyload, { preLoad: 1.3, error: 'dist/error.png', // 图片加载失败时的占位图 loading: 'dist/loading.gif', // 图片加载中时的占位图 attempt: 1 }) // 通过 v-lazy 指令使用 <ul> <li v-for="img in list"> <img v-lazy="img.src" :key="img.src" > </li> </ul>

前端性能优化之图片懒加载 - 掘金

预加载某图片

加载时机

方法加载时机推荐度
CSS + 隐藏但渲染页面加载时★★★☆☆
<link rel="preload">文档解析早期★★★★★
JSnew Image()JS执行时,加载好再显示★★★★☆

link rel="preload"

<!-- 在HTML head中 --> <link rel="preload" href="image.jpg" as="image">

JSnew Image()

加载完再显示

<div v-show="imageLoaded" class="global-modal" :class="modalClass" :style="modalStyle" > data() { return { imageLoaded: false } } mounted() { if (this.theme === 'img') { const img = new Image(); img.onload = () => { this.imageLoaded = true; }; img.src = require('@/assets/images/v3/certificate_bg.png'); } else { this.imageLoaded = true; } }

CSS + 隐藏但渲染

/* 创建一个渲染但不可见的元素 */ .preload-images { position: absolute; width: 0; height: 0; overflow: hidden; z-index: -1; opacity: 0.001; /* 几乎透明,但确保渲染 */ /* 关键:不要用 display: none */ background: url('@/assets/images/bg1.jpg'), url('@/assets/images/bg2.jpg'), url('@/assets/images/bg3.jpg'); }

小图片转 base64 编码:无需请求

原理

图片的 二进制数据 的 字符串 表示形式,图片大小->原文件的 4/3
类似html中嵌入的文字,因此无需单独文件(无法缓存),从而无需请求

举例

小图片(小于 100KB),如小图标、图像按钮、装饰性背景图;
经常用到(如头像)

Data URL

格式
HTML
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA..." alt="Embedded Image">
CSS
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA...');
方式
a.FileReaderreadAsDataURL()
getBase64(file: any) { //转化base64 let _ = this; return new Promise((resolve, reject) => { let reader: any = new FileReader(); let base64 = ''; reader.readAsDataURL(file); //开始转 reader.onload = function() { base64 = reader.result; }; //转 失败 reader.onerror = function(error: any) { reject(error); }; //转 结束 resolve 出去 reader.onloadend = function() { resolve(base64); }; }); },
b.canvas.toDataURL
c.url-loader将图片转 base64

参考链接:前端性能优化——首页资源压缩63%、白屏时间缩短86% - 掘金

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/21 20:06:21

Neovim-GTK源代码解析:理解Rust GUI编程的最佳实践

Neovim-GTK源代码解析&#xff1a;理解Rust GUI编程的最佳实践 【免费下载链接】neovim-gtk gtk ui for neovim 项目地址: https://gitcode.com/gh_mirrors/ne/neovim-gtk Neovim-GTK是一个基于GTK的Neovim图形界面客户端&#xff0c;采用Rust语言开发&#xff0c;完美结…

作者头像 李华
网站建设 2026/7/21 20:04:39

揭秘xManager:安卓设备上的Spotify版本管理神器

揭秘xManager&#xff1a;安卓设备上的Spotify版本管理神器 【免费下载链接】xManager Ad-Free, New Features & Freedom 项目地址: https://gitcode.com/GitHub_Trending/xm/xManager 在音乐流媒体日益普及的今天&#xff0c;Spotify已成为全球数亿用户的首选平台。…

作者头像 李华
网站建设 2026/7/21 20:02:06

开源固件OpenBeken:智能家居设备的终极掌控方案

开源固件OpenBeken&#xff1a;智能家居设备的终极掌控方案 【免费下载链接】OpenBK7231T_App Open source firmware (Tasmota/Esphome replacement) for BK7231T, BK7231N, BL2028N, T34, XR809, W800/W801, W600/W601, BL602, LN882H, Realtek chips and more 项目地址: ht…

作者头像 李华