移动Web开发性能优化

性能:页面的响应速度

  1. 打开页面到实际能够使用的时间,加载快不快(1.网络请求的时间 2.页面加载和渲染的时间)
  2. 与页面进行交互的流程程度,操作卡不卡(JavaScript脚本的执行速度)

更快的首屏内容

  更快地加载页面的首屏内容(不需要滚动就能看到的内容),优先考虑

性能优化点

  1. 网络请求过程中的优化点
  2. 页面加载和渲染过程中的优化点
  3. HTML、CSS、JS中的优化点

网络请求过程中的性能优化点

  1. 将多个资源分配在不同的域上,减少请求队列的等待时间(浏览器为每个域名分配的并发通道有限;多个域意味着更多的DNS查询时间,不宜过多,通常拆分为3-5个比较合适,chrome浏览器,同域名下资源加载的最大并发连接数为6
  2. 通过dns-prefetch减少DNS查询时间(如淘宝的移动端)  <link rel=dns-prefetch href="">
  3. 减少HTTP的请求数量,因为建立通道往往比较耗时

    资源合并(合并CSS、JS文件)(合并后的资源不能过大;考虑公用和缓存问题)

    内联首屏相关代码

    使用缓存(浏览器缓存、localstorage等)

  4. 减少请求资源的大小

    资源压缩(HTML、CSS的压缩以及JS的压缩和混滑)

    开启Gzip压缩

    减少cookie体积(因为每次请求,都会把当前域下的cookie都带上,所以不必要的cookie不要写)

页面加载和渲染过程中的优化点

  1. CSS一般在head中引入(如果在下面引入,网页较大的时候会有短暂的裸奔情况)
  2. JS一般在body的末尾引入(特殊情况除外,如rem布局不牵涉dom对象,且需要第一时间算出rem对应的px值)
  3. 减少回流(reflow)/重布局(relayout)与重绘(repaint)

    回流(reflow):指的是当渲染树中的一部分(或者全部)因为元素的规模、位置、隐藏等改变而需要重新构造的过程,这是一个计算密集型任务,可能会导致页面的性能下降

      回流会被触发的主要场景:

      1. 页面初次渲染
      2. 窗口大小改变
      3. 元素位置、大小改变
      4. 元素的内容改变,如用户输入文本导致文本框变大
      5. 元素的字体大小变化
      6. 添加或者删除可见的DOM元素

    重绘(repaint):当一些元素的属性更改,但并不影响其在文档流中的位置时,浏览器仅仅重绘该元素的过程,简单来说就是元素的位置和大小不变,但是外观(如颜色、阴影或内容)改变

      重绘会被触发的主要场景:

      1. 更改元素颜色
      2. 更改元素的背景色
      3. 更改文字的颜色或内容

    Tips:回流一定会引起重绘,重绘不一定会引起回流

  如何减少回流:

    1. 使用 CSS Class 操作:而不是直接在元素上设置多个样式。例如,使用 element.classList.add() 添加一个预定义的类,而不是多次设置 element.style.property
    2. 避免使用 table 布局:表格布局比其他布局更容易引起回流,因为表格的单元格大小通常是相互依赖的。
    3. 使用 documentFragment:当需要进行多次的 DOM 操作时,可以使用 documentFragment 在内存中构建节点,然后一次性地添加到文档中。
    4. 批量操作:如果需要对多个元素进行操作,先将它们从文档流中移除(例如,设置 display: none),完成所有操作后再将它们一次性添加回去。
    5. 避免使用内联样式:尽量使用外部的 CSS,因为修改内联样式可能导致回流。
    6. 减少对 layout 属性的访问:避免频繁地访问或计算那些会触发回流的属性,如 offsetWidthoffsetHeightclientWidthclientHeight。如果需要多次使用,可以考虑缓存其值。
    7. 使用绝对定位:对于动画或经常变化的元素,使用绝对定位可以减少对其他元素的影响,从而减少回流。
    8. 优化 JavaScript:避免使用低效的 JavaScript,因为它可能会触发大量的回流和重绘。
    9. 避免使用 JavaScript 设置那些可能触发大量回流的样式:例如,避免使用 JavaScript 设置宽度、高度或边距。
    10. 使用 requestAnimationFrame:对于动画,使用 requestAnimationFrame 可以确保浏览器在下一次重绘前完成所有的 JavaScript 操作,从而减少不必要的回流。
    11. 使用虚拟 DOM:如 React 的虚拟 DOM,可以在内存中比较差异,然后有选择地更新实际 DOM,从而减少回流和重绘。 

  如何减少重绘: 

    1. 避免不必要的 CSS 选择器:简化 CSS 选择器,避免使用深层次或过于具体的选择器。
    2. 使用 will-change 属性:此 CSS 属性允许你通知浏览器某个元素可能会有何种变化,例如 will-change: transform。这可以使浏览器优化即将发生的变化,但不要过度使用它,因为它可能会消耗更多的资源。
    3. 避免使用大型、复杂的阴影和渐变:复杂的 CSS 效果可能导致频繁的重绘。
    4. 减少使用 JavaScript 更改样式:特别是更改那些会导致重绘的样式。如果必须更改,尝试一次性修改,而不是多次。
    5. 使用 CSS 动画:相对于 JavaScript 动画,CSS 动画通常更加优化和高效。
    6. 避免使用 :hover:在大型元素或列表上的 :hover 效果可能会触发大量的重绘。如果可能,限制其使用。
    7. 限制 iframe、Web Fonts 和图片的数量:这些都可能导致重绘,特别是当它们被加载或重新渲染时。
    8. 浏览器开发者工具:使用浏览器提供的开发者工具来检测和分析页面上的重绘。例如,Chrome 的 DevTools 提供了一个“Paint Flashing”工具,可以高亮显示页面上的重绘区域。
    9. 避免频繁操作 DOM:频繁的 DOM 操作可能导致大量的重绘和回流。考虑使用虚拟 DOM 或其他优化策略来减少这种影响。
    10. 使用 transformopacity:当进行动画或变换时,尽量使用 transformopacity,因为它们通常可以由 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>

 

posted @ 2023-11-28 21:09  波波波维奇~  阅读(49)  评论(0)    收藏  举报