名词(术语)了解--CSSOM (CSS Object Model)
名词(术语)了解–CSSOM (CSS Object Model)
CSSOM 概述
CSSOM 是一个与 DOM (Document Object Model) 相对应的、用于 CSS 的 API 集合。
它提供了一种程序化的方式来读取和修改文档的样式信息。
CSSOM 的主要组成部分
- 样式规则树
document
└── stylesheets
└── cssRules
├── style rule 1
│ ├── selector
│ └── declaration block
├── style rule 2
└── style rule 3
- 计算样式
- 浏览器会将所有适用的 CSS 规则计算并应用到每个 DOM 节点
- 继承属性会从父节点传递到子节点
- 最终每个节点都会有一个完整的计算样式(computed style)
CSSOM API 主要功能
- 读取样式信息
// 获取元素的计算样式
const style = window.getComputedStyle(element);
console.log(style.backgroundColor);
// 访问样式表
const sheets = document.styleSheets;
- 修改样式
// 直接修改元素样式
element.style.backgroundColor = 'red';
// 添加新的样式规则
const sheet = document.styleSheets[0];
sheet.insertRule('body { background-color: lightblue }', 0);
- 操作 CSS 规则
// 获取所有CSS规则
const rules = document.styleSheets[0].cssRules;
// 删除规则
document.styleSheets[0].deleteRule(0);
CSSOM 渲染过程
-
构建过程
- 加载 HTML → 构建 DOM
- 加载 CSS → 构建 CSSOM
- DOM + CSSOM = 渲染树(Render Tree)
-
性能注意事项
- CSS 被视为渲染阻塞资源
- CSSOM 构建会阻止页面渲染
- 优化建议:
- 精简 CSS
- 移除未使用的 CSS
- 使用媒体查询划分 CSS
实际应用示例
- 动态主题切换
// 通过 CSSOM 实现动态主题切换
function toggleTheme(isDark) {
const root = document.documentElement;
if (isDark) {
root.style.setProperty('--bg-color', '#333');
root.style.setProperty('--text-color', '#fff');
} else {
root.style.setProperty('--bg-color', '#fff');
root.style.setProperty('--text-color', '#333');
}
}
- 响应式样式调整
// 监听视窗变化并动态调整样式
const mediaQuery = window.matchMedia('(max-width: 768px)');
function handleScreenChange(e) {
if (e.matches) {
// 小屏幕样式调整
document.body.style.fontSize = '14px';
} else {
// 大屏幕样式调整
document.body.style.fontSize = '16px';
}
}
mediaQuery.addListener(handleScreenChange);
最佳实践
-
性能优化
- 减少选择器复杂度
- 避免频繁修改样式
- 使用 CSS 类名切换代替直接样式操作
-
代码组织
- 将样式操作封装成可复用的函数
- 使用 CSS 变量实现主题系统
- 保持 CSS 结构清晰
-
调试技巧
- 使用浏览器开发工具的 Elements 面板
- 利用 Computed 标签页查看计算样式
- 使用 Sources 面板调试样式修改
深入CSSOM
1. CSSOM 的高级特性
1.1 视图相关 API (CSSOM View Module)
CSSOM View Module 提供了与元素视图相关的属性和方法:
// 1. 获取元素尺寸
const element = document.querySelector('.box');
// 元素完整尺寸(包括内边距和边框)
console.log(element.offsetWidth, element.offsetHeight);
// 元素内部尺寸(包括内边距)
console.log(element.clientWidth, element.clientHeight);
// 元素实际内容尺寸(包括溢出部分)
console.log(element.scrollWidth, element.scrollHeight);
// 2. 获取元素位置
// 相对于带定位父元素的偏移
console.log(element.offsetLeft, element.offsetTop);
// 相对于视口的位置
const rect = element.getBoundingClientRect();
console.log(rect.top, rect.left, rect.bottom, rect.right);
// 3. 视口信息
console.log(window.innerWidth, window.innerHeight); // 视口大小
console.log(window.pageXOffset, window.pageYOffset); // 页面滚动位置
1.2 CSS 自定义属性(CSS Variables)操作
// 1. 定义全局 CSS 变量
document.documentElement.style.setProperty('--primary-color', '#007bff');
// 2. 读取 CSS 变量
const styles = getComputedStyle(document.documentElement);
const primaryColor = styles.getPropertyValue('--primary-color');
// 3. 动态主题实现
class ThemeManager {
constructor() {
this.root = document.documentElement;
}
setTheme(theme) {
const themes = {
light: {
'--bg-color': '#ffffff',
'--text-color': '#333333',
'--shadow-color': 'rgba(0,0,0,0.1)'
},
dark: {
'--bg-color': '#333333',
'--text-color': '#ffffff',
'--shadow-color': 'rgba(255,255,255,0.1)'
}
};
Object.entries(themes[theme]).forEach(([property, value]) => {
this.root.style.setProperty(property, value);
});
}
}
2. 高级应用场景
2.1 响应式布局管理器
class ResponsiveLayoutManager {
constructor() {
this.breakpoints = {
mobile: 480,
tablet: 768,
desktop: 1024
};
this.mediaQueries = {};
this.init();
}
init() {
Object.entries(this.breakpoints).forEach(([device, width]) => {
const query = window.matchMedia(`(min-width: ${width}px)`);
query.addListener(this.handleBreakpoint.bind(this));
this.mediaQueries[device] = query;
});
}
handleBreakpoint(e) {
const html = document.documentElement;
Object.entries(this.mediaQueries).forEach(([device, query]) => {
if (query.matches) {
html.setAttribute('data-device', device);
}
});
}
}
const layoutManager = new ResponsiveLayoutManager();
2.2 动画性能优化器
class AnimationOptimizer {
static prepareForAnimation(element) {
// 强制浏览器创建新的图层,优化动画性能
element.style.willChange = 'transform';
// 使用 transform 代替位置属性
element.style.transform = 'translateZ(0)';
}
static cleanupAfterAnimation(element) {
// 动画结束后清理
element.style.willChange = 'auto';
}
static smoothAnimation(element, properties, duration = 300) {
this.prepareForAnimation(element);
const animations = Object.entries(properties).map(([prop, value]) => {
return element.animate({
[prop]: value
}, {
duration,
easing: 'cubic-bezier(0.4, 0, 0.2, 1)'
});
});
Promise.all(animations.map(anim => anim.finished))
.then(() => this.cleanupAfterAnimation(element));
}
}
2.3 样式隔离实现
class StyleIsolator {
constructor(scopeName) {
this.scopeName = scopeName;
this.styleSheet = this.createStyleSheet();
}
createStyleSheet() {
const style = document.createElement('style');
document.head.appendChild(style);
return style.sheet;
}
addRule(selector, cssText) {
// 添加作用域前缀
const scopedSelector = `.${this.scopeName} ${selector}`;
const rule = `${scopedSelector} { ${cssText} }`;
this.styleSheet.insertRule(rule, this.styleSheet.cssRules.length);
}
removeAllRules() {
while (this.styleSheet.cssRules.length > 0) {
this.styleSheet.deleteRule(0);
}
}
}
3. 性能优化最佳实践
3.1 批量样式修改
class StyleBatchProcessor {
static batchUpdate(element, updates) {
// 将元素脱离文档流
const display = element.style.display;
element.style.display = 'none';
// 批量更新样式
requestAnimationFrame(() => {
Object.entries(updates).forEach(([prop, value]) => {
element.style[prop] = value;
});
// 重新加入文档流
element.style.display = display;
});
}
}
// 使用示例
StyleBatchProcessor.batchUpdate(element, {
backgroundColor: '#fff',
transform: 'translateX(100px)',
opacity: '0.8'
});
3.2 样式计算缓存
class StyleCache {
constructor() {
this.cache = new WeakMap();
}
getComputedStyle(element, property) {
if (!this.cache.has(element)) {
this.cache.set(element, new Map());
}
const elementCache = this.cache.get(element);
if (!elementCache.has(property)) {
const value = window.getComputedStyle(element)[property];
elementCache.set(property, value);
}
return elementCache.get(property);
}
invalidate(element) {
this.cache.delete(element);
}
}
3.3 CSS 动画性能监控
class AnimationPerformanceMonitor {
static startMonitoring() {
this.observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'layout-shift') {
console.warn('Layout shift detected:', entry.value);
}
}
});
this.observer.observe({ entryTypes: ['layout-shift'] });
}
static measureAnimationPerformance(element, animationDuration) {
const startTime = performance.now();
const cleanup = () => {
const endTime = performance.now();
const duration = endTime - startTime;
if (duration > animationDuration * 1.1) {
console.warn('Animation performance issue detected');
}
};
element.addEventListener('transitionend', cleanup, { once: true });
}
}