深入解析:css、dom 性能优化方向

  • css 优化方向
    这里建议先阅读一下浏览器的渲染相关

    • 选择器优化

      1. 避免使用过于复杂的选择器、避免嵌套层级过深
        例如:body div.container ul li a span.my_icon { }, 优化为 -> my_icon{}, 除了匹配更快,代码也更具可读性,这里补充下 浏览器是从右往左来解析css。
      2. 避免使用通配符
        例如:*{padding: 0; margin: 0}; 匹配所有元素,性能损耗随页面元素增加增长。优化为 针对性对元素body, div, p, ul, li { margin: 0; padding: 0; }
      3. 优化统一选择器
        例如 tab_left: {width: 100px}、tab_center: {width: 100px} 可以优化为 .table_left,.table_center{width: 100px}, 它们公共的部分可以提取写一起,提高匹配效率
      4. 优化统一属性
        例如margin-left: 10px; margin-top:10px; 优化为 margin: 10px 0 0 10px; 更快解析,方便渲染树的构建,
      5. 合理使用 import(不是@import, 这个要避免使用,会阻塞加载)
        import 是 框架的模块化方案 输出后会以link标签的方式来加载,但是要保证导入的只包含复用样式
      6. 减少/合理使用高级选择器
        例如 [class*="button"] 这种应该避免使用
        例如大型表格几千个tr的情况 table tr:nth-child(2n),这种会每个tr 都进行一次计算,性能损耗较大,可以使用服务端渲染或者js 初始化来优化
    • 减少重绘、重排

      1. 避免频繁的操作样式
      2. 使用transform和opacity实现动画
      3. 合并DOM 操作,意思就是多个DOM 的读写操作尽量合并执行
        例如 element.style.width = '200px';、element.style.height = '300px';、element.style.margin = '10px'; 优化为element.style.cssText = width: 200px;height: 300px; margin: 10px;
    • 布局

      1. 使用flex/grid, 避免使用flot/position
      2. 使用contain 属性优化布局计算(作用也是影响分层结果)
        • layout (隔离内外布局的影响)
        • paint (类似overflow: hidden 的效果以及 创建独立绘制层)
        • size 元素的尺寸不会依赖子元素的尺寸(需要指定元素尺寸)
        • style 限制计数器
        • content (layout paint style 的简写)
  • DOM 优化方向

    • 操作DOM
      const ulEle = document.querySelector('ul');
      //例如我现在要给ul 下添加100个li
      const fragment = document.createDocumentFragment(); //先创建文本碎片
      for(let i = 0; i < 100; i++) {
      const liEle = document.createElement('li');
      liEle.innerHTML = i;
      // 这种每一次都触发重排
      // ulEle.appendChild(liEle);
      // 优化为:
      fragment.appendChild(liEle);
      }
      //触发一次重排
      ulEle.appendChild(fragment);
    • 事件委托
      const items = document.querySelectorAll('.list-item');
      items.forEach(item => {
      item.addEventListener('click', handleClick);
      });
      // 优化:事件委托
      document.querySelector('.list').addEventListener('click', (e) => {
      if (e.target.classList.contains('list-item')) {
      handleClick(e);
      }
      });
    • 懒加载
    • 虚拟DOM
    • 虚拟列表滚动
      • 对于子项高度明确的DOM 数据比较好处理,对于不知道子项的高度且子项高度也不统一的情况,可以采用预估以及动态修正的方式。
      //实现示例
      <head>
        <meta charset="UTF-8" />
          <style>
            body {
            margin: 0;
            padding: 0;
            }
            #container {
            height: 600px;
            overflow-y: auto;
            border: 1px solid #ccc;
            position: relative;
            scroll-behavior: smooth; /* 平滑滚动 */
            background: #fff;
            }
            #spacer {
            position: relative;
            width: 100%;
            }
            .item {
            position: absolute;
            left: 0;
            width: 100%;
            box-sizing: border-box;
            border-bottom: 1px solid #eee;
            padding: 8px;
            transition: top 0.25s ease; /* 平滑过渡位置 */
            }
            .content {
            background: #d9f1ff;
            border-radius: 6px;
            padding: 6px;
            }
            </style>
              </head>
                <body>
                  <div id="container">
                    <div id="spacer"></div>
                      </div>
                        <script>
                          const total = 1000;
                          const data = Array.from({ length: total }, (_, i) => ({
                          //模拟接口数据
                          id: i,
                          text: `${i}`,
                          }));
                          const container = document.getElementById("container"); //可视容器高度
                          const spacer = document.getElementById("spacer"); //内部元素总高度
                          const estimatedHeight = 60; // 预估子项的高度
                          const buffer = 6; // 缓冲项数量
                          const measuredHeights = new Map(); // 缓存实际子项实际高度 offsetHeight
                          const visibleItems = new Map(); // 缓存已经渲染的元素
                          let avgHeight = estimatedHeight; // 动态的子项的平均高度
                          function getAverageHeight() {
                          if (measuredHeights.size === 0) return estimatedHeight; //如果没有缓存高度数据,默认返回预估子高度
                          let sum = 0;
                          for (let h of measuredHeights.values()) sum += h; //动态数据总高度
                          return sum / measuredHeights.size; //平均高度
                          }
                          function getOffset(index) {
                          //通过索引获取元素的偏移量(预估)
                          let offset = 0;
                          for (let i = 0; i < index; i++) {
                          //计算偏移量
                          offset += measuredHeights.get(i) ?? avgHeight; // 如果子项高度缓存不存在,则使用预估高度
                          }
                          return offset;
                          }
                          let ticking = false; // 防止重复渲染
                          function render() {
                          if (ticking) return;
                          ticking = true;
                          requestAnimationFrame(() => {
                          // 批量处理更新
                          ticking = false;
                          spacer.innerHTML = ""; // 清空 spacer
                          const scrollTop = container.scrollTop;
                          const startIndex = Math.max(0, Math.floor(scrollTop / avgHeight) - buffer); // 获取可见项的起始索引, 最小值0
                          const endIndex = Math.min(total, startIndex + Math.ceil(container.clientHeight / avgHeight) + buffer); // 获取可见项的结束索引 最大值为总项数
                          avgHeight = getAverageHeight(); // 平均高度
                          spacer.style.height = total * avgHeight + "px"; //计算总高度
                          const fragment = document.createDocumentFragment(); // 创建文档片段
                          for (let i = startIndex; i < endIndex; i++) {
                          //遍历可见项
                          let item = visibleItems.get(i); //从缓存中获取可见项
                          if (!item) {
                          // 创建可见项
                          item = document.createElement("div");
                          item.className = "item";
                          const randomExtra = data[i].heightType === "small" ? 60 + Math.random() * 20 : 100 + Math.random() * 50; //随机高度 60 - 150
                        item.innerHTML = `<div class="item_content" style="height: ${randomExtra}px"><b>${i}</b> ${data[i].text}</div>`;
                          item.style.top = getOffset(i) + "px"; //初始偏移量
                          requestAnimationFrame(() => {
                          const h = item.offsetHeight; // 获取子项真实高度
                          if (!measuredHeights[i] || h !== measuredHeights.get(i)) { //如果子项高度缓存不存在或者高度有变化
                          measuredHeights.set(i, h); // 缓存子项高度
                          }
                          avgHeight = getAverageHeight(); // 更新平均高度
                          spacer.style.height = total * avgHeight + "px"; //更新总高度
                          const items = document.querySelectorAll(".item");
                          items.forEach((el, idx) => {
                          const index = idx + startIndex;
                          el.style.top = getOffset(index) + "px"; //动态修正位置
                          });
                          });
                          }
                          visibleItems.set(i, item); //缓存已经渲染的元素
                          fragment.appendChild(item);
                          }
                          spacer.appendChild(fragment);
                          });
                          }
                          render();
                          let timer = null;
                          container.addEventListener("scroll", () => {
                          if (timer) clearTimeout(timer);
                          timer = setTimeout(render, 16); //节流触发
                          });
                          </script>
                            </body>
    • 滚动或者input 输入的 节流 / 防抖
posted @ 2025-11-09 13:26  yxysuanfa  阅读(9)  评论(0)    收藏  举报