移动Web开发性能优化
性能:页面的响应速度
- 打开页面到实际能够使用的时间,加载快不快(1.网络请求的时间 2.页面加载和渲染的时间)
- 与页面进行交互的流程程度,操作卡不卡(JavaScript脚本的执行速度)
更快的首屏内容
更快地加载页面的首屏内容(不需要滚动就能看到的内容),优先考虑
性能优化点
- 网络请求过程中的优化点
- 页面加载和渲染过程中的优化点
- HTML、CSS、JS中的优化点
网络请求过程中的性能优化点
- 将多个资源分配在不同的域上,减少请求队列的等待时间(浏览器为每个域名分配的并发通道有限;多个域意味着更多的DNS查询时间,不宜过多,通常拆分为3-5个比较合适,chrome浏览器,同域名下资源加载的最大并发连接数为6
- 通过dns-prefetch减少DNS查询时间(如淘宝的移动端) <link rel=dns-prefetch href="">
- 减少HTTP的请求数量,因为建立通道往往比较耗时
资源合并(合并CSS、JS文件)(合并后的资源不能过大;考虑公用和缓存问题)
内联首屏相关代码
使用缓存(浏览器缓存、localstorage等)
4. 减少请求资源的大小
资源压缩(HTML、CSS的压缩以及JS的压缩和混滑)
开启Gzip压缩
减少cookie体积(因为每次请求,都会把当前域下的cookie都带上,所以不必要的cookie不要写)
页面加载和渲染过程中的优化点
- CSS一般在head中引入(如果在下面引入,网页较大的时候会有短暂的裸奔情况)
- JS一般在body的末尾引入(特殊情况除外,如rem布局不牵涉dom对象,且需要第一时间算出rem对应的px值)
- 减少回流(reflow)/重布局(relayout)与重绘(repaint)
回流(reflow):指的是当渲染树中的一部分(或者全部)因为元素的规模、位置、隐藏等改变而需要重新构造的过程,这是一个计算密集型任务,可能会导致页面的性能下降
回流会被触发的主要场景:
- 页面初次渲染
- 窗口大小改变
- 元素位置、大小改变
- 元素的内容改变,如用户输入文本导致文本框变大
- 元素的字体大小变化
- 添加或者删除可见的DOM元素
重绘(repaint):当一些元素的属性更改,但并不影响其在文档流中的位置时,浏览器仅仅重绘该元素的过程,简单来说就是元素的位置和大小不变,但是外观(如颜色、阴影或内容)改变
重绘会被触发的主要场景:
- 更改元素颜色
- 更改元素的背景色
- 更改文字的颜色或内容
Tips:回流一定会引起重绘,重绘不一定会引起回流
如何减少回流:
- 使用 CSS Class 操作:而不是直接在元素上设置多个样式。例如,使用
element.classList.add()添加一个预定义的类,而不是多次设置element.style.property。 - 避免使用 table 布局:表格布局比其他布局更容易引起回流,因为表格的单元格大小通常是相互依赖的。
- 使用
documentFragment:当需要进行多次的 DOM 操作时,可以使用documentFragment在内存中构建节点,然后一次性地添加到文档中。 - 批量操作:如果需要对多个元素进行操作,先将它们从文档流中移除(例如,设置
display: none),完成所有操作后再将它们一次性添加回去。 - 避免使用内联样式:尽量使用外部的 CSS,因为修改内联样式可能导致回流。
- 减少对 layout 属性的访问:避免频繁地访问或计算那些会触发回流的属性,如
offsetWidth、offsetHeight、clientWidth和clientHeight。如果需要多次使用,可以考虑缓存其值。 - 使用绝对定位:对于动画或经常变化的元素,使用绝对定位可以减少对其他元素的影响,从而减少回流。
- 优化 JavaScript:避免使用低效的 JavaScript,因为它可能会触发大量的回流和重绘。
- 避免使用 JavaScript 设置那些可能触发大量回流的样式:例如,避免使用 JavaScript 设置宽度、高度或边距。
- 使用
requestAnimationFrame:对于动画,使用requestAnimationFrame可以确保浏览器在下一次重绘前完成所有的 JavaScript 操作,从而减少不必要的回流。 - 使用虚拟 DOM:如 React 的虚拟 DOM,可以在内存中比较差异,然后有选择地更新实际 DOM,从而减少回流和重绘。
- 使用 CSS Class 操作:而不是直接在元素上设置多个样式。例如,使用
如何减少重绘:
- 避免不必要的 CSS 选择器:简化 CSS 选择器,避免使用深层次或过于具体的选择器。
- 使用
will-change属性:此 CSS 属性允许你通知浏览器某个元素可能会有何种变化,例如will-change: transform。这可以使浏览器优化即将发生的变化,但不要过度使用它,因为它可能会消耗更多的资源。 - 避免使用大型、复杂的阴影和渐变:复杂的 CSS 效果可能导致频繁的重绘。
- 减少使用 JavaScript 更改样式:特别是更改那些会导致重绘的样式。如果必须更改,尝试一次性修改,而不是多次。
- 使用 CSS 动画:相对于 JavaScript 动画,CSS 动画通常更加优化和高效。
- 避免使用 :hover:在大型元素或列表上的
:hover效果可能会触发大量的重绘。如果可能,限制其使用。 - 限制 iframe、Web Fonts 和图片的数量:这些都可能导致重绘,特别是当它们被加载或重新渲染时。
- 浏览器开发者工具:使用浏览器提供的开发者工具来检测和分析页面上的重绘。例如,Chrome 的 DevTools 提供了一个“Paint Flashing”工具,可以高亮显示页面上的重绘区域。
- 避免频繁操作 DOM:频繁的 DOM 操作可能导致大量的重绘和回流。考虑使用虚拟 DOM 或其他优化策略来减少这种影响。
- 使用
transform和opacity:当进行动画或变换时,尽量使用transform和opacity,因为它们通常可以由 GPU 加速,从而减少 CPU 的重绘负担。
HTML,CSS,JS中的优化点
DOM操作优化
事件优化
图片懒加载和预加载
图片优化
减少http的请求数量
使用CSS画图/动画 来代替简单的图片
合并小图标(css scripts),css雪碧图/精灵图
使用base64,把小图标(小于10kb)内嵌到html中(base64是一种用于编码数据的方法,通常用于将二进制数据(图像或文字)转换为ASCII字符串)
减少请求资源的大小
用图标字体代替简单的图标(小图片)
使用Photoshop等图像软件,压缩图片
媒体查询,小屏幕用小图片,大屏幕用大图片
选择合适的图片类型

动画优化
1. 优先使用CSS3过渡和动画
2. 优先使用translate3d做运动
3. 必须使用JavaScript做动画时,使用费requestAnimationFrame,不建议使用setTimeout,setInternal。(requestAnimationFrame是一个浏览器的API,主要用于在进行网页动画效果制作时实现更为平滑的动画。原理是将动画与浏览器刷新同步,这样可以在每一次屏幕刷新时执行一次动画,避免动画卡顿和重绘等问题,一般接手一个回调函数作为参数,且在浏览器下一次绘制之前调用该函数)
CSS动画库
27 个前端动画库让你的交互更加炫酷 - 掘金 (juejin.cn)
CSS优化
1. 不要使用过多的嵌套,过于复杂的选择器,可以通过样式直接选择(css选择器会从右往左解析)
2. 避免过多使用通配符选择器(如*,h1等)
3. 移出无匹配的样式(不要使用空规则集)
4. 避免使用CSS @import导入CSS,会发送多余的http请求,less sass的import语法除外
5. 提取公共部分,不要每个样式都单独写
DOM优化
1. 渲染优化
减少DOM元素的数量和层级嵌套
尽量避免使用table布局,使用其他标签代替(table标签是作为一个整体解析的,要等整个表格解析完才能显示,且布局容易改动)
2. 选择器优化
优先使用id获取单个元素(一般情况下,id用来给JS获取元素,class用来设置样式)
获取多个元素的时候,尽量使用className
3. 减少DOM操作
把选择器的结果保存到变量,把操作DOM的次数降下来
注意强制回流(获取和修改属性时都可能会引起回流,包括但不限于offsetTop、offsetLeft、scrollTop和scrollLeft,获取这些属性时会引起多次的回流和重绘,这些就是强制回流)
不要直接通过JS来修改元素的style,应该通过添加或者移出class的方式(可以使用classList的add和remove操作,也可以使用classList的toggle方法,有就删除,没有就添加)
使用DocumentFragment优化多次appendChild操作(DocumentFragment不是DOM树的一部分,不会引起DOM树的重新渲染(reflow),不会导致性能问题,且插入的是它的全部子孙节点,本身不会进入DOM树)
<body>
<ul></ul>
</body>
<script>
// 定义一个数组
var toDoData = ["洗衣服", "做饭", "写代码"];
// 找到 ul 对象
var ul = document.querySelector("ul");
// 创建文档碎片
var liFragment = document.createDocumentFragment();
// 遍历数组
for (var i = 0; i < toDoData.length; i++) {
// 创建 li 元素, 孤儿节点
var li = document.createElement("li");
// 添加类名
li.className = "item";
// 添加内容
li.innerHTML = toDoData[i];
// 把孤儿节点添加到文档碎片中
liFragment.appendChild(li);
}
// 把文档碎片添加到 ul 中, 这样只会触发一次上树操作
ul.appendChild(liFragment);
</script>
事件代理(也称为事件委托,把原本在子元素上监听的事件委托给父元素监听)
事件稀释(有些事件会在一段时间内多次触发,减少这些事件的触发频率就是事件稀释)
典型的包括scroll、resize、mousemove、touchmove
常见的方法有:防抖和节流
防抖 debounce(在某个事件期限内,事件处理函数只会执行一次)
防抖的核心思想是使用setTimeout来延迟函数的执行。每次当被防抖的函数调用时,它都会取消之前的定时器(如果存在的话)并设置一个新的定时器,只有在经过指定的延迟时间后,该函数才会执行 <script> var div = document.querySelector("div");
function 控制发射频率(发射导弹, 发射读秒) { let 发射计划 = null; return function () { //延时器读秒没结束,导弹发射取消,重新读秒 if (发射计划) { clearTimeout(发射计划); } 发射计划 = setTimeout(() => { 发射导弹.apply(this, arguments); // 调用函数, 发射导弹 发射计划 = null; // 延时器时间结束,导弹发射, 计划清空 }, 发射读秒); }; } // 使用防抖函数 div.onmousemove = 控制发射频率(function () { console.log("导弹发射!"); }, 200); </script> </html>
节流throttle(事件处理一段时间之后,在某个时间期限内不再工作)
<style>
body {
height: 2000px;
}
</style>
<body></body>
<script>
// 节流函数, 第一个参数是要节流的函数, 第二个参数是节流的时间
function throttle(fn, delay) {
// 上一次执行的时间
let lastTime = 0;
return function () {
// 当前时间, 当前时间戳, 单位是毫秒
const nowTime = Date.now();
// 如果当前时间减去上一次执行的时间大于等于节流的时间, 就执行函数
if (nowTime - lastTime >= delay) {
// 调用函数
fn.apply(this, arguments);
// 更新上一次执行的时间
lastTime = nowTime;
}
};
}
window.onresize = throttle(function () {
console.log("resize...");
}, 200); // 延迟200毫秒触发
window.onscroll = throttle(function () {
console.log("scroll....");
}, 200); // 延迟200毫秒触发
</script>
使用节流实现点击返回顶部
<script>
// 节流函数, 第一个参数是要节流的函数, 第二个参数是节流的时间
function throttle(fn, delay) {
// 上一次执行的时间
let lastTime = 0;
return function () {
// 当前时间, 当前时间戳, 单位是毫秒
const nowTime = Date.now();
// 如果当前时间减去上一次执行的时间大于等于节流的时间, 就执行函数
if (nowTime - lastTime >= delay) {
// 调用函数
fn.apply(this, arguments);
// 更新上一次执行的时间
lastTime = nowTime;
}
};
}
const a = document.querySelector("a"); // 选取页面上的第一个<a>标签,并将其存储在常量"a"中
let winHeight = window.innerHeight; // 获取当前窗口的内部高度,赋值给 "winHeight"
window.onresize = throttle(function () {
console.log("resize....");
winHeight = window.innerHeight; // 当窗口大小发生改变时,重新获取并更新窗口的内部高度
}, 200); // 延迟200毫秒触发
window.onscroll = throttle(function () {
console.log("scroll....");
if (window.scrollY >= winHeight) {
// 当滚动距离超过或等于窗口的内部高度时
a.style.display = "block"; // 显示<a>标签(即使其可见)
} else {
// 当滚动距离小于窗口的内部高度时
a.style.display = "none"; // 隐藏<a>标签(即使其不可见)
}
}, 200); // 延迟200毫秒触发
</script>
图片懒加载
也称为图片的延迟加载,按需加载,在需要的时候加载图片(如京东移动端),保证尽快加载首屏内容
需要修改一下html代码,修改src属性为loading.webp,原来的路径放在自定义属性data-src里面(自定义属性的名字可以自定义)
<div class="img-container"><img src="./img/loading.webp" data-src="./img/t1.png" alt="" /></div>
<div class="img-container"><img src="./img/loading.webp" data-src="./img/t2.png" alt="" /></div>
<div class="img-container"><img src="./img/loading.webp" data-src="./img/t3.png" alt="" /></div>
<div class="img-container"><img src="./img/loading.webp" data-src="./img/t4.png" alt="" /></div>
<div class="img-container"><img src="./img/loading.webp" data-src="./img/t5.png" alt="" /></div>
图片懒加载的代码如下
const imgArray = [...document.querySelectorAll("img")]; // 将页面上所有的<img>元素放入一个数组中
window.onscroll = lazyLoad; // 当用户滚动页面时,执行lazyLoad函数
lazyLoad(); // 初次加载页面时,执行lazyLoad函数
function lazyLoad() {
// 定义一个名为lazyLoad的函数
for (let index = 0; index < imgArray.length; index++) {
// 遍历存有<img>元素的数组
const element = imgArray[index]; // 获取当前遍历到的图片元素
if (isInVisibleArea(element)) {
// 如果当前图片处于可见区域
element.src = element.dataset.src; // 设置图片的src属性为其data-src属性所保存的真实路径
imgArray.splice(index--, 1); // 从数组中删除当前已被加载的图片,并将索引值减一,以防错过数组中的元素
}
}
}
function isInVisibleArea(el) {
// 定义一个用于判断图片是否处于可见区域的函数
const rect = el.getBoundingClientRect(); // 获取图片元素的位置信息
return rect.bottom > 0 && rect.top < window.innerHeight; // 如果图片元素位于可见范围内,则返回true
}
getBoundClientRect()方法用来获取该元素的位置,返回一个DOMRect对象,包含top,bottom,left,right等属性,代表该元素相对于视口的位置
需要设置img的具体高度,否则rect.bottom > 0 && rect.top < window.innerHeight;会出bug
图片的预加载
提前加载可能会用到的图片(如漫画网站,加载好第一张,在用户看漫画的时候,预先加载下面的几张)
预加载一张图片的写法
<body>
<div class="img-container">
<img src="./images/1.png" alt="图片" id="img" />
</div>
<script>
// 定义了一个图片数组,存放着四张图片的路径。
var imgs = ["./images/2.jpg", "./images/3.jpg", "./images/4.jpg", "./images/5.jpg"];
// 初始化一个索引变量 i,用来迭代图片数组。
var i = 0;
// 获取页面中 id 为 "img" 的 img 元素。
var img = document.getElementById("img");
// 预加载第一张图片
preload(imgs[i]);
// 当点击 img 元素时,执行下面的函数
img.onclick = function () {
// 如果 i 小于图片数组的长度(也就是说还有图片没有显示)
if (i < imgs.length) {
// 将 img 元素的 src 属性设置为当前索引对应的图片路径,展示图片
img.src = imgs[i];
// 索引加 1,准备显示下一张图片
i++;
// 如果还有下一张图片,则预加载下一张图片
if (i < imgs.length) {
preload(imgs[i]);
}
} else {
// 如果已经没有更多的图片了,则在控制台打印提示信息
console.log("已经是最后一张了!");
}
};
// 预加载图片的函数,创建一个新的 Image 对象,并为其 src 属性赋值,使得浏览器可以在显示图片之前先将图片下载到本地,提高用户体验
function preload(src) {
const img = new Image();
img.src = src;
}
</script>
</body>
预加载多张的写法
<body>
<div class="img-container"><img src="./images/1.png" alt=""></div>
<script>
//将图片位置存入数组
var imgArray =
[
"./images/2.jpg",
"./images/3.jpg",
"./images/4.jpg",
"./images/5.jpg",
"./images/6.jpg",
"./images/7.jpg",
"./images/8.jpg",
"./images/9.jpg",
"./images/10.jpg",
"./images/11.jpg",
"./images/12.jpg",
]
//定义一个数组存储已经加载过的图片
var preLoadedImages = [];
//定义变量存储当前图片的位置
var i= 0;
//找到图片对象
var img = document.querySelector("img");
//预加载三张图片
preLoadImages(i,3);
img.onclick = function(){
//判断是否是最后一张,如果不是,预加载后面三张
if(i < imgArray.length){
if(!preLoadedImages.includes(imgArray[i])){
preLoad(imgArray[i]);
}
img.src = imgArray[i] + '?rand=' + Math.random();
i++;
preLoadImages(i,3);
}
console.log(img.src)
}
//定义预加载函数
function preLoad(src){
//如果图片的路径不在已经加载过得图片的数组内,进行预加载
if(!preLoadedImages.includes(src)){
const img = new Image();
img.src = src;
//将路径添加到已经加载过的图片数组内
preLoadedImages.push(src);
}
}
//判断是否满足预加载条件
function preLoadImages(startIndex,num){
for(var j = 0; j < num; j++){
if(startIndex + j < imgArray.length){
preLoad(imgArray[startIndex+j]);
}
}
}
</script>
</body>

浙公网安备 33010602011771号