第四篇 强化视觉:字体、动画、变换
目标:在掌握盒模型和布局之后,进一步提升页面的"质感"。看完这一篇,你应该能:
- 为不同场景选择合适的字体、字号、行高和字重;
- 使用过渡和动画让交互变得自然、不突兀;
- 用 2D/3D 变换给元素增加层次感,而不是简单粗暴地"放大缩小"。
第9章 字体:让文字拥有自己的个性
这一章解决的核心问题:
- 中文 Web 项目里应该选哪些字体?怎么写回退字体?
- 字号、行高、字重如何搭配才舒服?
@font-face怎么加载自定义字体?加载不出来时会发生什么?用一句话概括:让页面上的文字既好看又好读。
9.1 字体族与字体文件
9.1.1 字体族(font-family)基础
CSS 中的 font-family 就是用来指定字体的。常见写法:
css
body {
/* font-family: 设置字体族,浏览器会按顺序查找可用字体 */
font-family: "PingFang SC", /* 苹果系统的苹方字体 */
"Microsoft YaHei", /* Windows系统的微软雅黑 */
"Noto Sans SC", /* Google的思源黑体 */
Arial, /* 西文备选字体 */
sans-serif; /* 无衬线字体族(最终兜底) */
}
这里包含几类字体:
- 具体字体:
"PingFang SC"(苹方)、"Microsoft YaHei"(微软雅黑)、"Noto Sans SC"等 - 西文字体:
Arial - 泛型字体族(generic family):
sans-serif(无衬线)、serif(衬线)、monospace(等宽)
浏览器的选择流程大致是:
- 按顺序找你写的字体
- 找到第一个当前系统有的
- 如果都没有,就退回到泛型字体族
可以用一个小图来理解:
txt
"PingFang SC", "Microsoft YaHei", "Noto Sans SC", Arial, sans-serif
↓ ↓ ↓ ↓ ↓
先尝试 再尝试 再尝试 再尝试 最后至少给我一个无衬线
实战建议:
- 中文环境下,推荐写一个「中文 + 西文 + 泛型」的链条
- 不要只写
Microsoft YaHei或SimSun之类单个字体,这样在 macOS / Linux 上效果会很差
9.1.2 常用中文 Web 字体组合
一个通用、比较稳妥的组合:
css
body {
font-family:
/* 系统UI字体(优先使用操作系统的界面字体) */
-apple-system, BlinkMacSystemFont, /* macOS/iOS系统字体 */
"Segoe UI", "Roboto", "Helvetica Neue", /* Windows/Android/老版macOS */
/* 中文字体族 */
"PingFang SC", /* macOS/iOS中文字体 */
"Hiragino Sans GB", /* macOS日文字体(支持中文) */
"Microsoft YaHei", /* Windows中文字体 */
"Noto Sans CJK SC", /* Google思源黑体简体 */
"Source Han Sans SC", /* Adobe思源黑体简体 */
/* 西文兜底 */
Arial, sans-serif; /* 最终备选方案 */
}
含义大致是:
- 优先使用系统 UI 字体(iOS/macOS 上的系统字体)
- 再退到常见的中文无衬线字体
- 最后至少还有一个通用的
sans-serif
你可以根据项目受众(Windows 为主还是移动端为主)去微调这份列表,但整体思路类似:先系统,后常见中文,再兜底。
9.1.3 字体文件格式与兼容性
在 Web 上常见的字体文件格式:
ttf/otf:传统桌面字体格式woff:为 Web 优化的字体格式,压缩率更高woff2:更现代的压缩,更省流量(现代浏览器支持好)
在实际项目里,通常建议:
- 优先提供
woff2+woff - 必要时提供
ttf作为兜底
注意:字体文件很大,乱用自定义字体会严重拖慢首屏加载,这一点在后面性能部分会再强调。
9.2 @font-face 的加载
@font-face 允许你引入自定义字体(如品牌专用字体或图标字体)。
9.2.1 基本用法
css
/* @font-face: 自定义字体声明规则 */
@font-face {
font-family: "MyBrandFont"; /* 给自定义字体起个名字 */
/* src: 字体文件的来源,按顺序尝试加载 */
src: url("/fonts/MyBrandFont.woff2") format("woff2"), /* 优先:最新压缩格式 */
url("/fonts/MyBrandFont.woff") format("woff"); /* 备选:旧版压缩格式 */
font-weight: 400; /* 声明这是正常粗细(normal)的字形 */
font-style: normal; /* 声明这是正体(非斜体)字形 */
/* font-display: 控制字体加载期间的文字显示策略 */
font-display: swap; /* swap=先显示备用字体,加载完成后切换 */
}
.brand-title {
/* 使用自定义字体,失败时退回到系统字体 */
font-family: "MyBrandFont", /* 优先使用自定义字体 */
system-ui, /* 退回系统UI字体 */
-apple-system, /* Apple系统字体 */
BlinkMacSystemFont, /* Chrome on Mac字体 */
"Segoe UI", /* Windows字体 */
sans-serif; /* 最终兜底方案 */
}
关键点解释:
font-family:给这套字体起一个名字(CSS 内部用)src:列出字体文件及格式font-weight/font-style:声明这是哪个粗细、哪种风格的字形font-display:控制字体加载时的行为(非常重要)
9.2.2 font-display 的几种策略
常见取值:
auto:浏览器自己决定swap:推荐,先用系统字体显示,字体加载完再"无缝切换"成目标字体fallback:短暂空白后,如果加载超时就不再换成目标字体block:在一小段时间内会「空白」,等字体下来再全部渲染
可以用一个时间轴来感受:
txt
加载过程:
[ 请求字体中...... ]
font-display: swap
0ms ──────> 文本先用系统字体显示
字体加载完成后,悄悄换成 MyBrandFont
font-display: block
0ms ──────> 文本区域可能是空白(FOIT:Flash of Invisible Text)
等字体加载完成才一起显示
大多数内容型网站,建议使用 swap,这样用户不会看到空白的文字区域,最多是"字体风格稍后变一下"。
9.2.3 字体加载失败时会怎样?
如果网络很差或字体路径错误:
- 浏览器会按
font-family列表继续向后找 - 找不到时会退到泛型族(比如
sans-serif)
这也是为什么即使用
@font-face也要在font-family中写好"备用字体链"。
9.3 字体渲染、行高、字距
这一节更多是「排版手感」,不是单纯记忆属性,而是建立一个舒服的默认排版模板。
9.3.1 字号与层级(Typographic Scale)
一个常见且好用的标题层级(以根字号 16px 为例):
css
/* font-size: 设置字体大小 */
/* rem单位: 相对于根元素(html)的字体大小,默认1rem=16px */
h1 {
font-size: 2.25rem; /* 2.25 × 16 = 36px 主标题 */
}
h2 {
font-size: 1.875rem; /* 1.875 × 16 = 30px 次级标题 */
}
h3 {
font-size: 1.5rem; /* 1.5 × 16 = 24px 三级标题 */
}
h4 {
font-size: 1.25rem; /* 1.25 × 16 = 20px 四级标题 */
}
body {
font-size: 1rem; /* 1 × 16 = 16px 正文大小 */
}
small {
font-size: 0.875rem; /* 0.875 × 16 = 14px 小字/注释 */
}
把它画成「标题梯度」:
txt
H1 ██████████████████ 36px
H2 ███████████████ 30px
H3 ███████████ 24px
H4 █████████ 20px
正文 ███████ 16px
注释 █████ 14px
关键是:
- 标题层级之间要有明显差别,但不要夸张到"像海报"
- 正文字号在中文环境下,16px 是一个比较舒服的起点
9.3.2 行高(line-height)
常见写法:
- 不带单位的倍数:
line-height: 1.5;、1.6;、1.75; - 不推荐为正文用 px 行高(容易在不同字号组合时出现割裂感)
一个常见的正文排版设置:
css
.article {
font-size: 16px; /* 设置基础字体大小 */
/* line-height: 设置行高(行间距) */
/* 无单位数值 = 字体大小的倍数 */
line-height: 1.75; /* 实际行高 = 16px × 1.75 = 28px */
/* 1.75是中文阅读的舒适行高 */
}
直观感觉可以想成:
txt
文本行(字号16px)
┌────────────────────┐
│ 文本文本文本文本文 │ ← 行高包裹的"行盒子"
└────────────────────┘
行高太小:文字挤在一起难读;
行高太大:行与行之间像断开一样,也不好阅读。
经验值:
- 正文段落:
1.6 ~ 1.8 - 标题:可以略小,如
1.2 ~ 1.4
9.3.3 字距与段落间距
几个相关属性:
letter-spacing:字母/字距word-spacing:单词间距(对中文不常用)text-indent:首行缩进margin-top / margin-bottom:控制段落之间的距离
中文常用设置示例:
css
.article p {
/* margin: 设置外边距 (上下 左右) */
margin: 0.75rem 0; /* 段落上下间距各12px (0.75×16) */
/* text-indent: 设置首行缩进 */
/* em单位: 相对于当前元素的字体大小 */
text-indent: 2em; /* 缩进2个字符宽度(中文习惯) */
}
.article p.no-indent {
text-indent: 0; /* 取消首行缩进(如章节开头) */
}
9.3.4 字体渲染差异(粗细与抗锯齿)
不同系统和浏览器对同一个字体的渲染可能不一样,例如:
- macOS 上文字更圆润、抗锯齿更明显
- Windows 上有时会显得更锐利甚至偏"糊"
你可以在 DevTools 中对同一段文本:
- 切换不同
font-weight(400 / 500 / 600 / 700) - 观察不同系统下的效果,挑选自己觉得舒服的档位
在设计系统中,常见的做法是只用少数几个字重:
- 正文:
400 - 强调:
500或600 - 标题:
600或700
9.4 中文字体的选择与性能
中文字体的一个现实问题是:文件更大。
9.4.1 尽量优先使用系统字体
对于大多数内容型网站,推荐策略是:
- 优先使用系统自带字体(体积为 0,不需要下载)
- 只在品牌要求很强时,才引入额外的 Web 字体
示例策略:
css
body {
font-family:
/* system-ui: 使用操作系统的默认UI字体(最优先) */
system-ui, /* 通用系统字体关键字 */
-apple-system, /* Safari/macOS专用 */
BlinkMacSystemFont, /* Chrome/macOS专用 */
"Segoe UI", /* Windows系统字体 */
"PingFang SC", /* macOS中文字体 */
"Microsoft YaHei", /* Windows中文字体 */
sans-serif; /* 无衬线字体兜底 */
}
system-ui是一个较新的泛型族,代表系统默认 UI 字体,现代浏览器支持较好。
9.4.2 如果必须用 Web 中文字体
要考虑几个问题:
- 子集化(Subsetting) :
- 一套完整中文字体可能有几万个字形,文件轻松上 MB
- 若只需部分字符(如 Logo 或标题语),可以做子集字体,只包含必要字符
- 分权重加载 :
- 不要一次性加载
300/400/500/600/700等很多个字重 - 优先加载正文用的
400和标题用的600即可
- 不要一次性加载
- 懒加载 :
- 某些不重要的装饰字体,可以在首屏之后再加载
9.4.3 使用字体服务
市面上有多种字体服务(如 Google Fonts、一些国内商用字体平台),它们会帮助你:
- 处理多格式(woff2/woff/ttf)
- 做 CDN 加速
- 一定程度的子集化
但要注意:
- 商用项目必须确认字体的授权
- 一些境外字体 CDN 可能在国内网络环境下速度不稳定
第9章到这里,你应该已经可以为页面做一套看起来「舒服」的文字系统:合理的字体族、字号梯度、行高、段落间距,并且知道自定义字体加载会带来什么影响。接下来第10章,我们会从静态视觉转向「动态」------用过渡和动画让页面动起来。
第10章 过渡与动画:让页面动起来
这一章解决的核心问题:
- hover 时元素"突然跳变"很生硬,如何让变化变得柔和?
transition要写哪些参数?顺序有什么讲究?transform缩放、旋转、位移这些变换如何组合?animation + keyframes和transition有什么区别,各自适合什么场景?
10.1 transition 的四要素
transition 用来在「状态 A」和「状态 B」之间加一条"缓冲曲线"。典型场景:按钮 hover、折叠面板展开等。
10.1.1 四个核心参数
语法可以拆解为四个部分:
css
/* transition: 过渡效果的简写属性 */
transition: property duration timing-function delay;
/* 要过渡的属性 持续时间 缓动函数 延迟时间 */
各参数详解:
-
property(过渡属性):指定哪个CSS属性需要过渡效果all- 所有属性都过渡opacity- 透明度transform- 变换(缩放/旋转/位移等)background-color- 背景颜色width/height- 宽高(性能较差,慎用)
-
duration(持续时间):动画执行的时长0.2s- 200毫秒300ms- 300毫秒- 建议范围:150ms-350ms(太快看不清,太慢显得卡顿)
-
timing-function(缓动函数):控制动画的速度变化曲线ease- 默认值,先快后慢linear- 匀速ease-in- 慢速开始,加速结束ease-out- 快速开始,减速结束ease-in-out- 慢速开始和结束cubic-bezier()- 自定义贝塞尔曲线
-
delay(延迟时间):动画开始前的等待时间0s- 立即开始0.1s- 延迟100毫秒开始
最常见的写法:
css
.btn {
/* transition: 可以同时过渡多个属性,用逗号分隔 */
transition:
background-color 0.2s ease, /* 背景色过渡:0.2秒,缓入缓出 */
transform 0.2s ease; /* 变换过渡:0.2秒,缓入缓出 */
}
小建议:尽量只对 少数关键属性 做过渡,比如
opacity、transform,它们通常性能较好。
10.1.2 从"生硬跳变"到"柔和过渡"
没有 transition 时:
css
/* 没有过渡效果的卡片(生硬) */
.card {
box-shadow: none; /* 初始状态:无阴影 */
}
.card:hover {
/* hover状态:突然出现阴影(视觉跳变) */
box-shadow: 0 10px 30px rgba(0,0,0,.15);
/* X偏移 Y偏移 模糊 颜色 */
}
/* 添加过渡效果后(柔和) */
.card {
box-shadow: none;
/* transition: 让阴影渐变出现,而不是突然跳变 */
transition: box-shadow 0.25s ease;
/* 过渡属性 持续0.25秒 缓动曲线 */
}
大脑中的感受大概是:
txt
状态 A(无阴影) ────(0.25s 内平滑变化)────> 状态 B(有阴影)
10.1.3 常见 timing-function 曲线
几个常用曲线的感觉:
linear:匀速,机械感强,多用于进度条ease:先快后慢,比线性自然(默认值)ease-in:慢慢启动,适合"出现"ease-out:慢慢停下,适合"消失"ease-in-out:两头慢,中间快,更流畅
你可以在 DevTools 的动画面板里直观看到这些曲线,也可以用 cubic-bezier() 自定义,例如:
css
.btn {
/* cubic-bezier: 自定义贝塞尔曲线,精确控制动画节奏 */
transition: transform 200ms cubic-bezier(0.22, 0.61, 0.36, 1);
/* 四个值分别是:x1, y1, x2, y2 */
/* 这个曲线比ease更有"弹性"感觉 */
}
这类自定义曲线经常被称为"手感更像 App 的动效曲线"。
10.2 transform:缩放、旋转、位移、倾斜
transform 决定元素在 2D/3D 空间中的几何变换。常见的有:
translateX/Y:位移scale:缩放rotate:旋转skew:倾斜
10.2.1 位移:translate
css
/* transform: 元素的2D/3D变换,不影响文档流 */
.move-right {
/* translateX: 水平位移(正值向右,负值向左) */
transform: translateX(20px); /* 向右移动20像素 */
}
.move-down {
/* translateY: 垂直位移(正值向下,负值向上) */
transform: translateY(10px); /* 向下移动10像素 */
}
/* 也可以同时位移 */
.move-both {
/* translate: 同时设置X和Y位移 */
transform: translate(20px, 10px); /* 右移20px,下移10px */
}
与 left/top 的区别:
left/top会影响布局(通常需要配合position)transform: translate不改变文档流占位,更适合做动效
left/top 属于定位属性,需要配合 position(如 relative、absolute 等)使用,会改变元素在文档流中的位置,可能影响其他元素的布局。
transform: translate 只是视觉上移动元素,不会改变元素在文档流中的原始占位,其他元素的布局不会受到影响。
10.2.2 缩放:scale
css
/* scale: 缩放变换 */
.zoom-in {
/* scale: 等比例缩放(1=原始大小) */
transform: scale(1.05); /* 放大到105%(1.05倍) */
}
.zoom-x {
/* scaleX: 仅横向缩放 */
transform: scaleX(1.2); /* 宽度变为120%,高度不变 */
}
.zoom-y {
/* scaleY: 仅纵向缩放 */
transform: scaleY(0.8); /* 高度变为80%,宽度不变 */
}
/* 非等比缩放 */
.zoom-custom {
transform: scale(1.2, 0.8); /* X轴120%,Y轴80% */
}
配合 transition,可以做卡片 hover 的轻微放大:
css
.card {
transition: transform 0.2s ease;
}
.card:hover {
/* transform: 可以组合多个变换函数 */
transform:
translateY(-4px) /* 向上浮起4像素 */
scale(1.02); /* 轻微放大2% */
/* ⚠️ 变换函数的执行顺序:从左到右书写,但矩阵运算是从右到左 */
/* 对于这个例子,视觉上差异不大,但复杂变换时顺序很重要 */
/* 例:rotate(45deg) translateX(100px) 和 translateX(100px) rotate(45deg) 结果完全不同 */
}
10.2.3 旋转与倾斜:rotate、skew
css
/* rotate: 旋转变换 */
.rotate {
/* rotate: 顺时针旋转(负值为逆时针) */
transform: rotate(5deg); /* 顺时针旋转5度 */
}
.rotate-reverse {
transform: rotate(-45deg); /* 逆时针旋转45度 */
}
/* skew: 倾斜变换 */
.skew {
/* skewX: 水平倾斜 */
transform: skewX(10deg); /* X轴倾斜10度 */
}
.skew-y {
/* skewY: 垂直倾斜 */
transform: skewY(5deg); /* Y轴倾斜5度 */
}
旋转常用在图标按钮(比如刷新、关闭)上;倾斜适合作为装饰效果,慎用在正文内容上,以免影响可读性。
10.2.4 transform 原点:transform-origin
默认情况下,变换的原点是元素的中心:
css
.scale-from-top {
/* transform-origin: 设置变换的基准点(默认是center center) */
transform-origin: top center; /* 基准点设为顶部中心 */
/* 上方位 水平位 */
transform: scaleY(0); /* 从顶部向下收缩为0 */
}
/* 其他常用基准点 */
.origin-examples {
transform-origin: center center; /* 默认:中心点 */
transform-origin: top left; /* 左上角 */
transform-origin: bottom right; /* 右下角 */
transform-origin: 50% 50%; /* 百分比定位 */
transform-origin: 10px 20px; /* 具体像素位置 */
}
示意:
txt
默认原点:中心
↑
┌───────┐
│ • │ ← transform-origin 在中间
└───────┘
改变原点:top center
•
┌───────┐
│ │
└───────┘
这在做折叠菜单(从上往下展开)时非常常见:
css
/* 折叠菜单效果:从上往下展开 */
.collapse {
transform-origin: top; /* 变换基准点在顶部 */
transform: scaleY(0); /* 初始状态:垂直方向缩为0(隐藏) */
overflow: hidden; /* 隐藏溢出内容 */
}
.collapse.open {
transform: scaleY(1); /* 展开状态:恢复原始高度 */
transition: transform 0.2s ease; /* 平滑过渡效果 */
}
10.3 animation 与 keyframes
transition 是在两个状态之间自动补间,而 animation + @keyframes 可以定义多帧动画,更适合:
- 循环动画(如加载动画)
- 独立于交互事件的装饰性动效
10.3.1 基本结构
css
/* @keyframes: 定义动画的关键帧序列 */
@keyframes pulse {
0% { transform: scale(1); } /* 开始帧:原始大小 */
50% { transform: scale(1.05); } /* 中间帧:放大5% */
100% { transform: scale(1); } /* 结束帧:回到原始大小 */
}
.btn-pulse {
/* animation: 动画属性的简写 */
animation: pulse 1.2s ease-in-out infinite;
/* 动画名 时长 缓动函数 循环次数 */
}
animation 简写参数顺序一般为:
css
/* animation 完整语法:8个参数 */
animation: name duration timing-function delay iteration-count direction fill-mode play-state;
/* 动画名 时长 缓动函数 延迟 循环次数 播放方向 填充模式 播放状态 */
/* ⚠️ 重要:duration 必须写在 delay 之前,因为两者都是时间值,浏览器靠位置区分 */
常用参数详解:
-
name(动画名称):引用@keyframes定义的动画- 例:
pulse、fade-in、slide-up
- 例:
-
duration(持续时间):动画播放一次的时长- 例:
1s、200ms、2.5s
- 例:
-
timing-function(缓动函数):与transition相同ease、linear、ease-in-out等
-
delay(延迟时间):动画开始前的等待时间- 例:
0s、0.5s
- 例:
-
iteration-count(循环次数):动画重复播放次数1- 播放一次3- 播放三次infinite- 无限循环
-
direction(播放方向):动画的播放顺序normal- 正常播放(默认)reverse- 反向播放alternate- 交替播放(正反正反...)alternate-reverse- 反向交替
-
fill-mode(填充模式):动画开始前/结束后的状态none- 不保持(默认)forwards- 保持最后一帧状态backwards- 立即应用第一帧状态both- 同时应用forwards和backwards
-
play-state(播放状态):控制动画暂停/播放running- 播放中(默认)paused- 暂停
10.3.2 from / to 写法
单纯"从 A 到 B"的动画,可以用 from / to:
css
/* from/to语法:简单的两帧动画 */
@keyframes fade-in {
from { opacity: 0; } /* from = 0% 开始状态:完全透明 */
to { opacity: 1; } /* to = 100% 结束状态:完全不透明 */
}
.fade-in {
/* 淡入效果:0.3秒内从透明变为不透明 */
animation: fade-in 0.3s ease-out;
/* 动画名 0.3秒 缓出曲线 */
}
10.3.3 一次性入场动画
如果你只希望元素在第一次进入时播放一个动画,而不是一直循环,可以:
- 在初始状态触发类名(如 JS 加上
.enter) - 用
animation-fill-mode: forwards保持结束状态
css
/* 上滑淡入动画 */
@keyframes slide-up {
from {
transform: translateY(12px); /* 从下方12px处开始 */
opacity: 0; /* 初始透明 */
}
to {
transform: translateY(0); /* 滑到原位 */
opacity: 1; /* 变为不透明 */
}
}
.card-enter {
animation: slide-up 0.3s ease-out forwards;
/* forwards=保持最终状态 */
}
一般来说:交互驱动的小动效 优先考虑
transition,
时间驱动/循环/多步骤动画 再考虑animation。
10.4 实战:呼吸按钮、加载动画
这一节用两个小例子把上面的概念串起来。
10.4.1 呼吸按钮(pulse button)
目标效果:按钮轻微放大缩小,像在"呼吸",吸引用户注意但不过分抢眼。
HTML:
html
<button class="btn btn-primary btn-pulse">立即开始</button>
CSS(核心部分):
css
/* 呼吸按钮基本样式 */
.btn-primary {
background: #2563eb;
color: #fff;
border-radius: 999px; /* 超大圆角 = 胶囊按钮 */
padding: 0.6em 1.6em; /* em单位:随字体大小缩放 */
box-shadow: 0 10px 30px rgba(37,99,235,.25);
/* 同时过渡两个属性 */
transition: transform 0.15s ease,
box-shadow 0.15s ease;
}
/* hover状态:轻微上浮和放大 */
.btn-primary:hover {
transform: translateY(-1px) scale(1.02); /* 上移1px + 放大2% */
box-shadow: 0 14px 40px rgba(37,99,235,.35); /* 阴影加强 */
}
/* 呼吸动画关键帧 */
@keyframes btn-pulse {
0% {
transform: scale(1); /* 原始大小 */
box-shadow: 0 10px 30px rgba(37,99,235,.25); /* 较小阴影 */
}
50% {
transform: scale(1.04); /* 放大4% */
box-shadow: 0 14px 40px rgba(37,99,235,.35); /* 较大阴影 */
}
100% {
transform: scale(1); /* 回到原始 */
box-shadow: 0 10px 30px rgba(37,99,235,.25);
}
}
/* 应用呼吸动画 */
.btn-pulse {
animation: btn-pulse 1.5s ease-in-out infinite;
/* 动画名 每次循环1.5秒 缓入缓出 无限循环 */
}
按钮的动线大致可以想象为:
txt
scale: 1 → 1.04 → 1
shadow: 较小 → 稍大 → 较小
节奏: 呼───吸───呼───吸───(循环)
10.4.2 加载动画(loading spinner)
目标效果:一个简单的圆环一直旋转,表示"正在加载"。
HTML:
html
<div class="spinner" aria-label="Loading"></div>
CSS:
css
/* 加载动画:旋转圆环 */
.spinner {
width: 24px;
height: 24px;
border-radius: 50%; /* 变成圆形 */
/* 制作圆环:四边框,但只有顶边是亮色 */
border: 3px solid rgba(148, 163, 184, 0.3); /* 整体淡灰色边框 */
border-top-color: #2563eb; /* 顶部覆盖为蓝色 */
/* 旋转动画 */
animation: spin 0.8s linear infinite;
/* 动画名 0.8秒 匀速 无限循环 */
}
@keyframes spin {
/* 只需要结束帧:旋转360度(一圈) */
to { transform: rotate(360deg); }
}
可以用一行文字来记它的本质:
txt
圆圈 + 部分高亮 + 持续 rotate(360deg) = 经典 loading-spinner
10.4.3 动效的"度"与性能
最后,再强调两个常被忽略的点:
-
动效的"度"
- 动画太快:眨眼就过,看不清变化
- 动画太慢:让人着急,影响操作效率
- 一般 UI 交互动效时长常见在
150ms ~ 300ms之间
-
性能与属性选择
- 优先对
opacity、transform做过渡/动画 - 避免频繁动画
width/height/top/left等会触发布局重排的属性
- 优先对
动效的目标,不是"炫技",而是让用户感觉:界面懂我在做什么,并且在温柔地回应我。
第10章带你从过渡、变换到关键帧动画,完成了 CSS 动效的基础拼图。接下来第11章,我们会在此基础上继续探索滤镜与特效,比如毛玻璃、混合模式,让你的页面在细节上更接近成熟产品的视觉质感。
第11章 滤镜与特效
这一章解决的核心问题:
- 如何用
filter做出模糊、灰度、亮度调整等效果?mix-blend-mode是什么?怎么让文字/图层和背景"混色"?backdrop-filter如何实现毛玻璃(玻璃拟态)效果?- 在不牺牲可读性和性能的前提下,用特效提升页面质感。
11.1 filter 滤镜
filter 可以理解为给元素贴上一个"后期滤镜",对其像素进行模糊、变亮、变暗、灰度等处理。
常见的滤镜函数:
blur(px):高斯模糊brightness(%):亮度contrast(%):对比度grayscale(%):灰度saturate(%):饱和度sepia(%):褐色(复古效果)drop-shadow():投影(类似 box-shadow,但跟随图形轮廓)
11.1.1 基础示例
css
/* filter: CSS滤镜属性,用于图像特效 */
.img-blur {
/* blur: 高斯模糊效果 */
filter: blur(4px); /* 模糊半径4像素 */
}
.img-gray {
/* grayscale: 灰度滤镜 */
filter: grayscale(100%); /* 100% = 完全灰度(黑白照片) */
/* 0% = 原始彩色 */
}
.img-dim {
/* 多个滤镜可以叠加使用 */
filter: brightness(0.8) /* 亮度降至80% */
contrast(1.1); /* 对比度提升到110% */
}
你也可以链式叠加多个滤镜,执行顺序从左到右:
css
.hero-bg {
/* 链式叠加多个滤镜,按顺序执行 */
filter: grayscale(30%) /* 30%灰度(略微脱色) */
brightness(0.9) /* 90%亮度(稍暗) */
saturate(1.1); /* 110%饱和度(颜色稍浓) */
}
11.1.2 滤镜 vs 直接改颜色
有些效果既可以通过调整颜色属性(如 background-color)实现,也可以通过滤镜实现。
filter的优势:可以对图片或视频整体做后期调整,而不改动源文件- 代价:在一些设备上滤镜可能比简单颜色变更更耗性能
常见用法:
- 给背景图加一点
brightness()压暗,突出前景文字 - 给缩略图加
grayscale(100%),hover 时还原彩色
css
/* 缩略图悬停彩色效果 */
.thumb {
filter: grayscale(100%); /* 默认:完全灰度 */
transition: filter 0.2s ease; /* 滤镜过渡效果 */
}
.thumb:hover {
filter: grayscale(0%); /* hover:恢复彩色 */
}
11.2 mix-blend-mode 混合模式
mix-blend-mode 用于控制一个元素和其"背后内容"怎样混合颜色,类似于 Photoshop 里的图层混合模式。
常见混合模式:
multiply:正片叠底,通常让颜色变暗screen:滤色,让颜色变亮overlay:叠加,对比增强difference:差值,产生反相效果(更偏实验性)
11.2.1 基础用法
css
.title-overlay {
/* mix-blend-mode: 混合模式,控制元素与背景的颜色混合 */
mix-blend-mode: overlay; /* overlay = 叠加模式 */
/* 效果:增强对比度 */
}
HTML:
html
<div class="hero">
<img src="hero-bg.jpg" class="hero-bg" alt="背景图">
<h1 class="hero-title title-overlay">混合模式标题</h1>
</div>
简单示意:
txt
背景:蓝色渐变
前景:白色文字 + overlay 混合
结果:文字会带一点背景的颜色调子,比纯白更"贴在画面上"。
注意:
mix-blend-mode的最终效果高度依赖背景内容,难以精确控制,一般用于图形/装饰性排版,不适合作为关键 UI 的唯一对比手段。
11.2.2 常见小用法
- 让 icon/装饰图案跟背景混色,形成更丰富的质感
- 做横幅(banner)时,让白色大标题与背景渐变融合得更自然
示例:
css
.badge-fancy {
background: #f97316; /* 橙色背景 */
/* multiply: 正片叠底模式 */
mix-blend-mode: multiply; /* 颜色会变暗,更融入背景 */
/* 类似在纸上叠加颜料 */
}
在深色背景上,会显得颜色更"沉",而不是刺眼的纯橙色。
11.3 backdrop-filter 毛玻璃效果
backdrop-filter 是实现「玻璃拟态(Glassmorphism)」的关键属性,它不是对元素自身,而是对元素背后的内容应用滤镜。
典型用法:
- 半透明卡片,背景被模糊
- 固定在顶部的导航栏,略带模糊背景
11.3.1 基础示例
css
/* 玻璃拟态卡片 */
.glass-card {
/* 半透明背景 */
background: rgba(255, 255, 255, 0.12); /* 白色,12%不透明度 */
border-radius: 16px; /* 圆角 */
/* 半透明边框增加层次感 */
border: 1px solid rgba(255, 255, 255, 0.3);
/* backdrop-filter: 对元素背后内容应用滤镜 */
backdrop-filter: blur(18px); /* 背景模糊18px */
/* 这是玻璃效果的核心 */
}
视觉结构可以想象为:
txt
背景图 / 彩色渐变
↓(通过 glass-card 看到时,会被 blur)
半透明玻璃卡片(带 blur 的 backdrop-filter)
↓
卡片内部文字和图标(保持清晰)
11.3.2 浏览器支持与兼容性
backdrop-filter 在现代浏览器中支持已经不错,但:
- 某些老版本浏览器不支持
- 移动端不同厂商实现可能略有差异
实战建议:
- 为
.glass-card提供一个「退化样式」,例如简单的半透明背景,不依赖 blur - 在需要时,可以用
@supports (backdrop-filter: blur(10px)) { ... }做能力检测
css
/* 渐进增强:先提供基础样式,再根据浏览器能力增强 */
.glass-card {
/* 退化样式:普通半透明卡片(不支持backdrop-filter时) */
background: rgba(15, 23, 42, 0.7); /* 70%不透明的深色 */
}
/* @supports: 检测浏览器是否支持某个CSS特性 */
@supports (backdrop-filter: blur(16px)) {
/* 如果支持backdrop-filter */
.glass-card {
background: rgba(15, 23, 42, 0.4); /* 降低不透明度到40% */
backdrop-filter: blur(16px); /* 启用玻璃模糊效果 */
-webkit-backdrop-filter: blur(16px); /* Safari 17及更早版本兼容(Safari 18+已无需前缀) */
}
}
11.4 实战:制作玻璃拟态卡片
综合用上一节的知识,做一个常见的玻璃拟态卡片样式。
11.4.1 HTML 结构
html
<section class="hero-glass">
<div class="glass-card">
<h2 class="glass-title">Pro 版订阅</h2>
<p class="glass-subtitle">解锁全部 CSS 实战案例与模板</p>
<button class="btn btn-primary">立即升级</button>
</div>
</section>
11.4.2 CSS 样式(核心部分)
css
.hero-glass {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background:
radial-gradient(circle at 0% 0%, rgba(96, 165, 250, .35), transparent 55%),
radial-gradient(circle at 100% 100%, rgba(244, 114, 182, .35), transparent 55%),
#020617; /* 深色背景 */
}
.glass-card {
max-width: 360px;
padding: 24px 28px;
border-radius: 20px;
border: 1px solid rgba(148, 163, 184, 0.5);
background: rgba(15, 23, 42, 0.55);
box-shadow:
0 18px 40px rgba(15, 23, 42, 0.9),
0 0 0 1px rgba(148, 163, 184, 0.35);
backdrop-filter: blur(18px);
}
.glass-title {
color: #e5e7eb;
font-size: 1.5rem;
margin-bottom: 0.25rem;
}
.glass-subtitle {
color: #cbd5f5;
font-size: 0.95rem;
margin-bottom: 1.25rem;
}
结构示意:
txt
背景层:彩色渐变 + 深色底
↓
玻璃层:半透明 + blur + 边框 + 投影(glass-card)
↓
内容层:清晰文字 + 按钮
11.4.3 字体与对比度的注意事项
在做这种特效时,容易只顾好看,忘了"可读性"与"可访问性":
- 保证文字与背景的对比度足够(尤其是小字号)
- 对重要信息,避免依赖颜色与模糊效果作为唯一区分
- 在深浅两套主题中分别检查玻璃拟态卡片的可读性
第11章中,我们用
filter、mix-blend-mode、backdrop-filter做了几种常见视觉特效,并完成了一个玻璃拟态卡片。到这里,本篇「强化视觉」从字体、动效到特效已经成型。后面的篇章中,我们会更多地把这些能力带入组件化与实际页面,去构建真正可维护、可复用的 CSS 设计系统。100vh;
display: flex;
align-items: center;
justify-content: center;
background:
radial-gradient(circle at 0% 0%, rgba(96, 165, 250, .35), transparent 55%),
radial-gradient(circle at 100% 100%, rgba(244, 114, 182, .35), transparent 55%),
#020617; /* 深色背景 */
}
.glass-card {
max-width: 360px;
padding: 24px 28px;
border-radius: 20px;
border: 1px solid rgba(148, 163, 184, 0.5);
background: rgba(15, 23, 42, 0.55);
box-shadow:
0 18px 40px rgba(15, 23, 42, 0.9),
0 0 0 1px rgba(148, 163, 184, 0.35);
backdrop-filter: blur(18px);
}
.glass-title {
color: #e5e7eb;
font-size: 1.5rem;
margin-bottom: 0.25rem;
}
.glass-subtitle {
color: #cbd5f5;
font-size: 0.95rem;
margin-bottom: 1.25rem;
}
结构示意:
```txt
背景层:彩色渐变 + 深色底
↓
玻璃层:半透明 + blur + 边框 + 投影(glass-card)
↓
内容层:清晰文字 + 按钮
11.4.3 字体与对比度的注意事项
在做这种特效时,容易只顾好看,忘了"可读性"与"可访问性":
- 保证文字与背景的对比度足够(尤其是小字号)
- 对重要信息,避免依赖颜色与模糊效果作为唯一区分
- 在深浅两套主题中分别检查玻璃拟态卡片的可读性
第11章中,我们用
filter、mix-blend-mode、backdrop-filter做了几种常见视觉特效,并完成了一个玻璃拟态卡片。到这里,本篇「强化视觉」从字体、动效到特效已经成型。后面的篇章中,我们会更多地把这些能力带入组件化与实际页面,去构建真正可维护、可复用的 CSS 设计系统。