还在用水波纹、波浪圆球这类千篇一律的加载动画?纯原生 CSS 实现的仿苹果 FaceID 加载动画,凭借 CSS 自定义属性、SVG 遮罩、多轨关键帧动画的巧妙结合,打造出极具科技感和视觉层次感的动态效果,无需 JavaScript,轻量且高效,直接嵌入项目即可使用。

效果核心亮点
这款加载动画之所以能还原 FaceID 的科技感,核心在于三个设计巧思:
- 多维度视觉层次:通过外层阴影、渐变底色、SVG 遮罩层的叠加,实现立体的光影效果;
- 多轨异步动画:多个多边形围绕不同原点旋转,配合延迟差实现错落的动态轨迹,还原 FaceID 的扫描感;
- 动态色彩与对比度变化:通过色相旋转和对比度调节,让动画在运动中产生视觉张力,避免单调。
同时,动画全程基于transform和filter实现,不触发页面重排,依托 GPU 加速保证流畅性,符合现代前端性能优化要求。
实现原理拆解
整个动画的实现分为HTML 结构搭建和CSS 样式与动画定义两部分,核心用到 CSS 自定义属性、SVG mask 遮罩、@keyframes 关键帧动画三大核心技术,以下逐一对核心逻辑拆解。
1. HTML 结构:极简容器 + SVG 遮罩
HTML 部分仅需一个核心加载容器.loader,内部包含实现遮罩的 SVG 和视觉载体.box,结构极简,无冗余标签:
- SVG 标签定义
mask遮罩层,通过 7 个多边形的组合,形成 FaceID 标志性的扫描轮廓; - 遮罩层通过
id="clipping"与 CSS 中的mask属性关联,实现对.box元素的视觉裁剪; - 外层
.loader作为动画主容器,承载所有样式和动画属性。
.loader {
--color-one: #ffbf48; /* 主色1 */
--color-two: #be4a1d; /* 主色2 */
--color-three: #ffbf4780; /* 浅透色1 */
--color-four: #bf4a1d80; /* 浅透色2 */
--color-five: #ffbf4740; /* 超透色1 */
--time-animation: 2s; /* 基础动画时长 */
--size: 1; /* 整体缩放比例,可直接调整大小 */
}
通过--size缩放属性,只需修改一个值,即可适配不同页面的尺寸需求,无需调整宽高、间距等多个属性。
3. 视觉基底:渐变 + 阴影打造立体光影
动画的基础视觉效果由.loader和其伪元素::before实现,通过线性渐变和内外层阴影的组合,打造出圆润的立体光影基底:
- 外层通过
box-shadow添加扩散阴影,模拟发光效果; - 伪元素
::before设置圆形轮廓,通过上下边框配色、线性渐变背景,实现底色的层次变化; - 内层阴影
inset进一步强化立体感,让基底更具质感。
4. 核心遮罩:SVG mask 实现轮廓裁剪
这是还原 FaceID 视觉特征的关键步骤,通过CSS mask 属性关联 SVG 中的mask遮罩层,对.box的渐变背景进行裁剪:
- SVG 中定义 7 个多边形,黑色区域为显示区,白色区域为裁剪区,组合成 FaceID 的扫描轮廓;
- 为兼容 webkit 内核浏览器,同时设置
mask和-webkit-mask属性; - 对多边形添加
blur模糊和contrast对比度滤镜,让轮廓边缘更柔和,还原原生 FaceID 的模糊扫描效果。
5. 动态灵魂:多轨关键帧动画
整个动画的动态效果由三个自定义@keyframes关键帧动画和多元素异步旋转实现,形成错落有致的扫描动效:
(1)rotation:基础旋转动画
定义 0° 到 360° 的线性旋转,作为所有多边形的基础动态,通过设置不同的旋转原点(transform-origin)、旋转方向(reverse)和动画延迟(animation-delay),让 7 个多边形围绕不同点异步旋转,形成复杂且自然的扫描轨迹。
(2)roundness:对比度动态调节
对遮罩层进行对比度的周期性调节,让扫描轮廓在 “清晰 - 模糊 - 清晰” 之间切换,模拟 FaceID 扫描时的对焦效果,增强动态张力。
(3)colorize:色相旋转动画
对主容器进行hue-rotate色相旋转,让整个动画的色彩在周期内缓慢变化,避免单一色彩的视觉疲劳,同时通过ease-in-out缓动函数,让色彩变化更自然。
HTML:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSS仿FaceID加载动画</title>
<style>
/* 此处粘贴下方CSS代码 */
body {
margin: 0;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: #111;
}
</style>
</head>
<body>
<div class="loader">
<svg width="100" height="100" viewBox="0 0 100 100">
<defs>
<mask id="clipping">
<polygon points="0,0 100,0 100,100 0,100" fill="black"></polygon>
<polygon points="25,25 75,25 50,75" fill="white"></polygon>
<polygon points="50,25 75,75 25,75" fill="white"></polygon>
<polygon points="35,35 65,35 50,65" fill="white"></polygon>
<polygon points="35,35 65,35 50,65" fill="white"></polygon>
<polygon points="35,35 65,35 50,65" fill="white"></polygon>
<polygon points="35,35 65,35 50,65" fill="white"></polygon>
</mask>
</defs>
</svg>
<div class="box"></div>
</div>
</body>
</html>
CSS
.loader {
--color-one: #ffbf48;
--color-two: #be4a1d;
--color-three: #ffbf4780;
--color-four: #bf4a1d80;
--color-five: #ffbf4740;
--time-animation: 2s;
--size: 1; /* 调整此值改变动画大小,如1.5为1.5倍 */
position: relative;
border-radius: 50%;
transform: scale(var(--size));
box-shadow:
0 0 25px 0 var(--color-three),
0 20px 50px 0 var(--color-four);
animation: colorize calc(var(--time-animation) * 3) ease-in-out infinite;
}
.loader::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 100px;
border-radius: 50%;
border-top: solid 1px var(--color-one);
border-bottom: solid 1px var(--color-two);
background: linear-gradient(180deg, var(--color-five), var(--color-four));
box-shadow:
inset 0 10px 10px 0 var(--color-three),
inset 0 -10px 10px 0 var(--color-four);
}
.loader .box {
width: 100px;
height: 100px;
background: linear-gradient(
180deg,
var(--color-one) 30%,
var(--color-two) 70%
);
mask: url(#clipping);
-webkit-mask: url(#clipping);
}
.loader svg {
position: absolute;
}
.loader svg #clipping {
filter: contrast(15);
animation: roundness calc(var(--time-animation) / 2) linear infinite;
}
.loader svg #clipping polygon {
filter: blur(7px);
}
.loader svg #clipping polygon:nth-child(1) {
transform-origin: 75% 25%;
transform: rotate(90deg);
}
.loader svg #clipping polygon:nth-child(2) {
transform-origin: 50% 50%;
animation: rotation var(--time-animation) linear infinite reverse;
}
.loader svg #clipping polygon:nth-child(3) {
transform-origin: 50% 60%;
animation: rotation var(--time-animation) linear infinite;
animation-delay: calc(var(--time-animation) / -3);
}
.loader svg #clipping polygon:nth-child(4) {
transform-origin: 40% 40%;
animation: rotation var(--time-animation) linear infinite reverse;
}
.loader svg #clipping polygon:nth-child(5) {
transform-origin: 40% 40%;
animation: rotation var(--time-animation) linear infinite reverse;
animation-delay: calc(var(--time-animation) / -2);
}
.loader svg #clipping polygon:nth-child(6) {
transform-origin: 60% 40%;
animation: rotation var(--time-animation) linear infinite;
}
.loader svg #clipping polygon:nth-child(7) {
transform-origin: 60% 40%;
animation: rotation var(--time-animation) linear infinite;
animation-delay: calc(var(--time-animation) / -1.5);
}
/* 关键帧动画定义 */
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes roundness {
0% {
filter: contrast(15);
}
20% {
filter: contrast(3);
}
40% {
filter: contrast(3);
}
60% {
filter: contrast(15);
}
100% {
filter: contrast(15);
}
}
@keyframes colorize {
0% {
filter: hue-rotate(0deg);
}
20% {
filter: hue-rotate(-30deg);
}
40% {
filter: hue-rotate(-60deg);
}
60% {
filter: hue-rotate(-90deg);
}
80% {
filter: hue-rotate(-45deg);
}
100% {
filter: hue-rotate(0deg);
}
}
总结
完美体现了纯 CSS 实现复杂动态效果的可能性,核心在于对 CSS 自定义属性、mask 遮罩、多轨关键帧动画的灵活组合。相比传统的水波纹、圆球旋转动画,它不仅视觉效果更精致、更具科技感,而且性能更优、可定制性更强,只需几行代码修改,即可融入各类前端项目,提升用户等待时的视觉体验。
告别单调的加载动画,用这款纯 CSS 实现的 FaceID 效果,为你的项目增添一抹高级的科技感吧!