前端微交互设计:让技术博客更像一个好产品
本文目录
技术博客的微交互不应该喧宾夺主,它的价值是让读者知道当前状态、下一步动作和内容层级。
适合博客的微交互
- 卡片 hover:轻微抬升、边框高亮、背景光泽变化。
- 阅读进度:顶部细进度条或侧边轨迹,不遮挡正文。
- 点赞/踩:即时反馈,短促缩放或粒子效果即可。
- 图片预览:点击放大,支持关闭、缩放和键盘操作。
- 选中文本评论:选中后浮出评论入口,提交后定位到原文。
- 移动端触感:按钮面积足够大,动画时间短,避免卡顿。
滚动渐变的分寸
类似苹果官网的滚动效果,核心不是“炫”,而是用渐变、透明度、视差和内容分段引导视线。正文页面要克制,首页和专题页可以更强。
反例
- 大量动画同时触发,导致低端手机掉帧。
- 文本还没读完就被动效抢走注意力。
- hover 只在桌面有效,移动端没有替代反馈。
给每种输入方式都留一条路
按钮应明确表达默认、焦点、按下、加载、成功、失败和禁用状态。Hover 只能增强桌面反馈,键盘焦点需要清晰可见,触屏用户不能因为没有 hover 而发现不了操作。减少动态效果偏好要停掉非必要的位移、视差和循环动画,但不能把成功/失败反馈一起抹掉。
.article-link { transition: transform 160ms ease, box-shadow 160ms ease; }
@media (hover: hover) and (pointer: fine) {
.article-link:hover { transform: translateY(-2px); }
}
.article-link:focus-visible { outline: 3px solid #087ea4; outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) { .article-link { transition: none; } }
滚动触发的内容揭示适合用 IntersectionObserver,它异步报告元素跨过阈值,适合“一次进入视口就显示”,不适合逐像素读滚动进度。内容默认保持可见,只有脚本确认可用后才启用隐藏态,避免 JavaScript 失败让正文消失。
const sections = document.querySelectorAll("[data-reveal]");
const reduceMotion = matchMedia("(prefers-reduced-motion: reduce)").matches;
if (reduceMotion || !("IntersectionObserver" in window)) {
sections.forEach(section => section.classList.add("is-visible"));
} else {
const observer = new IntersectionObserver(entries => {
for (const entry of entries) {
if (!entry.isIntersecting) continue;
entry.target.classList.add("is-visible");
observer.unobserve(entry.target);
}
}, { rootMargin: "0px 0px -8% 0px", threshold: 0.12 });
sections.forEach(section => observer.observe(section));
}
如果需要持续滚动渐变,先判断是否真的帮助理解;用 requestAnimationFrame 合并读写,优先更新 transform/opacity,避免滚动回调里反复读取布局再改尺寸。首页专题可以有更强的段落变化,长文章正文则应以稳定阅读为主。
阅读进度条应该说明“读到哪里”,而不是伪装成加载状态。用原生 <progress> 提供语义,页面高度变化和窗口缩放时重新计算;滚动事件里只排一个动画帧,避免每个事件都读布局。进度数值频繁变化时不加 aria-live,以免读屏器持续播报数字。
<progress id="reading-progress" max="100" value="0" aria-label="文章阅读进度"></progress>
const progress = document.querySelector("#reading-progress");
let framePending = false;
function updateProgress() {
framePending = false;
const maxScroll = document.documentElement.scrollHeight - window.innerHeight;
progress.value = maxScroll > 0 ? Math.min(100, window.scrollY / maxScroll * 100) : 0;
}
function scheduleProgress() {
if (framePending) return;
framePending = true;
requestAnimationFrame(updateProgress);
}
window.addEventListener("scroll", scheduleProgress, { passive: true });
window.addEventListener("resize", scheduleProgress);
scheduleProgress();
点赞和评论要反馈真实状态
点赞/踩按钮可用 aria-pressed 表示当前选择,旁边用简短状态文字播报计数变化。可以乐观更新,但服务端失败时必须恢复旧值并提示重试;动画只是装饰,不代表保存成功。评论提交还应有发送中、成功、失败和防重复提交状态。
<button type="button" aria-pressed="false" aria-describedby="vote-count">有帮助</button>
<span id="vote-count" aria-live="polite">12 人觉得有帮助</span>
图片灯箱与恢复焦点
放大图片应支持关闭按钮、Escape、键盘焦点、替代文本和窄屏缩放。优先用原生 <dialog> 的 showModal(),打开时把焦点放到合适控件,关闭后交还给打开它的按钮。自制浮层还要处理背景不可交互、焦点管理和滚动恢复,不能只加 position: fixed。
动效上线前的检查
- 同屏只强调一两个元素,不能遮挡标题、选中文本或移动端系统栏。
- 动画结束后布局稳定,长文案和错误提示不会把相邻按钮挤动。
- 在键盘、触屏、减少动态效果、文字放大和低端设备下检查。
- 优先动画
transform、opacity;过多阴影、滤镜和大面积模糊可能触发昂贵绘制。 - 进度提示需要足够对比度和可见范围,但不能覆盖正文或固定导航。
官方参考
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。