“动态设计”这个词放在产品详情页里,通常不是指工业结构,而是指用户从打开页面到点击购买之间,那几秒钟的视觉引导和交互反馈。电动剃须刀这类产品最大的问题是“看起来都差不多”:金属机身、三刀头、防水、Type-C充电。如果页面不转起来、不拆开、不演示刀网与刀片的相对运动,用户很难感知到产品差异。这里我们来看一套基于 Web 的米家电动剃须刀动态设计落地思路,重点讲场景拆解、动效实现、性能控制和验收方式。
这次我们不讨论外壳材质,也不讨论电机扭矩,而是站在前端与动效开发的角度,把“米家电动剃须刀”这个产品对象变成一组可交互的动态展示页面。整个方案围绕四个目标展开:让产品在首页有呼吸感、让刀头结构能被用户主动拆解观察、让核心卖点通过滚动逐层呈现、让所有动效在移动端保持 60 帧不掉链子。内容适合三类读者:正在做电商产品页的视觉开发、需要把工业产品转成 Web 展示的前端工程师、以及做营销 H5 或官网动效的互动设计师。
1. 项目目标与核心能力速览
在开始写代码之前,先把“动态设计”拆成可交付的能力清单。下面这张表可以直接用来做项目排期和评审对齐。
| 能力模块 | 实现方式 | 主要技术点 | 交付形式 |
|---|---|---|---|
| 首屏产品呼吸动效 | CSS + JS 驱动 | 光影浮动、轻微缩放循环 | Web 页面 |
| 产品 360 度展示 | 模型序列帧或 Three.js | 鼠标 / 触摸拖拽、惯性回弹 | 交互组件 |
| 刀头结构分解 | 滚动驱动动画 | 刀网、刀片、机身分层位移动画 | 页面滚动展示 |
| 核心卖点图文动效 | GSAP ScrollTrigger | 文本逐行显现、参数表渐进高亮 | 页面滚动展示 |
| 交互反馈动效 | JavaScript 状态切换 | 按钮涟漪、线条描边、状态文案切换 | 页面组件 |
| 性能监控 | Lighthouse + FPS 面板 | 帧率、长任务、内存占用 | 测试报告 |
这个项目的优势在于不依赖后端服务,所有动效资源可以静态托管。也不涉及模型推理和接口服务,因此对部署环境没有特殊要求。但要注意,动态设计不等于“堆特效”。每个动效都需要有明确的产品解释目的,避免用户被动画干扰而忽略功能信息。
2. 适用场景与使用边界
动态设计在米家电动剃须刀这类产品上,主要承担三件事:第一,把产品从“静态图”变成“有体感的对象”,用户能感受到它的形态、比例和材质;第二,把“看不见的功能”变成“看得见的演示”,例如刀网与刀片的相对运动、IPX7 防水的密封结构、Type-C 充电口的易用性;第三,把页面浏览节奏变成“叙事节奏”,用户在滚动中自然接收到产品卖点。
这套方案最适合的场景是官网产品页、电商详情页、新品发布 H5 和线下大屏展示。不适合的场景包括:需要多语言 SEO 的大量图文内容页、用户需要快速比价的促销列表页、以及网络环境较差且用户流量敏感的活动页。在开始动效开发前,应先和产品负责人确认目标用户是“第一次了解产品”还是“已经在电商平台看完参数”,这两种用户对动效的耐心完全不同。若用户只是二次回访查参数,过长的开屏动画反而会降低访问效率。
合规方面需要特别注意,页面中出现的所有产品渲染图、功能说明文字、参数数据,都应使用官方已公开的素材或经过品牌方授权的设计稿。不要自行编造防水等级、续航时长、刀头材质等参数。涉及“智能”相关功能描述时,应准确限定范围,避免出现夸大宣传的措辞。如果页面中有用户上传内容或评论功能,还需要做好内容审核机制。
3. 动效拆解与技术选型
先把页面按“浏览动线”拆成五个动效段落,每个段落对应一个产品信息层级。
3.1 首屏产品光影动效
首屏不需要复杂结构,重点是用氛围把用户注意力引入产品主体。剃须刀通常呈细长圆柱体造型,适合用上下浮光、边缘高光扫过这类动效来表现金属质感。不要做成整体页面位移,而是让一个半透明渐变层在产品模型上缓慢移动,形成“光照扫过”的效果。这个动效用 CSS keyframes 就能完成,不需要引入额外动画库。
3.2 产品 360 度旋转展示
这是整个方案中“信息密度最高”的交互点。实现上有两条路:一是预渲染 60 到 90 帧序列图,用 JS 根据鼠标位置切换图片;二是直接使用 Three.js 加载产品 glTF 模型。序列图方案兼容性好,移动端性能可控,缺点是素材体积大且不够灵活。Three.js 方案交互顺滑、可做局部高亮,但对低端手机 GPU 有压力。如果项目周期短且没有现成模型文件,建议先做序列图方案;如果团队已有建模能力,直接上 Three.js。
3.3 滚动驱动:刀头结构分解
刀头结构分解是整页最有价值的动效段落。用户在滚动时,刀网、刀片、机身三个部分依次分离并横向排开,同时出现对应文字标签。这个过程用 GSAP 的 ScrollTrigger 比较容易实现,它能将滚动进度与元素位移绑定,并且提供scrub参数实现“滚动多少、运动多少”的同步效果。
3.4 卖点图文逐项呈现
当页面滚动到参数区时,让参数表格或特征卡片在进入视口时逐项显现。这里采用“上移 + 淡入”的组合,位移控制在 20 到 40 像素以内,避免用户看到大幅跳跃。对于需要突出的关键数据,例如“双环刀网”“智能防夹须”,在首次出现时加一个短暂的描边高亮动画,引导视线停留。
3.5 交互反馈动效
页面底部的“功能对比”或“购买”按钮需要明确的点击反馈。比较稳妥的做法是按钮点击后出现一层由内向外扩散的涟漪,同时按钮文案做短暂“加载中”状态切换,最后显示成功文案。这个交互层级不追求炫技,但要保证状态清晰。注意不要使用会触发整页刷新的表单提交方式,以免打断用户浏览。
技术选型上,核心库建议使用 GSAP 3 与 ScrollTrigger 插件,序列帧或模型展示根据实际情况选择。CSS 动画用于简单循环动效,JavaScript 负责滚动联动和点击交互。这样分层的好处在于:没有 JavaScript 时页面仍能完整阅读,只是缺少滚动动效;而加入 JavaScript 后又能获得流畅的视觉叙事。
4. 环境准备与项目初始化
本项目不需要 GPU 或模型推理环境,只需要一个标准的 Web 前端开发环境即可。
4.1 开发环境清单
| 项目 | 建议版本 / 工具 | 说明 |
|---|---|---|
| 操作系统 | Windows 10+ / macOS / Linux | 任意现代系统 |
| Node.js | 16.x 及以上 | 用于运行构建工具 |
| 包管理器 | npm / pnpm / yarn | 任选其一 |
| 浏览器 | Chrome / Edge 最新版 | 开发调试与性能测试 |
| 编辑器 | VS Code | 推荐,不强制 |
4.2 初始化项目
如果是从零开始,可以用 Vite 快速搭建一个静态页面项目。下面的命令是通用模板,实际项目名、路径需要按自己环境调整。
# 创建项目 npm create vite@latest mijia-razor-show -- --template vanilla cd mijia-razor-show # 安装依赖 npm install # 安装动效相关库 npm install gsap如果项目内部已有前端工程,则只需要安装 GSAP,不需要单独创建新项目。序列帧方案还需要准备一组产品旋转图,建议图片尺寸统一、命名规格化,例如frame-001.jpg到frame-090.jpg。
4.3 目录结构建议
mijia-razor-show/ ├── index.html ├── src/ │ ├── main.js │ ├── style.css │ ├── animations/ │ │ ├── hero.js # 首屏动效 │ │ ├── scroll.js # 滚动动效 │ │ ├── rotate-view.js # 360 度展示 │ │ └── interact.js # 交互反馈 │ └── assets/ │ ├── frames/ # 序列帧 │ └── images/ # 卖点图、背景图这个目录结构把动效代码按功能拆开,后续哪一段出问题可以直接定位到文件,不需要在单一入口文件里查找。
5. 核心动效实现与验证
下面按功能模块给出可运行的代码示例。代码只演示核心逻辑,线上使用需要补充错误处理和浏览器兼容逻辑。
5.1 首屏光影扫过
在 CSS 中定义光影层,利用transform和opacity实现循环扫过。
<div class="hero"> <div class="product"></div> <div class="light-sweep"></div> </div>.hero { position: relative; overflow: hidden; height: 100vh; } .product { width: 320px; height: 320px; background: url("./assets/images/product.png") center/contain no-repeat; margin: 0 auto; position: relative; z-index: 1; } .light-sweep { position: absolute; top: 0; left: -30%; width: 30%; height: 100%; background: linear-gradient(to right, transparent, rgba(255, 255, 255, 0.18), transparent); transform: skewX(-20deg); animation: sweep 4s ease-in-out infinite; z-index: 2; pointer-events: none; } @keyframes sweep { 0% { left: -30%; } 60%, 100% { left: 110%; } }验证标准:打开页面后,光影层应该在 4 秒内完成一次从左侧到右侧的移动,随后循环,整个过程不应出现掉帧或页面滚动受阻。首屏动效应在用户首次滚动时自动停止或淡出,避免持续占用性能。
5.2 滚动驱动刀头分解
使用 GSAP ScrollTrigger,将滚动进度映射到刀头三个部分的位移动画。
<section class="structure-section"> <div class="razor-parts"> <div class="part part-net">刀网</div> <div class="part part-blade">刀片</div> <div class="part part-body">机身</div> </div> <div class="structure-copy"> <p class="copy-line">双环刀网,贴合面部轮廓</p> <p class="copy-line">自动研磨刀片,持久锋利</p> <p class="copy-line">IPX7 防水,可整机冲洗</p> </div> </section>import gsap from "gsap"; import { ScrollTrigger } from "gsap/ScrollTrigger"; gsap.registerPlugin(ScrollTrigger); gsap.to(".part-net", { x: -120, opacity: 0.7, ease: "none", scrollTrigger: { trigger: ".structure-section", start: "top center", end: "center center", scrub: 0.5 } }); gsap.to(".part-blade", { y: -60, opacity: 0.8, ease: "none", scrollTrigger: { trigger: ".structure-section", start: "top center", end: "center center", scrub: 0.5 } }); gsap.to(".part-body", { opacity: 1, ease: "none", scrollTrigger: { trigger: ".structure-section", start: "top center", end: "center center", scrub: 0.5 } });如果页面中用到了大量动画对象,建议把相同触发条件的动画写在一个ScrollTrigger容器内,通过containerAnimation或batch方法减少监听数量。对比“每行文字一个 ScrollTrigger”与“整个区块一个 ScrollTrigger”,后者更有利于移动端性能。
验证标准:滚动页面时,刀头三部分应随滚动进度分离或聚合。反向滚动时动画应平滑回退。关键观察点是文案出现节奏是否与结构拆分节奏同步,不建议让结构拆完很久才显示对应文案。
5.3 360 度展示基础交互
这是一个简化版序列帧切换逻辑。真实项目需要根据实际帧数和图片路径调整。
const frames = []; const totalFrames = 90; for (let i = 1; i <= totalFrames; i++) { const num = String(i).padStart(3, "0"); frames.push(`./assets/frames/frame-${num}.jpg`); } const imgElement = document.getElementById("rotate-product"); let currentFrame = 0; let isDragging = false; let startX = 0; document.addEventListener("mousedown", (e) => { isDragging = true; startX = e.clientX; }); document.addEventListener("mousemove", (e) => { if (!isDragging) return; const delta = e.clientX - startX; if (Math.abs(delta) > 20) { currentFrame = (currentFrame + 1) % totalFrames; imgElement.src = frames[currentFrame]; startX = e.clientX; } }); document.addEventListener("mouseup", () => { isDragging = false; });移动端需要改成touchstart、touchmove、touchend,并且要注意阻止默认滚动行为。这个组件建议添加“拖动查看”的引导提示,避免用户不知道可以交互。
验证标准:拖拽产品图,画面应跟随手指方向切换;松开后画面停留在当前帧,不会出现抖动回弹。如果使用 antialias,注意图片加载顺序,可以先预加载相邻 3 帧,减少切换时的闪烁。
5.4 卖点文案显现动画
文本显现采用“位移 + 淡入”组合。避免使用display: none切换,容易出现闪烁。
.copy-line { opacity: 0; transform: translateY(24px); transition: opacity 0.6s ease, transform 0.6s ease; } .copy-line.visible { opacity: 1; transform: translateY(0); }const copyLines = document.querySelectorAll(".copy-line"); const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.add("visible"); } }); }, { threshold: 0.6 } ); copyLines.forEach((line) => observer.observe(line));验证标准:向下滚动页面时,每行文案应在所在区块进入视口 60% 后出现,出现过程不遮挡页面主要内容。清除浏览器缓存后重新加载,观察是否存在文案长时间不可见的问题,这通常是 IntersectionObserver 未触发或 CSS 类名拼接错误。
5.5 交互反馈与状态切换
购买按钮点击后的反馈分三个状态:默认、加载中、成功。通过 JavaScript 切换按钮内文字和状态类。
<button id="buy-btn" class="buy-btn"> <span class="btn-text">立即购买</span> </button>.buy-btn.loading .btn-text { opacity: 0.4; } .buy-btn.success .btn-text { opacity: 1; }const buyBtn = document.getElementById("buy-btn"); buyBtn.addEventListener("click", () => { if (buyBtn.classList.contains("loading")) return; buyBtn.classList.add("loading"); buyBtn.querySelector(".btn-text").textContent = "正在加入购物车..."; // 模拟请求 setTimeout(() => { buyBtn.classList.remove("loading"); buyBtn.classList.add("success"); buyBtn.querySelector(".btn-text").textContent = "已加入"; }, 800); });这个场景不涉及真实交易功能,演示的是前端状态逻辑。若接入真实电商接口,应把模拟setTimeout替换为实际请求处理,并增加请求失败时的状态回退。
6. 性能优化与观察方法
动态设计页面最大的风险是“动效一堆,帧率掉到 30”。在开发过程中就应建立性能观察习惯,而不是等全部做完了再测试。
6.1 帧率与长任务观察
打开 Chrome DevTools 的 Performance 面板,录制一段 5 秒滚动操作。重点记录三个指标:FPS 是否低于 55、连续是否存在超过 200ms 的主线程长任务、GC 是否频繁。如果 FPS 低于 55,优先检查动画属性是否触发了重排。只使用transform和opacity做动画,完全可以避免布局抖动。
6.2 资源体积控制
序列帧方案下,90 张 JPG 图如果不压缩,体积可能达到几十甚至上百 MB。建议每张图压缩至 1200 像素宽以内,并开启 CDN 与浏览器缓存。Three.js 模型方案也要注意 glTF 文件是否包含多余纹理,单文件模型建议控制在 20MB 以内。
6.3 滚动动效的区域管理
ScrollTrigger 监听器不应绑定在每一个小元素上。同一个区块内的多个动画,尽量合并为一个scrollTrigger实例。代码维护时也要注意:页面滚动结束后,是否有残留的animation或requestAnimationFrame在持续执行。可以在页面不可见时暂停循环动效,例如visibilitychange事件中暂停首屏光影动画。
document.addEventListener("visibilitychange", () => { if (document.hidden) { document.querySelector(".light-sweep").style.animationPlayState = "paused"; } else { document.querySelector(".light-sweep").style.animationPlayState = "running"; } });6.4 移动端功耗控制
持续运行的循环动效会显著增加移动端功耗。设计时建议将首屏循环动画限制在 2 到 3 次,用户滑动后立即结束动画,并将动画元素移除或设为不可见。滚动驱动动画在页面完全离开视口后,也应通过 ScrollTrigger 的onLeave回调停止相关动画组件。
7. 兼容性与测试验证
这里列出目标环境的兼容性清单,供提交测试前逐项确认。
| 环境 | 支持情况 | 注意事项 |
|---|---|---|
| Chrome / Edge 桌面端 | 完整支持 | 建议为主要测试环境 |
| Safari iOS | 支持但需注意事件差异 | touch 事件需单独处理 |
| 微信内置浏览器 | 支持 | 序列帧预加载需要时间,避免自动播放被拦截 |
| 低端 Android WebView | 可能性能不足 | 关闭多实例动画,降低图片大小 |
| IE / 老版本 Safari | 不支持现代 API | 需要提供纯静态降级版页面 |
验证测试建议覆盖以下路径:首屏加载后手动滚动到底端再回滚到中部,观察所有动效是否重新触发;快速上下滑动时,动画是否出现位置跳变;在弱网环境重复刷新,序列帧是否出现大面积白图;连续点击购买按钮,状态切换是否被重复触发。
8. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 首屏光影动画不循环 | CSS animation 被浏览器降级或徽章覆盖 | 检查元素层级与 z-index | 调整 animation-play-state,或改为 GSAP 控制 |
| 滚动时刀头位移闪烁 | transform 与 margin 混用 | 审查动画样式,确认只使用 transform | 移除 margin-top 动画,替换为 y 方向位移 |
| 序列帧切换卡顿 | 图片体积过大、未预加载 | 查看 Network 面板 | 压缩图片并增加预加载逻辑 |
| 360 度展示拖动无反应 | touch 事件未处理 | 手机浏览器调试 Console | 补充 touchstart/touchmove 事件 |
| 卖点文案不显示 | IntersectionObserver 未触发或类名错误 | 检查元素进入视口比例 | 将 threshold 降低到 0.3 |
| 页面滚动卡顿 | 动画监听过多 | Performance 面板查看长任务 | 合并 ScrollTrigger 实例,停止不可见动画 |
| 按钮连点重复请求 | 缺少状态锁 | 检查 click 回调 | 增加 loading 状态判断 |
9. 动效开发规范与交付建议
这段内容适合写进团队协作文档。动态设计不是“前端自己埋头做动效”,而是“设计、前端、产品三方共同控制交互节奏”。建议在开发前输出一份动效说明文档,包括每个动效的触发条件、持续时间、缓动函数、是否循环、是否可跳过,这样验收时不会出现“这个效果要改,那个效果不要”的反复拉扯。
代码层面建议做四件事:第一,把动效相关参数提取为常量,例如滚动触发位置、动画时长、位移距离,后续调整不需要翻遍全文件;第二,建立统一的动效工具函数,例如“元素进入视口淡入”“按钮点击波纹”,避免每个开发者写一套不同的实现;第三,所有自动播放动效必须支持prefers-reduced-motion媒体查询降级,尊重用户的减弱动效设置;第四,所有动效在无 JavaScript 环境下都应保证内容完整可见,不能出现“开启 JS 才能看到完整产品参数”的情况。
@media (prefers-reduced-motion: reduce) { .light-sweep, .copy-line { animation: none !important; transition: none !important; opacity: 1 !important; transform: none !important; } }版权方面要确认:产品名称“米家电动剃须刀”及相关品牌素材的使用范围;渲染图与参数不能自行加工;若页面包含用户评价截图,需要确认截图已做隐私处理。涉及“米家”商标的线上页面,应尽量在品牌方授权或官方素材规范下完成,避免营销物料出现品牌侵权风险。
10. 总结与下一步
这次我们从“米家电动剃须刀”的动态设计出发,把项目拆解为首屏光影、360 度展示、滚动刀头结构、卖点文案显现和购买交互反馈五个模块。整体技术方案以 GSAP + ScrollTrigger 为主,辅以 CSS 动画与 IntersectionObserver,不依赖后端服务,静态托管即可上线。值得先动手验证的两个功能是:滚动驱动刀头分解和 360 度序列帧交互,因为这两个模块最影响用户对产品结构的理解,也最容易暴露性能问题。最容易踩的坑是序列帧图片体积失控,以及移动端 touch 事件与桌面端 mouse 事件不统一导致的交互失效。
后续如果要做深度扩展,可以考虑接入 WebGL 展示真实产品模型,或增加自定义颜色搭配功能,让用户在页面内选择机身颜色并实时替换产品贴图。另外还可以加入短视频自动生成逻辑:用户拖动 360 度展示时,可以选择“录制”按钮,将当前拖拽过程导出为一段产品展示视频,用于运营在社群中分发。这个方向需要前端录制库与后端视频合成服务的配合,属于动态设计页面向营销工具延伸的自然路径。先跑通基础页面,再逐步叠加交互深度,是这类项目最稳妥的推进方式。