每天一个高级前端知识 - Day 1
今日主题:浏览器渲染流水线深度解析 - 从URL到像素的魔法
核心概念:关键渲染路径(Critical Rendering Path)
浏览器从收到HTML到最终显示像素,经历了以下阶段:
HTML → DOM树
CSS → CSSOM树 → 渲染树 → 布局(Layout) → 绘制(Paint) → 合成(Composite)
🔍 深入细节
1. DOM树构建
- 字节 → 字符 → 令牌 → 节点 → DOM
- 阻塞:
<script>标签会暂停解析(除非加async/defer)
2. CSSOM构建
- CSS会阻塞渲染(关键CSS内联优化)
- CSS不会阻塞DOM解析,但会阻塞JS执行(因为JS可能查询样式)
3. 渲染树
- 过滤
display:none、<head>等不可见元素 visibility:hidden仍在树中(占空间)
4. Layout(回流)
- 计算每个节点的几何信息(宽高、位置)
- 触发条件:改变几何属性、添加/删除DOM、改变窗口大小
- 代价最高,需避免
5. Paint(重绘)
- 填充像素(颜色、背景、阴影等)
- 触发条件:改变非几何属性(color, background等)
6. Composite(合成)
- 将绘制层合并为最终屏幕图像
- 触发条件:transform、opacity、will-change等(仅合成)
💡 性能优化实战
// ❌ 糟糕实践 - 强制同步布局
const height = element.offsetHeight; // 读取
element.style.height = height + 10 + 'px'; // 写入
// ✅ 最佳实践 - 批处理读写
// 先读后写
const height = element.offsetHeight;
const width = element.offsetWidth;
// 批量写入
element.style.height = height + 10 + 'px';
element.style.width = width + 10 + 'px';
// 🚀 高级技巧 - 使用requestAnimationFrame
requestAnimationFrame(() => {
// 在下一帧绘制前批量修改
element.style.transform = 'translateX(100px)';
});
🎯 今日挑战
实现一个高性能滚动变色导航栏,要求:
- 滚动超过100px时改变背景色
- 使用
transform而不是top/left实现固定效果 - 使用
IntersectionObserver(避免滚动监听)
参考实现
class PerformanceNavbar {
constructor() {
this.navbar = document.querySelector('.navbar');
this.sentinel = document.createElement('div');
this.init();
}
init() {
// 创建哨兵元素
this.sentinel.style.position = 'absolute';
this.sentinel.style.top = '100px';
this.sentinel.style.height = '1px';
document.body.prepend(this.sentinel);
// IntersectionObserver比scroll监听性能好100倍+
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (!entry.isIntersecting) {
this.navbar.classList.add('scrolled');
} else {
this.navbar.classList.remove('scrolled');
}
});
});
observer.observe(this.sentinel);
}
}
明日预告:Event Loop微任务/宏任务底层原理 & 如何写一个零延迟的调度器
保持好奇,持续输出!🔥