-
css 优化方向
这里建议先阅读一下浏览器的渲染相关
-
选择器优化
- 避免使用过于复杂的选择器、避免嵌套层级过深
例如:body div.container ul li a span.my_icon { }
, 优化为 ->my_icon{}
, 除了匹配更快,代码也更具可读性,这里补充下 浏览器是从右往左来解析css。 - 避免使用通配符
例如:*{padding: 0; margin: 0}
; 匹配所有元素,性能损耗随页面元素增加增长。优化为 针对性对元素body, div, p, ul, li { margin: 0; padding: 0; }
- 优化统一选择器
例如tab_left: {width: 100px}、tab_center: {width: 100px}
可以优化为.table_left,.table_center{width: 100px}
, 它们公共的部分可以提取写一起,提高匹配效率 - 优化统一属性
例如margin-left: 10px; margin-top:10px;
优化为margin: 10px 0 0 10px
; 更快解析,方便渲染树的构建, - 合理使用 import(不是@import, 这个要避免使用,会阻塞加载)
import 是 框架的模块化方案 输出后会以link标签的方式来加载,但是要保证导入的只包含复用样式 - 减少/合理使用高级选择器
例如[class*="button"]
这种应该避免使用
例如大型表格几千个tr的情况 table tr:nth-child(2n),这种会每个tr 都进行一次计算,性能损耗较大,可以使用服务端渲染或者js 初始化来优化
- 避免使用过于复杂的选择器、避免嵌套层级过深
-
减少重绘、重排
- 避免频繁的操作样式
- 使用transform和opacity实现动画
- 合并DOM 操作,意思就是多个DOM 的读写操作尽量合并执行
例如element.style.width = '200px';、element.style.height = '300px';、element.style.margin = '10px'
; 优化为element.style.cssText = width: 200px;height: 300px; margin: 10px;
-
布局
- 使用flex/grid, 避免使用flot/position
- 使用contain 属性优化布局计算(作用也是影响分层结果)
- layout (隔离内外布局的影响)
- paint (类似overflow: hidden 的效果以及 创建独立绘制层)
- size 元素的尺寸不会依赖子元素的尺寸(需要指定元素尺寸)
- style 限制计数器
- content (layout paint style 的简写)
-
-
DOM 优化方向
-
操作DOM
javascriptconst 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);
-
事件委托
javascriptconst 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 数据比较好处理,对于不知道子项的高度且子项高度也不统一的情况,可以采用预估以及动态修正的方式。
javascript//实现示例 <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 输入的 节流 / 防抖
-
css、dom 性能优化方向
开心不就得了2025-10-11 11:49
相关推荐
道可到3 小时前
一个属性,让无数前端工程师夜不能寐闲云S3 小时前
Lit开发:字体图标的使用我是天龙_绍3 小时前
uniapp 个人中心页面开发指南刘永胜是我3 小时前
解决React热更新中"process is not defined"错误:一个稳定可靠的方案星链引擎4 小时前
开发者深度版(面向技术人员 / 工程师)_大学牲4 小时前
Flutter 之魂 GetX🔥(一)从零了解状态管理悠哉摸鱼大王4 小时前
从麦克风输入到传输给后端实现ASR用户877244753964 小时前
Lubanno7UniverSheet:让 React/Vue 项目轻松拥有 Excel 级电子表格能力比老马还六4 小时前
Blockly集合积木开发