HTML、CSS和JS中的性能优化
图片优化
图片通常是网页中体积较大的资源,优化图片可以减少网络传输量,提高页面加载速度。
1. 选择合适的图片格式
| 格式 | 特点 | 适用场景 |
|---|---|---|
| JPEG / JPG | 有损压缩,兼容性好 | 照片、复杂图片 |
| PNG | 支持透明,无损压缩 | 需要无损质量的图片 |
| WebP | 支持有损、无损压缩及透明 | 网页图片,推荐 |
| AVIF | 通常具有较高压缩效率 | 对体积要求较高的图片 |
| SVG | 矢量图,放大不失真 | 图标、Logo、简单图形 |
推荐: 普通网页图片优先考虑 WebP、AVIF,图标和简单矢量图形考虑 SVG。
2. 压缩图片体积
- 使用图片压缩工具减少文件大小。
- 避免使用尺寸远大于实际显示区域的图片。
- 根据图片用途调整压缩质量。
- 使用响应式图片,为不同屏幕提供不同尺寸的资源。
例如:
<img src="image-800.webp" srcset=" image-400.webp 400w, image-800.webp 800w, image-1200.webp 1200w " sizes="(max-width: 600px) 100vw, 800px" alt="示例图片"/>浏览器会结合显示尺寸、设备像素密度等因素选择合适的图片。
3. 其他图片优化手段
- 懒加载:非首屏图片滚动到附近时再加载。
- 预加载:提前获取首屏关键图片。
- CDN:使用内容分发网络加速图片传输。
- 设置宽高:提前为图片预留空间,减少布局偏移。
- CSS 替代图片:简单渐变、阴影、边框等效果可以直接用 CSS 实现。
<img src="image.webp" width="800" height="450" alt="示例图片"/>动画优化
动画优化的核心是:尽量减少布局计算和绘制开销,让动画保持流畅。
1. 优先使用 transform 和 opacity
不同 CSS 属性对渲染性能的影响不同。
| 属性 | 可能触发的渲染工作 | 推荐程度 |
|---|---|---|
| width、height | 布局、绘制、合成 | 尽量避免频繁动画 |
| top、left | 布局、绘制、合成 | 尽量避免频繁动画 |
| background-color | 绘制、合成 | 可以使用 |
| transform | 通常只需合成 | 推荐 |
| opacity | 通常只需合成 | 推荐 |
例如:
.box { transition: transform 0.3s ease;}
.box:hover { transform: translateX(100px);}相比不断修改 left,使用 transform 通常具有更好的动画性能。
2. 使用 requestAnimationFrame
需要通过 JavaScript 实现动画时,可以使用 requestAnimationFrame()。
const box = document.querySelector('.box');
let x = 0;
function animate() { x += 1;
box.style.transform = `translateX(${x}px)`;
if (x < 300) { requestAnimationFrame(animate); }}
requestAnimationFrame(animate);其优势:
- 让动画更新与浏览器绘制节奏协调。
- 页面处于后台时,通常会暂停回调。
- 避免使用固定
setInterval频率造成不必要的动画更新。
3. 其他动画优化
- 避免同时执行大量复杂动画。
- 不要在动画循环中频繁交替读取、修改布局属性。
- 对复杂动画可适当使用
will-change,但不要滥用。 - 对持续动画优先考虑 CSS Animation 或 Web Animations API。
- 为需要减少动态效果的用户支持
prefers-reduced-motion。
CSS 优化
CSS 优化主要是减少文件体积、降低样式计算和渲染成本。
1. 精简 CSS 文件
- 删除未使用的样式。
- 合并重复声明。
- 使用生产环境 CSS 压缩。
- 避免引入大量实际用不到的 CSS 框架样式。
- 将非关键页面的样式按需加载。
2. 合理使用 CSS 选择器
/* 不推荐:不必要的复杂嵌套 */.container .content ul li a span { color: red;}
/* 推荐:通过 class 定位 */.menu-title { color: red;}优先使用语义清晰的 class,减少不必要的深层选择器。
不过,现代浏览器已经对 CSS 选择器进行了大量优化,选择器复杂度通常不是主要性能瓶颈,不必为性能而刻意避免后代选择器等正常写法。
3. 避免频繁触发布局计算
例如:
// 不推荐:反复读取布局,再修改尺寸elements.forEach((element) => { const width = element.offsetWidth; element.style.width = `${width + 10}px`;});
// 推荐:集中读取,再集中修改const widths = elements.map((element) => element.offsetWidth);
elements.forEach((element, index) => { element.style.width = `${widths[index] + 10}px`;});这种做法可以减少浏览器被迫同步计算布局的情况。
4. 其他 CSS 优化
- 使用 CSS 变量统一管理重复的设计值。
- 对复杂、独立组件按需考虑
contain。 - 对较长的非首屏内容,可以考虑
content-visibility: auto。 - 动画尽量使用
transform和opacity。
DOM 优化
DOM 优化主要是减少不必要的节点创建、查询和修改操作。
1. 减少 DOM 节点数量
过多的 DOM 节点会增加浏览器的样式计算、布局和内存开销。
优化方式:
- 避免无意义的多层标签嵌套。
- 对大量数据使用分页。
- 长列表可以使用虚拟列表。
- 不需要展示的内容尽量不提前生成大量 DOM。
2. 批量操作 DOM
使用 DocumentFragment 可以先组织节点,再一次性插入页面。
const ul = document.querySelector('ul');
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) { const li = document.createElement('li'); li.textContent = `第 ${i} 项`;
fragment.appendChild(li);}
ul.appendChild(fragment);相比每创建一个元素就插入页面,这样可以减少对已连接 DOM 的重复操作。
3. 减少重复查询 DOM
// 不推荐:反复查询相同元素for (let i = 0; i < 100; i++) { document.querySelector('.box').textContent = i;}
// 推荐:缓存 DOM 引用const box = document.querySelector('.box');
for (let i = 0; i < 100; i++) { box.textContent = i;}如果不需要每次修改都立即展示结果,还可以只设置一次最终值,进一步减少 DOM 更新。
4. 其他 DOM 优化
- 尽量复用已有节点。
- 避免频繁读取
offsetWidth、getBoundingClientRect()等布局信息。 - 对大量数据使用分页或虚拟滚动。
- 使用 Vue 等框架时,合理使用
key、computed等机制,避免不必要的组件更新。
事件代理
事件代理(Event Delegation)也叫事件委托,是指利用事件冒泡,将多个子元素的事件统一交给父元素处理。
1. 为什么使用事件代理?
假设列表中有 1000 个按钮。
传统方式:
document.querySelectorAll('.btn').forEach((btn) => { btn.addEventListener('click', () => { console.log('按钮被点击'); });});这种方式需要为每个按钮注册事件监听器。
使用事件代理:
<ul id="list"> <li><button class="btn">按钮1</button></li> <li><button class="btn">按钮2</button></li> <li><button class="btn">按钮3</button></li></ul>
<script> const list = document.querySelector('#list');
list.addEventListener('click', (event) => { const btn = event.target.closest('.btn');
if (!btn || !list.contains(btn)) return;
console.log('按钮被点击'); });</script>无论列表中有多少个按钮,都只需要在父元素上注册一个事件监听器。
2. 事件代理的优点
- 减少事件监听器数量。
- 方便统一管理事件。
- 动态创建的子元素也能响应事件。
- 特别适合列表、表格等具有大量相似子元素的场景。
注意:事件代理主要适用于能够冒泡的事件,并不是所有事件都适合委托。
事件稀释
事件稀释是指减少高频事件处理函数的执行次数,避免频繁执行带来性能问题。
常见方法是防抖(Debounce)和节流(Throttle)。
1. 防抖 Debounce
在连续触发事件时,只有停止触发一段时间后,才执行事件处理函数。
function debounce(fn, delay) { let timer = null;
return function (...args) { clearTimeout(timer);
timer = setTimeout(() => { fn.apply(this, args); }, delay); };}
// 用户停止输入300ms后才执行input.addEventListener( 'input', debounce(() => { console.log('发送搜索请求'); }, 300));常见场景:搜索框输入、表单验证、输入联想等。
2. 节流 Throttle
在连续触发事件时,限制函数的执行频率,让函数在指定时间间隔内最多执行一次。
function throttle(fn, delay) { let lastTime = 0;
return function (...args) { const now = Date.now();
if (now - lastTime >= delay) { lastTime = now; fn.apply(this, args); } };}
// 最多每100ms执行一次window.addEventListener( 'scroll', throttle(() => { console.log(window.scrollY); }, 100), { passive: true });常见场景:滚动监听、窗口大小变化、限制按钮连续操作等。
3. 防抖和节流的区别
| 对比 | 防抖 | 节流 |
|---|---|---|
| 核心思想 | 停止触发后再执行 | 按固定时间间隔限制执行 |
| 连续触发 | 不断重新计时 | 期间仍可定期执行 |
| 适用场景 | 搜索输入 | 滚动监听 |
图片懒加载
图片懒加载(Lazy Loading)是指页面初始化时不立即加载所有图片,而是在图片接近可视区域时再加载。
1. 原生懒加载(推荐)
现代浏览器已经支持通过 loading 属性实现懒加载。
<!-- 非首屏图片 --><img src="image.webp" loading="lazy" width="800" height="450" alt="示例图片"/>| 属性值 | 作用 |
|---|---|
eager | 立即加载,默认值 |
lazy | 延迟加载,接近可视区域时加载 |
2. JavaScript 实现懒加载
如果需要自定义图片加载时机,可以使用 IntersectionObserver API。
<img data-src="image.webp" class="lazy-img" alt="示例图片" />const images = document.querySelectorAll('.lazy-img');
const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (!entry.isIntersecting) return;
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img); });}, { rootMargin: '200px'});
images.forEach((img) => observer.observe(img));这里的 rootMargin: '200px' 表示可以在图片进入可视区域前提前触发加载。
实际开发中,普通图片优先使用原生 loading="lazy",只有存在特殊需求时再考虑 IntersectionObserver。
图片预加载
图片预加载(Preloading)是指提前请求后续需要的图片资源,减少真正展示时的等待时间。
1. HTML 预加载(推荐用于关键图片)
通过 <link rel="preload"> 提前加载图片。
<head> <link rel="preload" as="image" href="/images/banner.webp" fetchpriority="high" /></head>
<body> <img src="/images/banner.webp" fetchpriority="high" alt="首页 Banner" /></body>适用场景:首屏 Banner、关键背景图片等,尤其是浏览器无法在初始 HTML 中直接发现的关键图片。
如果首屏图片已经通过 HTML 中的 <img> 很早被浏览器发现,通常可以直接设置 fetchpriority="high",不一定需要额外预加载。
2. JavaScript 预加载
使用 Image 对象提前请求图片资源。
function preloadImage(src) { const img = new Image(); img.src = src;
return img;}
preloadImage('/images/banner-2.webp');适用场景:轮播图下一张图片、即将打开的图片预览等。
3. 图片懒加载与预加载的区别
| 对比 | 图片懒加载 | 图片预加载 |
|---|---|---|
| 核心思想 | 延迟加载 | 提前加载 |
| 主要目的 | 减少初始请求 | 减少展示等待 |
| 适用场景 | 非首屏图片 | 关键图片、即将使用的图片 |
| 常用技术 | loading="lazy" | rel="preload"、Image |
| 注意事项 | 首屏关键图片不宜懒加载 | 避免预加载大量非关键图片 |
预加载应合理控制数量,否则过多的预加载请求可能争抢网络带宽,反而影响关键资源。
总结
| 优化方向 | 核心优化技术 |
|---|---|
| 图片优化 | 压缩、WebP / AVIF、响应式图片、CDN |
| 动画优化 | transform、opacity、requestAnimationFrame |
| CSS 优化 | 压缩、按需加载、减少不必要的布局计算 |
| DOM 优化 | 减少节点、批量操作、虚拟列表 |
| 事件代理 | 事件冒泡、父元素统一监听 |
| 事件稀释 | 防抖、节流 |
| 图片懒加载 | loading=“lazy”、IntersectionObserver |
| 图片预加载 | preload、fetchpriority、Image |
对于现代 Vue 3 项目,很多底层 DOM 更新优化已经由框架处理。实际开发时,更值得优先掌握的是:图片格式与加载策略、事件代理、防抖节流、动画性能,以及避免不必要的组件和 DOM 更新。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!














