怎么给博客加动效?我的渐进增强特效清单
7 个特效 × 实现要点 × 性能平衡 × 避坑指南(每个特效都有试玩框,动手试试 👇)
这篇清单记录了我在博客上实现的 7 个动效:每个是什么、怎么做、注意什么。所有特效都遵循同一个原则——内容永远在 HTML 里,特效只是锦上添花。每个特效下面都有一个试玩框,可以直接体验效果。
〇、先立原则:渐进增强 & GPU 优先
给博客加动效,第一个要立的规矩是:内容先完整写在 HTML 源码里,JS 只负责加动画,绝不删内容。
第二个规矩:GPU 优先——所有动效只操作 transform / opacity(合成器属性,走 GPU 合成层),避免用 top / left / width / margin 做动画(那会触发 CPU 布局 + 重绘,掉帧卡顿)。最典型的例子:进度条动画用 scaleX 而不是 width。下面每个特效的代码都是这么写的。
为什么?因为百度、Google、AI 爬虫大多不执行 JS,它们只读源码。内容在源码里,特效随便玩;内容靠 JS 生成,SEO 直接崩。我用爬虫视角(禁用 JS)抓过自己页面验证:标题、正文、图片全部完整。
特效总览
| 特效 | 难度 | 一句话效果 | 试玩 |
|---|---|---|---|
| 粒子背景 | ★★★ | 全屏粒子连线跟随鼠标,最「动感」的装饰 | 👇 |
| 3D 卡片 & 磁吸按钮 | ★★ | 鼠标划过卡片倾斜、按钮被「吸」向鼠标 | 👇 |
| 开屏动画 | ★★ | 首次打开的全屏启动页:头像呼吸 + 进度条 | 👇 |
| 打字机标题 | ★ | 标题逐字打出,有节奏的出场 | 👇 |
| 数字滚动 | ★ | 浏览数从 0 滚到真实值 | 👇 |
| 滚动渐入 | ★★ | 卡片进入视口时淡入上浮 | 👇 |
| 过界回弹 | ★★★ | 滑到顶部/底部有阻尼反馈 | 👇 |
一、粒子背景
canvas 全屏画一层粒子,距离近的粒子连成线,鼠标靠近时被推开,粉色主题跟马卡龙风格很搭。
// 核心逻辑(简化)
for (var a = 0; a < particles.length; a++) {
for (var b = a + 1; b < particles.length; b++) {
var d = dist(particles[a], particles[b]);
if (d < 120) drawLine(a, b, (1 - d / 120) * 0.2); // 越远越淡
}
}
- 连线是 O(n²) 双重循环,循环里缓存
length和元素引用,能省不少事 - 粒子数量控制在 100 个以内,体验和性能都舒服
- canvas 2D 本身由 GPU 加速渲染,粒子动画不吃 CPU 布局
- canvas 用
position: fixed铺底,内容区 z-index 压在上面 - 移动端建议直接不渲染(触屏没 hover,省电省流量)
二、3D 卡片 & 磁吸按钮
鼠标在卡片上移动时,卡片按鼠标位置做 rotateX/rotateY 倾斜,像在转一张牌;按钮朝鼠标方向偏移几像素,像被磁铁吸住。
card.addEventListener('mousemove', function(e) {
var r = card.getBoundingClientRect();
var x = (e.clientX - r.left) / r.width - 0.5;
var y = (e.clientY - r.top) / r.height - 0.5;
card.style.transform = 'rotateY(' + (x * 8) + 'deg) rotateX(' + (-y * 8) + 'deg)';
});
transition 才有缓冲感,并且鼠标移出时记得把 transform 复位。三、开屏动画(调时长调了三次)
首次打开网站显示全屏启动页:头像弹跳进入 + 呼吸光晕、站名和欢迎语依次淡入、进度条走满,然后整体淡出。
/* GPU 优先:进度条用 scaleX 而不是 width(width 触发 CPU 布局) */
@keyframes pop { from { transform: scale(0); opacity: 0; }
to { transform: scale(1); opacity: 1; } }
@keyframes load { from { transform: scaleX(0); }
to { transform: scaleX(1); } }
这个特效我踩的坑最多,四个要点:
- 时长要反复调:1.4s → 0.85s → 1.15s。太短一闪而过,太长惹人烦,最终停在 1.15s + 0.4s 淡出
- 只显示一次:sessionStorage 打标记,刷新不重复出现(刷新=热启动,重开标签=冷启动)
- 尊重减少动效:系统开了「减弱动态效果」(prefers-reduced-motion)就直接跳过
- 淡出逻辑必须独立:淡出定时器先挂上,其他逻辑包 try-catch——任何报错都不能让开屏卡住(这个 bug 我修了两次才根治)
四、打字机标题(SEO 风险最高的一个)
标题逐字打出配光标闪烁,很有节奏感。但标题是 SEO 的核心,必须做好两道保险:
// 打字机:逐字打出 + 光标闪烁
var text = '这里是你的标题文字';
var el = document.querySelector('.article-title');
var i = 0;
var timer = setInterval(function(){
el.textContent = text.slice(0, ++i);
if (i >= text.length) clearInterval(timer);
}, 110);
/* 光标闪烁(CSS):
.title { border-right: 2px solid #a58fff;
animation: blink .8s steps(1) infinite; }
@keyframes blink { 50% { border-color: transparent; } } */
<title>标签保持独立完整(搜索引擎结果标题靠它,跟页面动画无关)- 页面标题即使被清空重打,也要加 3 秒兜底恢复
五、数字滚动
浏览数从 0 滚到真实值(比如 53600),requestAnimationFrame 做缓动即可。注意:数字位数变化会改变宽度,可能引起一点点布局偏移——给数字容器预留宽度或用等宽数字。
// 数字滚动:0 → 目标值,ease-out 缓动
var el = document.querySelector('.views');
var target = 53600, dur = 1200, t0 = null;
function step(ts){
if (!t0) t0 = ts;
var p = Math.min((ts - t0) / dur, 1);
var ease = 1 - Math.pow(1 - p, 3); // ease-out 缓动
el.textContent = Math.round(target * ease).toLocaleString();
if (p < 1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
// 提示:给数字容器用 tabular-nums,避免位数变化引起宽度抖动
六、滚动渐入
列表卡片进入视口时从透明淡入上浮,IntersectionObserver 实现。两个兜底缺一不可:
if ('IntersectionObserver' in window) {
// 进入视口 → 加 .visible 类
} else {
// 老浏览器:全部直接显示,不做渐入
}
// 外加一个超时强制显示,防止内容永远隐形
七、过界回弹
页面顶部继续下拉有橡皮筋回弹,滑到底部继续上滑有阻尼反馈、松手弹簧归位。上下对称,让「滑到边界」这件事有明确反馈,手感完整。
🎈 这是一段可以滚动的文字。
先把滚动条滚到底部,再继续向下滚轮(或手指上滑),容器会有一个阻尼回弹。
滚回顶部,继续向上滚轮(或手指下拉),同样有橡皮筋效果。
这就是「过界反馈」:滑到边界时告诉用户——到底了,别再滑啦。
🍰 上下的手感是对称的,用户会觉得整个页面很「Q弹」。
✨ 实现上就是监听滚轮/触摸事件,在边界溢出时给容器一个 transform 偏移,松手后弹簧复位。
🐟 记住:只对边界溢出做反馈,正常滚动绝不拦截。
// 过界回弹:边界溢出给 transform 偏移,松手弹簧复位
box.addEventListener('wheel', function(e){
var top = box.scrollTop, max = box.scrollHeight - box.clientHeight;
var over = 0;
if (top <= 0 && e.deltaY < 0) over = e.deltaY; // 顶部继续下拉
else if (top >= max - 1 && e.deltaY > 0) over = e.deltaY; // 底部继续上滑
if (over) {
e.preventDefault();
off = Math.max(-20, Math.min(20, off + over * 0.18)); // 限制偏移量
box.style.transform = 'translateY(' + off + 'px)';
reset(); // 松手后 transition 弹性复位(约 0.35s 弹簧)
}
}, { passive: false });
八、动效的「度」:性能与体验的平衡
| 手段 | 做法 |
|---|---|
| GPU 优先 | 动效只用 transform/opacity(合成器属性走 GPU),进度条用 scaleX 而非 width |
| 尊重减少动效 | prefers-reduced-motion 开启时跳过开屏、粒子等大动效 |
| 移动端取舍 | 触屏设备(hover: none)降级部分特效;流量敏感的预取只做前几篇 |
| 利用空闲时间 | 开屏那 1 秒多的时间窗口,PC 端顺手 prefetch 全站文章和图片 |
| 高频循环优化 | 缓存数组 length、局部变量引用,减少属性链查找 |
九、兼容与排障(血泪经验)
Q1:老浏览器(Win7 的 Chrome 109 / IE11)怎么办?
JS 全部用 ES5 写法(var/function),IE11 缺的 API(NodeList.forEach、Element.remove)加 polyfill,fetch 等特性做检测。结果:Chrome 109 吃到 90% 特效,IE11 不报错、内容完整、吃到圆角阴影渐变这类基础效果——每个浏览器都拿到能力范围内最好的体验。
Q2:JS 写了但不执行?先数标签配对
有次开屏一直不消失,查到最后是开屏 JS 的 </script> 结束标签丢了,整个脚本语法错误、一行都没执行。排查方法:数页面里 <script> 和 </script> 的数量,不相等就是有没闭合的。
Q3:改了代码没效果?先查版本号
CSS/JS 引用链接不带 ?v= 时,浏览器会缓存旧文件很久,改了什么都是白改。给所有静态资源链接加版本号,是动效开发的基本卫生。
推荐阅读:想让博客先「看起来可爱」?看 《怎么把博客变得可爱?我的马卡龙主题改造笔记》;搭建博客用的工具 《WorkBuddy 完整使用教程》;博客里的小游戏 烟花模拟器 / 贪吃蛇 欢迎来玩 🐟