引言:
CSS 编程是使用 CSS 变量、数学函数、条件逻辑、模块化作用域等特性构建复杂样式系统的工程化方法论。本文完整系统地讲解 CSS 自定义属性、calc/min/clamp 等运算函数、@media/@supports/@container 条件查询、@layer/@scope 模块化机制、设计系统与主题构建、CSS 与 JavaScript 协同,以及嵌套、:has()、视图过渡等最新演进特性,涵盖 CSS 从基础到工程化的完整知识体系。
CSS 编程:从样式表到真正的“工程化”
当我们谈论“CSS 编程”时,我们并不是在说 CSS 是一门像 JavaScript 那样的通用编程语言。CSS 本质上是一种声明式语言——你告诉浏览器“元素应该长什么样”,而不是“如何一步步计算出来”。
但是,随着 Web 应用变得越来越复杂,CSS 也在不断进化,获得了很多传统意义上属于“编程语言”的特性:变量、逻辑判断、数学计算、函数、作用域、模块化……这些能力让 CSS 从简单的“样式描述”变成了可以构建复杂设计系统的“工程化工具”。
本文将完整、系统地讲解 CSS 编程的方方面面,涵盖 CSS 变量、运算与函数、条件逻辑、模块化作用域、设计系统、主题切换、CSS 与 JavaScript 协同,以及 CSS 的最新演进特性。
引言:CSS 是编程语言吗?
这个问题在开发者社区一直有争议。我们可以从几个维度来看:
支持“CSS 是编程语言”的观点:
- 它有语法规则和语义。
- 它有计算能力(通过
calc()、min()、max() 等函数)。
- 它有变量(CSS 自定义属性)。
- 它有条件逻辑(通过
@media、@supports 查询)。
- 它有函数(颜色函数、数学函数、自定义函数等)。
反对观点:
- 没有循环和迭代结构。
- 无法定义算法或程序流程。
- 不能处理输入/输出(除了修改样式)。
- 缺少抽象数据类型。
结论:CSS 不是一种通用编程语言,但它是一种特定领域的编程语言(DSL,Domain Specific Language)。它的“编程”体现在:用声明式的方式描述视觉逻辑,用工程化的手段管理复杂样式系统。
第一章:CSS 变量——样式表的“存储引擎”
变量是任何编程语言的基石。CSS 自定义属性(CSS Variables)让样式表拥有了真正的“存储与复用”能力。
1.1 变量的声明与使用
声明一个自定义属性,需要使用以两个连字符 -- 开头的名称,并像普通属性一样赋予它一个值。
1 2 3 4 5 6 7 8 9 10
| :root { --primary-color: #3498db; --secondary-color: #2ecc71; --spacing-unit: 8px; --border-radius: 4px; --font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; --transition-duration: 0.3s; --shadow: 0 4px 6px rgba(0, 0, 0, 0.1); }
|
使用变量时,通过 var() 函数来引用它。var() 函数接受一个变量名作为必要参数,并可以接受一个可选的“回退值”,当变量未定义时使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| .button { background-color: var(--primary-color); padding: calc(var(--spacing-unit) * 2) calc(var(--spacing-unit) * 4); border-radius: var(--border-radius); font-family: var(--font-family); transition: all var(--transition-duration) ease; box-shadow: var(--shadow); }
.button-secondary { background-color: var(--secondary-color, #2ecc71); }
|
1.2 变量的作用域与继承
自定义属性遵循 CSS 的层叠和继承规则。这意味着你可以为不同的元素或类分别定义相同的变量名,从而实现局部覆盖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| :root { --bg-color: #ffffff; --text-color: #333333; --card-bg: #f5f5f5; }
.dark-theme { --bg-color: #1a1a1a; --text-color: #f0f0f0; --card-bg: #2d2d2d; }
.card { --card-bg: #ffffff; background: var(--card-bg); color: var(--text-color); }
.dark-theme .card { --card-bg: #2d2d2d; }
body { background: var(--bg-color); color: var(--text-color); }
|
1.3 JavaScript 操作 CSS 变量
CSS 变量是 CSS 和 JavaScript 之间的“桥梁”——JavaScript 可以读写 CSS 变量,实现动态样式控制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| const root = document.documentElement; const primaryColor = getComputedStyle(root) .getPropertyValue('--primary-color').trim(); console.log(primaryColor);
function setTheme(color) { document.documentElement.style.setProperty('--primary-color', color); }
const colorPicker = document.querySelector('#color-picker'); colorPicker.addEventListener('input', (e) => { document.documentElement.style.setProperty('--primary-color', e.target.value); });
function applyTheme(theme) { const root = document.documentElement; Object.entries(theme).forEach(([key, value]) => { root.style.setProperty(`--${key}`, value); }); }
const darkTheme = { 'bg-color': '#1a1a1a', 'text-color': '#f0f0f0', 'card-bg': '#2d2d2d' }; applyTheme(darkTheme);
|
1.4 CSS 变量的高级用法
在 CSS 变量中存储复杂值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| :root { --gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%); --gradient-secondary: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); --blur-filter: blur(8px); --brightness-filter: brightness(1.2); --card-transform: rotate(2deg) scale(1.02); }
.hero { background: var(--gradient-primary); }
.modal-backdrop { filter: var(--blur-filter); }
.card:hover { transform: var(--card-transform); }
|
变量与 calc() 结合:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| :root { --header-height: 60px; --footer-height: 80px; --content-min-height: calc(100vh - var(--header-height) - var(--footer-height)); }
.main-content { min-height: var(--content-min-height); }
:root { --container-padding: 16px; }
@media (min-width: 768px) { :root { --container-padding: 32px; } }
@media (min-width: 1200px) { :root { --container-padding: 48px; } }
.container { padding: 0 var(--container-padding); }
|
第二章:CSS 运算与函数
CSS 提供了多种内置函数和运算能力,可以进行数值计算和颜色处理。
2.1 数学运算函数
calc():四则运算
calc() 允许你在 CSS 属性值中进行加、减、乘、除运算,可以混合使用不同单位。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| .container { width: calc(100% - 40px); margin: calc(10px + 2vw); font-size: calc(1rem + 0.5vw); padding: calc((var(--spacing) * 2) + 4px); }
.centered { position: absolute; top: calc(50% - 50px); left: calc(50% - 50px); width: 100px; height: 100px; }
.grid { gap: calc(8px + (24 - 8) * ((100vw - 320px) / (1200 - 320))); }
|
min():取最小值
min() 函数从多个值中取最小值,非常适合设置响应式的最大尺寸。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| .container { width: min(1200px, 90%); }
.heading { font-size: min(3rem, 6vw); }
.sidebar { padding: min(2rem, 4%); }
|
max():取最大值
max() 函数从多个值中取最大值,适合设置响应式的最小尺寸。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| .container { width: max(300px, 70%); }
.text { font-size: max(1rem, 1.2vw); }
.element { padding: max(8px, 2%); }
|
clamp():在最小值和最大值之间取值
clamp() 是 min() 和 max() 的结合,接受三个参数:最小值、首选值、最大值。它是响应式设计的利器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| h1 { font-size: clamp(1.5rem, 4vw, 3rem); }
.container { width: clamp(320px, 80%, 1200px); }
.element { padding: clamp(0.5rem, 2vw, 2rem); }
.grid { grid-template-columns: repeat(auto-fit, minmax(clamp(150px, 25%, 300px), 1fr)); }
|
2.2 颜色函数
基础颜色函数:rgb()、rgba()、hsl()、hsla()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| .element { color: rgb(52, 152, 219); background: rgba(52, 152, 219, 0.5); }
.element { color: hsl(210, 80%, 50%); background: hsla(210, 80%, 50%, 0.5); }
:root { --brand-hue: 210; --brand-saturation: 80%; --brand-lightness: 50%; }
.button { background: hsl(var(--brand-hue), var(--brand-saturation), var(--brand-lightness)); }
.button:hover { background: hsl(var(--brand-hue), var(--brand-saturation), calc(var(--brand-lightness) - 10%)); }
|
color-mix():混合颜色(CSS Color Level 5)
color-mix() 允许在颜色空间中混合两种颜色,指定混合比例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| .button-primary { background: color-mix(in srgb, #3498db 70%, white); }
.button-primary-hover { background: color-mix(in srgb, #3498db 70%, black); }
:root { --primary: #3498db; } .button { background: color-mix(in srgb, var(--primary) 80%, var(--secondary) 20%); }
.element { background: color-mix(in hsl, red 50%, blue); border-color: color-mix(in oklch, #3498db 60%, #2ecc71); }
|
color-contrast():选择最佳对比色
color-contrast() 函数从多个候选颜色中选出与基准颜色对比度最高的那个,用于确保可访问性。
1 2 3 4 5 6 7 8 9 10 11 12 13
| .element { background: #3498db; color: color-contrast(#3498db vs white, black); }
.element { background: #1a1a1a; color: color-contrast(#1a1a1a vs white, #f0f0f0, #cccccc); }
|
其他颜色函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| .element { background: hsl(from var(--primary) h s calc(l + 10%)); }
:root { --primary: #3498db; --primary-light: hsl(from var(--primary) h s calc(l + 20%)); --primary-dark: hsl(from var(--primary) h s calc(l - 20%)); }
|
2.3 其他常用函数
attr():获取 HTML 属性值
attr() 函数允许从 HTML 元素的属性中获取值并用于样式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| .element { content: attr(data-label); background-color: attr(data-color, #3498db); }
.tooltip::after { content: attr(data-tooltip); position: absolute; background: #333; color: white; padding: 4px 8px; border-radius: 4px; font-size: 12px; }
|
1 2 3 4
| <div class="element" data-label="提示信息" data-color="#e74c3c"> 悬停查看提示 </div>
|
url():加载外部资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| .hero { background-image: url('/images/hero.jpg'); }
:root { --hero-image: url('/images/hero-default.jpg'); }
.hero { background-image: var(--hero-image); }
@media (min-resolution: 2dppx) { .hero { background-image: url('/images/hero@2x.jpg'); } }
|
var() 与自定义属性的组合使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| :root { --spacing-xs: 4px; --spacing-sm: 8px; --spacing-md: 16px; --spacing-lg: 24px; --spacing-xl: 32px; --spacing: var(--spacing-md); }
.card { padding: var(--spacing); margin-bottom: var(--spacing-lg); }
@media (max-width: 768px) { :root { --spacing: var(--spacing-sm); --spacing-lg: var(--spacing-md); } }
|
第三章:CSS 条件逻辑
CSS 中的条件逻辑主要通过 @media 查询、@supports 查询和 @container 查询 实现。
媒体查询是 CSS 最基础的条件逻辑,根据设备特性应用不同的样式。
视口相关查询:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| .grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
@media (max-width: 1024px) { .grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 768px) { .grid { grid-template-columns: 1fr; } }
@media (min-width: 768px) and (max-width: 1024px) { .sidebar { display: none; } }
@media (min-height: 800px) { .hero { min-height: 80vh; } }
|
设备特性查询:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| @media (prefers-color-scheme: dark) { :root { --bg: #1a1a1a; --text: #f0f0f0; --card-bg: #2d2d2d; --border-color: #404040; } }
@media (prefers-color-scheme: light) { :root { --bg: #ffffff; --text: #222222; --card-bg: #f5f5f5; --border-color: #e0e0e0; } }
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } }
@media (prefers-contrast: high) { :root { --text: #000000; --bg: #ffffff; --border-color: #000000; --shadow: none; } }
@media (prefers-color-scheme: dark) { .theme-toggle-icon { content: "🌙"; } }
@media (prefers-color-scheme: light) { .theme-toggle-icon { content: "☀️"; } }
|
设备类型查询:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @media print { .no-print { display: none !important; } body { background: white; color: black; font-size: 12pt; } a[href]::after { content: " (" attr(href) ")"; } }
@media speech { .sr-only { display: block; } }
@media (hover: none) and (pointer: coarse) { .button { min-height: 44px; min-width: 44px; padding: 12px 16px; } }
|
3.2 @supports 特性检测
@supports 查询允许检测浏览器是否支持某个 CSS 特性,从而提供回退方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| @supports (display: grid) { .container { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px; } }
@supports not (display: grid) { .container { display: flex; flex-wrap: wrap; } .container > * { flex: 1 1 33.333%; margin: 10px; } }
@supports (selector(&)) { .card { &__title { font-size: 1.5rem; } &__body { padding: 1rem; } } }
@supports (container-type: inline-size) { .card-container { container-type: inline-size; } @container (min-width: 400px) { .card { display: flex; flex-direction: row; } } }
@supports (grid-template-columns: subgrid) { .grid { display: grid; grid-template-columns: subgrid; } }
@supports (selector(:has(*))) { .container:has(.featured) { background: var(--highlight); } }
@supports (display: grid) and (gap: 20px) { .grid { display: grid; gap: 20px; } }
|
3.3 @container 容器查询
容器查询是 CSS 响应式设计的重大突破——它允许组件根据其容器的大小来调整样式,而不是视口大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| .card-container { container-type: inline-size; container-name: card; }
.card-wrapper { container: card / inline-size; }
@container card (min-width: 400px) { .card { display: flex; flex-direction: row; align-items: center; } .card-image { flex: 0 0 40%; margin-right: 20px; } }
@container card (max-width: 399px) { .card { display: flex; flex-direction: column; } .card-image { margin-bottom: 16px; } }
@container card (min-height: 300px) { .card-content { display: flex; flex-direction: column; justify-content: center; } }
@container card (min-width: 400px) and (max-width: 800px) { .card { padding: 16px; } }
.product-card { container: product / inline-size; }
@container product (min-width: 300px) { .product-card { display: grid; grid-template-columns: 120px 1fr; gap: 16px; } }
@container product (min-width: 500px) { .product-card { grid-template-columns: 200px 1fr; gap: 24px; } .product-title { font-size: 1.5rem; } }
|
第四章:CSS 模块化与作用域
4.1 @layer 级联层
@layer 是 CSS 的一个相对较新的特性,用于控制样式规则在不同层级之间的优先级,而不需要依赖选择器权重或 !important。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| @layer reset, base, components, utilities, overrides;
@layer reset { * { margin: 0; padding: 0; box-sizing: border-box; } body { line-height: 1.5; } }
@layer base { :root { --primary: #3498db; --text: #333; } body { font-family: 'Inter', sans-serif; color: var(--text); background: #fff; } }
@layer components { .button { display: inline-block; padding: 8px 16px; background: var(--primary); color: white; border: none; border-radius: 4px; cursor: pointer; } .card { padding: 20px; background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } }
@layer utilities { .text-center { text-align: center; } .mt-1 { margin-top: 8px; } .mt-2 { margin-top: 16px; } }
@layer framework { @layer components { .button { } } }
@import url('reset.css') layer(reset); @import url('components.css') layer(components);
|
4.2 @scope 作用域
@scope 是一项较新的 CSS 特性,允许将样式限制在 DOM 树的特定范围内,从而避免样式泄漏。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| @scope (.card) { .title { font-size: 1.5rem; font-weight: bold; color: var(--text-primary); } .body { padding: 1rem; line-height: 1.6; } .footer { padding: 0.75rem 1rem; border-top: 1px solid var(--border); font-size: 0.875rem; color: var(--text-secondary); } }
@scope (.card) to (.card-inner) { .title { } }
@scope (.sidebar) { .nav-item { padding: 8px 16px; } @scope (.nav-section) { .nav-item { padding-left: 32px; } } }
@scope (.card) { .title { font-size: 1.25rem; } @media (min-width: 768px) { .title { font-size: 1.75rem; } } }
|
4.3 CSS 模块(构建工具层面)
CSS 模块是通过构建工具(如 Webpack、Vite)实现的样式作用域隔离,它会在构建时为每个类名生成唯一的哈希值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| .button { background: #3498db; color: white; padding: 8px 16px; border-radius: 4px; }
.button-primary { background: #2ecc71; }
.container { padding: 20px; max-width: 1200px; margin: 0 auto; }
|
1 2 3 4 5 6 7 8 9
| import styles from './styles.module.css';
<div className={styles.container}> <button className={`${styles.button} ${styles.buttonPrimary}`}> 点击我 </button> </div>
|
构建后,类名会被转换为类似 .button_abc123 的格式,确保样式不会污染全局。
4.4 CSS-in-JS(运行时作用域)
CSS-in-JS 是在 JavaScript 中定义样式的方案,天然具有作用域隔离。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import styled from 'styled-components';
const Button = styled.button` background: #3498db; color: white; padding: 8px 16px; border-radius: 4px; &:hover { background: #2980b9; } `;
const PrimaryButton = styled(Button)` background: #2ecc71; &:hover { background: #27ae60; } `;
<Button>普通按钮</Button> <PrimaryButton>主要按钮</PrimaryButton>
|
1 2 3 4 5 6 7 8 9 10 11
| import { css } from '@emotion/react';
const buttonStyle = css` background: #3498db; color: white; padding: 8px 16px; border-radius: 4px; `;
<button css={buttonStyle}>点击我</button>
|
第五章:设计系统与 CSS 编程
设计系统(Design System)是一套可复用的设计规范和组件库,而 CSS 变量是实现设计系统的最佳工具。
5.1 设计令牌(Design Tokens)
设计令牌是设计系统中最小、最基础的样式单元。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
| :root { --color-brand-50: #ebf5fb; --color-brand-100: #d6eaf8; --color-brand-200: #aed6f1; --color-brand-300: #85c1e9; --color-brand-400: #5dade2; --color-brand-500: #3498db; --color-brand-600: #2e86c1; --color-brand-700: #2874a6; --color-brand-800: #21618c; --color-brand-900: #1a5276; --color-neutral-50: #f8f9fa; --color-neutral-100: #f1f3f4; --color-neutral-200: #e9ecef; --color-neutral-300: #dee2e6; --color-neutral-400: #ced4da; --color-neutral-500: #adb5bd; --color-neutral-600: #6c757d; --color-neutral-700: #495057; --color-neutral-800: #343a40; --color-neutral-900: #212529; --color-success: #2ecc71; --color-warning: #f39c12; --color-danger: #e74c3c; --color-info: #3498db; --font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; --font-family-heading: 'Poppins', 'Inter', sans-serif; --font-family-mono: 'JetBrains Mono', 'Fira Code', monospace; --font-size-xs: 0.75rem; --font-size-sm: 0.875rem; --font-size-base: 1rem; --font-size-lg: 1.125rem; --font-size-xl: 1.25rem; --font-size-2xl: 1.5rem; --font-size-3xl: 1.875rem; --font-size-4xl: 2.25rem; --font-size-5xl: 3rem; --font-weight-normal: 400; --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700; --font-weight-extrabold: 800; --line-height-tight: 1.25; --line-height-normal: 1.5; --line-height-loose: 1.75; --space-0: 0; --space-1: 0.25rem; --space-2: 0.5rem; --space-3: 0.75rem; --space-4: 1rem; --space-5: 1.25rem; --space-6: 1.5rem; --space-7: 1.75rem; --space-8: 2rem; --space-9: 2.25rem; --space-10: 2.5rem; --space-11: 2.75rem; --space-12: 3rem; --space-14: 3.5rem; --space-16: 4rem; --space-20: 5rem; --space-24: 6rem; --radius-none: 0; --radius-sm: 0.125rem; --radius-base: 0.25rem; --radius-md: 0.375rem; --radius-lg: 0.5rem; --radius-xl: 0.75rem; --radius-2xl: 1rem; --radius-3xl: 1.5rem; --radius-full: 9999px; --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.05); --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06); --shadow-base: 0 4px 6px rgba(0, 0, 0, 0.07), 0 2px 4px rgba(0, 0, 0, 0.06); --shadow-md: 0 10px 15px rgba(0, 0, 0, 0.1), 0 4px 6px rgba(0, 0, 0, 0.05); --shadow-lg: 0 20px 25px rgba(0, 0, 0, 0.1), 0 10px 10px rgba(0, 0, 0, 0.04); --shadow-xl: 0 25px 50px rgba(0, 0, 0, 0.15); --shadow-2xl: 0 50px 100px rgba(0, 0, 0, 0.2); --transition-fast: 0.15s ease; --transition-base: 0.3s ease; --transition-slow: 0.5s ease; --transition-slower: 0.8s ease; --container-sm: 640px; --container-md: 768px; --container-lg: 1024px; --container-xl: 1280px; --container-2xl: 1536px; }
|
5.2 基于设计令牌的组件构建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
|
.btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); padding: var(--space-2) var(--space-5); font-family: var(--font-family-base); font-size: var(--font-size-base); font-weight: var(--font-weight-medium); line-height: var(--line-height-normal); text-decoration: none; border: 1px solid transparent; border-radius: var(--radius-md); cursor: pointer; transition: all var(--transition-fast); &:disabled { opacity: 0.6; cursor: not-allowed; } }
.btn-primary { background: var(--color-brand-500); color: white; &:hover:not(:disabled) { background: var(--color-brand-600); } &:active:not(:disabled) { background: var(--color-brand-700); } }
.btn-secondary { background: var(--color-neutral-200); color: var(--color-neutral-800); &:hover:not(:disabled) { background: var(--color-neutral-300); } }
.btn-success { background: var(--color-success); color: white; &:hover:not(:disabled) { filter: brightness(0.9); } }
.btn-danger { background: var(--color-danger); color: white; &:hover:not(:disabled) { filter: brightness(0.9); } }
.btn-sm { padding: var(--space-1) var(--space-3); font-size: var(--font-size-sm); }
.btn-lg { padding: var(--space-3) var(--space-7); font-size: var(--font-size-lg); }
.card { background: white; border-radius: var(--radius-lg); box-shadow: var(--shadow-base); overflow: hidden; transition: box-shadow var(--transition-base); &:hover { box-shadow: var(--shadow-lg); } }
.card-header { padding: var(--space-5) var(--space-5) var(--space-3); border-bottom: 1px solid var(--color-neutral-200); }
.card-title { font-family: var(--font-family-heading); font-size: var(--font-size-xl); font-weight: var(--font-weight-semibold); color: var(--color-neutral-900); margin: 0; }
.card-body { padding: var(--space-5); }
.card-footer { padding: var(--space-3) var(--space-5); background: var(--color-neutral-50); border-top: 1px solid var(--color-neutral-200); }
.input { display: block; width: 100%; padding: var(--space-2) var(--space-3); font-family: var(--font-family-base); font-size: var(--font-size-base); line-height: var(--line-height-normal); color: var(--color-neutral-800); background: white; border: 1px solid var(--color-neutral-300); border-radius: var(--radius-md); transition: border-color var(--transition-fast), box-shadow var(--transition-fast); &:focus { outline: none; border-color: var(--color-brand-500); box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.15); } &::placeholder { color: var(--color-neutral-400); } &:disabled { background: var(--color-neutral-100); cursor: not-allowed; } }
|
5.3 主题系统
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
|
:root, [data-theme="light"] { --theme-bg: #ffffff; --theme-text: #212529; --theme-text-secondary: #6c757d; --theme-card-bg: #ffffff; --theme-card-border: #e9ecef; --theme-input-bg: #ffffff; --theme-input-border: #ced4da; --theme-hover: #f8f9fa; --theme-shadow: 0 4px 6px rgba(0, 0, 0, 0.07); --theme-shadow-lg: 0 20px 25px rgba(0, 0, 0, 0.1); --theme-scrollbar-track: #f1f3f4; --theme-scrollbar-thumb: #ced4da; }
[data-theme="dark"] { --theme-bg: #121212; --theme-text: #e4e4e4; --theme-text-secondary: #a0a0a0; --theme-card-bg: #1e1e1e; --theme-card-border: #333333; --theme-input-bg: #2d2d2d; --theme-input-border: #404040; --theme-hover: #2d2d2d; --theme-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); --theme-shadow-lg: 0 20px 25px rgba(0, 0, 0, 0.4); --theme-scrollbar-track: #2d2d2d; --theme-scrollbar-thumb: #555555; }
[data-theme="high-contrast"] { --theme-bg: #ffffff; --theme-text: #000000; --theme-text-secondary: #000000; --theme-card-bg: #ffffff; --theme-card-border: #000000; --theme-input-bg: #ffffff; --theme-input-border: #000000; --theme-hover: #f0f0f0; --theme-shadow: none; --theme-shadow-lg: none; }
body { background: var(--theme-bg); color: var(--theme-text); transition: background var(--transition-slow), color var(--transition-slow); }
.card { background: var(--theme-card-bg); border: 1px solid var(--theme-card-border); box-shadow: var(--theme-shadow); }
.input { background: var(--theme-input-bg); border-color: var(--theme-input-border); color: var(--theme-text); }
[data-theme="dark"] { scrollbar-color: var(--theme-scrollbar-thumb) var(--theme-scrollbar-track); scrollbar-width: thin; }
|
第六章:CSS 与 JavaScript 的协同编程
6.1 通过 CSS 变量动态控制样式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
| class ThemeManager { constructor() { this.root = document.documentElement; this.currentTheme = this.loadTheme() || 'light'; this.themes = { light: { 'theme-bg': '#ffffff', 'theme-text': '#212529', 'theme-text-secondary': '#6c757d', 'theme-card-bg': '#ffffff', 'theme-card-border': '#e9ecef', 'theme-input-bg': '#ffffff', 'theme-input-border': '#ced4da', 'theme-hover': '#f8f9fa', 'theme-shadow': '0 4px 6px rgba(0,0,0,0.07)', 'theme-shadow-lg': '0 20px 25px rgba(0,0,0,0.1)', }, dark: { 'theme-bg': '#121212', 'theme-text': '#e4e4e4', 'theme-text-secondary': '#a0a0a0', 'theme-card-bg': '#1e1e1e', 'theme-card-border': '#333333', 'theme-input-bg': '#2d2d2d', 'theme-input-border': '#404040', 'theme-hover': '#2d2d2d', 'theme-shadow': '0 4px 6px rgba(0,0,0,0.3)', 'theme-shadow-lg': '0 20px 25px rgba(0,0,0,0.4)', }, 'high-contrast': { 'theme-bg': '#ffffff', 'theme-text': '#000000', 'theme-text-secondary': '#000000', 'theme-card-bg': '#ffffff', 'theme-card-border': '#000000', 'theme-input-bg': '#ffffff', 'theme-input-border': '#000000', 'theme-hover': '#f0f0f0', 'theme-shadow': 'none', 'theme-shadow-lg': 'none', } }; this.applyTheme(this.currentTheme); this.setupSystemPreferenceListener(); } loadTheme() { return localStorage.getItem('theme'); } saveTheme(theme) { localStorage.setItem('theme', theme); } applyTheme(theme) { if (!this.themes[theme]) return; this.root.setAttribute('data-theme', theme); const themeVars = this.themes[theme]; Object.entries(themeVars).forEach(([key, value]) => { this.root.style.setProperty(`--${key}`, value); }); this.currentTheme = theme; this.saveTheme(theme); this.dispatchThemeChange(theme); } toggleTheme() { const themes = ['light', 'dark']; const nextIndex = (themes.indexOf(this.currentTheme) + 1) % themes.length; this.applyTheme(themes[nextIndex]); } getCurrentTheme() { return this.currentTheme; } setupSystemPreferenceListener() { const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); mediaQuery.addEventListener('change', (e) => { if (!localStorage.getItem('theme')) { this.applyTheme(e.matches ? 'dark' : 'light'); } }); } dispatchThemeChange(theme) { window.dispatchEvent(new CustomEvent('themeChange', { detail: { theme } })); } }
const themeManager = new ThemeManager();
document.querySelector('#theme-toggle').addEventListener('click', () => { themeManager.toggleTheme(); });
window.addEventListener('themeChange', (e) => { console.log('主题已切换为:', e.detail.theme); });
|
6.2 CSS 自定义属性与动画控制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| class AnimationController { constructor() { this.root = document.documentElement; this.defaults = { duration: 0.3, easing: 'ease', delay: 0 }; } setAnimation(selector, properties) { const element = document.querySelector(selector); if (!element) return; const { duration, easing, delay } = { ...this.defaults, ...properties }; element.style.setProperty('--anim-duration', `${duration}s`); element.style.setProperty('--anim-easing', easing); element.style.setProperty('--anim-delay', `${delay}s`); } animateIn(selector) { this.setAnimation(selector, { duration: 0.5, easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)' }); document.querySelector(selector)?.classList.add('anim-in'); } animateOut(selector) { this.setAnimation(selector, { duration: 0.3, easing: 'ease-in' }); document.querySelector(selector)?.classList.add('anim-out'); } }
|
6.3 响应式断点的 JavaScript 管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| class BreakpointManager { constructor() { this.breakpoints = { sm: 640, md: 768, lg: 1024, xl: 1280, '2xl': 1536 }; this.current = this.detect(); this.setupListener(); } detect() { const width = window.innerWidth; let current = 'xs'; for (const [name, size] of Object.entries(this.breakpoints)) { if (width >= size) { current = name; } } return current; } setupListener() { let timeout; window.addEventListener('resize', () => { clearTimeout(timeout); timeout = setTimeout(() => { const newBreakpoint = this.detect(); if (newBreakpoint !== this.current) { this.current = newBreakpoint; window.dispatchEvent(new CustomEvent('breakpointChange', { detail: { breakpoint: this.current } })); } }, 100); }); } isAbove(breakpoint) { const sizes = Object.values(this.breakpoints); const currentWidth = window.innerWidth; return currentWidth >= (this.breakpoints[breakpoint] || 0); } isBelow(breakpoint) { const sizes = Object.values(this.breakpoints); const currentWidth = window.innerWidth; return currentWidth < (this.breakpoints[breakpoint] || 0); } }
const breakpoints = new BreakpointManager();
window.addEventListener('breakpointChange', (e) => { console.log('当前断点:', e.detail.breakpoint); });
if (breakpoints.isAbove('lg')) { }
|
6.4 高性能动画与 requestAnimationFrame
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| class VariableAnimation { constructor(element, variableName, config) { this.element = element; this.variableName = variableName; this.config = { from: 0, to: 1, duration: 1000, easing: this.easeInOut, ...config }; this.running = false; } easeInOut(t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; } start() { if (this.running) return; this.running = true; this.startTime = performance.now(); this.animate(); } animate() { if (!this.running) return; const elapsed = performance.now() - this.startTime; const progress = Math.min(elapsed / this.config.duration, 1); const easedProgress = this.config.easing(progress); const value = this.config.from + (this.config.to - this.config.from) * easedProgress; this.element.style.setProperty(this.variableName, value); if (progress < 1) { requestAnimationFrame(() => this.animate()); } else { this.running = false; this.element.style.setProperty(this.variableName, this.config.to); } } stop() { this.running = false; } }
const element = document.querySelector('.progress-bar'); const anim = new VariableAnimation(element, '--progress', { from: 0, to: 100, duration: 2000 }); anim.start();
|
第七章:CSS 最新演进特性
7.1 CSS 嵌套(Nesting)
CSS 嵌套模块让样式表结构更清晰,更接近 HTML 结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| .card { padding: 20px; } .card .card-title { font-size: 1.5rem; } .card .card-title:hover { color: blue; } .card .card-body { padding: 16px; }
.card { padding: 20px; .card-title { font-size: 1.5rem; &:hover { color: blue; } } .card-body { padding: 16px; } }
.nav { display: flex; gap: 8px; &-list { list-style: none; padding: 0; margin: 0; } &-item { display: inline-block; &.active { background: var(--primary); color: white; } } &-link { text-decoration: none; padding: 8px 16px; display: block; &:hover { background: var(--hover-bg); } &::after { content: '→'; margin-left: 4px; } } }
.card { display: flex; flex-direction: column; @media (min-width: 768px) { flex-direction: row; .card-image { flex: 0 0 40%; } } }
|
7.2 CSS 作用域(Scoping)
@scope 规则允许精确控制样式的作用范围。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @scope (.component) { .title { font-weight: bold; } .body { padding: 1rem; } }
@scope (.card) to (.card-inner) { .title { } }
@scope (.sidebar) { .nav-item { padding: 8px; } @scope (.nav-section) { .nav-item { padding-left: 24px; } } }
|
7.3 CSS 函数与混入(Functions and Mixins)
CSS Functions and Mixins 模块(W3C 工作草案)将带来真正的"代码复用"能力。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @function --fluid-font(--min, --pref, --max) { result: clamp(var(--min), var(--pref), var(--max)); }
.heading { font-size: --fluid-font(1.5rem, 4vw, 3rem); }
@mixin --card-styles(--bg, --radius, --shadow) { background: var(--bg); border-radius: var(--radius); box-shadow: var(--shadow); padding: 1.5rem; }
.card-primary { @apply --card-styles(white, 8px, var(--shadow-md)); }
.card-dark { @apply --card-styles(#1a1a1a, 12px, var(--shadow-lg)); }
|
滚动驱动动画允许动画由滚动进度控制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| @keyframes progress { from { width: 0%; } to { width: 100%; } }
.progress-bar { animation: progress linear; animation-timeline: scroll(root); animation-range: contain; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
.anim-on-scroll { animation: fadeIn ease both; animation-timeline: view(); animation-range: entry 0% entry 100%; }
.sticky-element { position: sticky; top: 20px; animation: sticky-rotate linear; animation-timeline: scroll(root); animation-range: contain; }
@keyframes sticky-rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
7.5 CSS 视图过渡(View Transitions)
视图过渡 API 允许在页面状态变化时创建平滑的动画过渡。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| ::view-transition-old(root) { animation: fadeOut 0.3s ease forwards; }
::view-transition-new(root) { animation: fadeIn 0.3s ease forwards; }
@keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.smooth-transition { view-transition-name: main-content; }
|
7.6 CSS :has() 选择器
:has() 选择器允许根据子元素或后续兄弟元素来选择父元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| .card:has(img) { padding: 0; }
.card:has(.card-title) { background: var(--highlight-bg); }
.container:has(.btn-primary) { border-color: var(--primary); }
.form-group:has(.input:invalid) { .form-label { color: var(--danger); } }
.article:has(h2:nth-of-type(3)) { }
main:has(~ .sidebar) { grid-template-columns: 1fr 300px; }
label:has(input:checked) { font-weight: bold; color: var(--primary); }
|
第八章:CSS 性能优化与最佳实践
8.1 选择器性能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| * .item { }
.item { }
ul li:last-child { }
.item:last-child { }
input[type="text"] { }
.input-text { }
|
8.2 重排与重绘优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
.element { transition: transform 0.3s ease, opacity 0.3s ease; }
.element:hover { transform: scale(1.05); opacity: 0.9; }
.element { transition: width 0.3s ease, height 0.3s ease; }
.element:hover { width: 120px; height: 120px; }
|
8.3 高效使用 CSS 变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| :root { --color: #3498db; }
.button { background: var(--color); }
:root { --width: 100px; --height: 100px; --margin: 10px; }
.element { width: var(--width); height: var(--height); margin: var(--margin); }
|
8.4 CSS 代码组织最佳实践
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| @layer reset, base, components, utilities;
.card { }
.card__title { } .card__body { } .card__footer { }
.card--featured { } .card__title--large { }
.element { padding: 8px; font-size: 14px; }
@media (min-width: 768px) { .element { padding: 16px; font-size: 16px; } }
@media (min-width: 1024px) { .element { padding: 24px; font-size: 18px; } }
|
总结
CSS 已经从单纯的“描述性语言”演变成为一种工程化的设计语言。它拥有了变量、运算、条件逻辑、模块化等编程语言的特性,让开发者能够构建复杂、可维护、可扩展的样式系统。
本文完整系统地讲解了 CSS 编程的各个方面:
- CSS 变量:实现了样式的存储、复用和动态控制
- 运算与函数:提供了数学计算、颜色处理等能力
- 条件逻辑:通过
@media、@supports、@container 实现了响应式和特性检测
- 模块化与作用域:通过
@layer、@scope、CSS 模块等实现了样式封装
- 设计系统:构建了完整的令牌体系和主题系统
- CSS 与 JavaScript 协同:实现了动态样式控制和主题切换
- 最新演进特性:涵盖了嵌套、
:has()、视图过渡等现代特性
“CSS 编程”的核心在于:用编程思维组织样式代码、用工程化手段管理设计系统、用声明式方式表达视觉逻辑。理解这一点,你就能真正驾驭 CSS,让它成为构建 Web 应用最得心应手的工具之一。