CSS 自定义属性(变量)完全指南:主题与动态样式
CSS 自定义属性(通常称为“CSS 变量”)是现代 CSS 中最强大的特性之一。它们支持主题化、动态值,并显著减少重复。
基本语法
/* 在 :root 上定义(全局作用域) */
:root {
--color-primary: #3b82f6;
--color-secondary: #6366f1;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--border-radius: 8px;
--font-size-base: 16px;
--font-family: 'Inter', system-ui, sans-serif;
}
/* 使用 var() */
.button {
background: var(--color-primary);
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--border-radius);
font-family: var(--font-family);
}
回退值
/* var(--property, fallback) */
.card {
background: var(--card-bg, white); /* 如果 --card-bg 未定义则使用 white */
padding: var(--card-padding, 24px);
/* 嵌套回退 */
color: var(--text-color, var(--color-neutral-900, #111));
}
作用域与继承
/* 全局(所有地方可用) */
:root {
--font-size: 16px;
}
/* 组件作用域 */
.card {
--card-padding: 24px;
--card-border: 1px solid #e5e7eb;
padding: var(--card-padding);
border: var(--card-border);
}
/* 嵌套:覆盖父作用域 */
.card.card--compact {
--card-padding: 12px; /* 仅影响 .card.card--compact 及其子元素 */
}
暗黑模式主题
/* 亮色模式(默认) */
:root {
--color-bg: #ffffff;
--color-bg-secondary: #f9fafb;
--color-text: #111827;
--color-text-secondary: #6b7280;
--color-border: #e5e7eb;
--color-primary: #3b82f6;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
/* 暗黑模式 */
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #111827;
--color-bg-secondary: #1f2937;
--color-text: #f9fafb;
--color-text-secondary: #9ca3af;
--color-border: #374151;
--color-primary: #60a5fa;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
}
/* 或者使用类(由 JavaScript 切换) */
[data-theme="dark"] {
--color-bg: #111827;
--color-text: #f9fafb;
/* ... */
}
// 切换暗黑模式
const toggle = document.getElementById('theme-toggle');
toggle.addEventListener('click', () => {
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
document.documentElement.setAttribute('data-theme', isDark ? 'light' : 'dark');
localStorage.setItem('theme', isDark ? 'light' : 'dark');
});
// 页面加载时恢复
const saved = localStorage.getItem('theme');
if (saved) document.documentElement.setAttribute('data-theme', saved);
设计令牌系统
:root {
/* ── 原始值 ── */
--blue-50: #eff6ff;
--blue-100: #dbeafe;
--blue-500: #3b82f6;
--blue-600: #2563eb;
--blue-900: #1e3a8a;
--gray-50: #f9fafb;
--gray-100: #f3f4f6;
--gray-500: #6b7280;
--gray-900: #111827;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-6: 24px;
--space-8: 32px;
/* ── 语义令牌(在组件中使用这些) ── */
--color-primary: var(--blue-500);
--color-primary-hover: var(--blue-600);
--color-text: var(--gray-900);
--color-text-muted: var(--gray-500);
--color-bg: white;
--color-bg-subtle: var(--gray-50);
--color-border: var(--gray-100);
--spacing-component: var(--space-4);
--spacing-section: var(--space-8);
/* ── 组件令牌 ── */
--button-padding: var(--space-2) var(--space-4);
--card-padding: var(--space-6);
--input-height: 40px;
--border-radius-sm: 4px;
--border-radius-md: 8px;
--border-radius-lg: 12px;
}
JavaScript 集成
// 读取 CSS 变量值
const root = document.documentElement;
const primaryColor = getComputedStyle(root).getPropertyValue('--color-primary').trim();
console.log(primaryColor); // '#3b82f6'
// 从 JavaScript 设置 CSS 变量
root.style.setProperty('--color-primary', '#ef4444');
// 动态主题
function applyTheme(colors) {
Object.entries(colors).forEach(([key, value]) => {
root.style.setProperty(`--color-${key}`, value);
});
}
applyTheme({ primary: '#8b5cf6', secondary: '#ec4899' });
// 使用 CSS 变量动画
let hue = 0;
function animateHue() {
root.style.setProperty('--hue', hue++);
if (hue > 360) hue = 0;
requestAnimationFrame(animateHue);
}
响应式值
:root {
--container-padding: 16px;
--font-size-h1: 2rem;
--columns: 1;
}
@media (min-width: 640px) {
:root {
--container-padding: 24px;
--font-size-h1: 2.5rem;
--columns: 2;
}
}
@media (min-width: 1024px) {
:root {
--container-padding: 48px;
--font-size-h1: 3rem;
--columns: 3;
}
}
.container { padding: 0 var(--container-padding); }
h1 { font-size: var(--font-size-h1); }
.grid { grid-template-columns: repeat(var(--columns), 1fr); }
CSS 变量与 calc()
:root {
--base-size: 8px;
--sidebar-width: 260px;
}
.grid {
grid-template-columns: var(--sidebar-width) 1fr;
}
/* 响应式侧边栏,在平板上变窄 */
.content {
max-width: calc(100% - var(--sidebar-width) - 32px);
}
/* 缩放排版 */
h1 { font-size: calc(var(--base-size) * 4); } /* 32px */
h2 { font-size: calc(var(--base-size) * 3); } /* 24px */
p { font-size: calc(var(--base-size) * 2); } /* 16px */
组件模式
/* 按钮组件,带有可自定义的变量 */
.btn {
/* 组件默认值 */
--btn-bg: var(--color-primary);
--btn-text: white;
--btn-padding: 0.5rem 1rem;
--btn-radius: var(--border-radius-md);
--btn-font-size: 0.875rem;
/* 使用变量 */
background: var(--btn-bg);
color: var(--btn-text);
padding: var(--btn-padding);
border-radius: var(--btn-radius);
font-size: var(--btn-font-size);
border: none;
cursor: pointer;
}
/* 变体:仅覆盖变量 */
.btn-danger { --btn-bg: #ef4444; }
.btn-success { --btn-bg: #22c55e; }
.btn-outline {
--btn-bg: transparent;
--btn-text: var(--color-primary);
border: 1px solid var(--color-primary);
}
/* 尺寸变体 */
.btn-lg { --btn-padding: 0.75rem 1.5rem; --btn-font-size: 1rem; }
.btn-sm { --btn-padding: 0.25rem 0.5rem; --btn-font-size: 0.75rem; }
CSS 变量不能做什么
/* ❌ 不能在媒体查询值中使用 */
:root { --breakpoint-md: 768px; }
@media (min-width: var(--breakpoint-md)) { } /* 无效! */
/* ❌ 不能用作属性名 */
.item { var(--property): red; } /* 无效! */
/* ✅ 变通方案:使用 @custom-media(未来规范)或 JS */
→ 使用 颜色转换器 转换和探索颜色格式。