视频加载失败

HTML、CSS和JS中的性能优化

2799 字
14 分钟
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 更新。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

HTML、CSS和JS中的性能优化
https://f.youzi177.cn/posts/2026102304html-he-css-he-js-zhong-de-xing-neng-you-hua/
作者
小封鸭
发布于
2026-10-23
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
小封鸭
小楼一夜听春雨,深巷明朝卖杏花
公告
欢迎来到我的博客。这里主要记录前端全栈和日常内容
分类
标签
最新动态
站点统计
文章
60
分类
3
标签
12
总字数
107,734
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
EdgeOne Pages
博客版本
Firefly v6.16.8
文章许可
CC BY-NC-SA 4.0
文章目录