纯CSS3齿轮咬合滚动动画:齿比计算与animation参数详解
简介纯CSS3实现的齿轮咬合滚动动画源码是一份极简的前端交互示例面向对CSS动画与视觉实现感兴趣的学习者展示了仅依靠样式表完成齿轮精确咬合与持续滚动的完整思路。包体仅1个HTML文件大小约1KB没有冗余依赖可直接在浏览器中打开便于逐行研读与实时修改参数。目前已有79人学习下载适合作为CSS3进阶的微型案例。实现中运用了keyframes动画关键帧、transform的rotate旋转、transition平滑过渡、相对单位与定位来保证齿轮在不同视口下的咬合节奏同时借助伪元素与HSLA色值补充齿轮细节并可能涉及媒体查询与will-change等性能优化手段为读者提供了多个可拆解的知识点。开发者可以动手调整齿数、转速或加入暂停交互将这套齿轮模型迁移至加载动画、仪表盘或机械感UI中体会纯CSS实现动态视觉的轻量而可控的工作方式。1. 纯CSS3齿轮咬合滚动动画不写JS先把齿比算明白齿轮动画只要一涉及「咬合」就比单独旋转难一个量级两个齿数不同的轮子转一圈的时间、方向、起始角度必须严格对齐差半度都会让人一眼看出「打齿」。很多人第一反应是上 Canvas 或 SVG但纯 CSS3 动画天生够用——animation本身就支持linear无限循环、reverse反向执行、负delay调整相位这三个能力正好是齿轮啮合的全部数学基础。真正拦路的是两件事齿形怎么用CSS画出来以及把齿数比换算成animation-duration。这篇顺着「先把机械规则讲清再落到可运行代码」的顺序给出一套不用一行 JavaScript 的实现方案最后讲怎么验证咬合、怎么把项目整理成可直接分发运行的源码压缩包。适合要写加载页、装饰性 H5 动效或者想把animation节奏控制彻底吃透的前端开发者。2. 咬合前的几何课齿数、角速度与CSS动画参数一一对应2.1 齿轮咬合的三条硬规则把两个真实齿轮放在一起观察能得到三条运动学约束它们直接决定动画参数怎么写。第一条外啮合的两个齿轮转向相反一个顺时针另一个必然是逆时针。第二条转速与齿数成反比20 齿的齿轮转 1 圈10 齿的齿轮在同一时间里已经转了 2 圈。第三条啮合点瞬时线速度相同两轮在接触点的切向速度相等所以小齿轮角速度大但齿尖走过的弧长一样。这三条规则落到 CSS 上分别是转向由animation-direction: reverse控制转速比由animation-duration的比值控制初始咬合位置由animation-delay的负值来对齐相位。只要这三个参数写对剩下的纯视觉效果只是锦上添花。机械里还有模数、压力角等概念动画只保留「齿数比决定转速比、直径与齿数成正比」这两条就够用不必做全套齿轮设计。2.2 把运动规则翻译成 animation 参数CSS 的animation简写属性背后有多个子属性写齿轮动画时最常接触的就是下面这几个机械概念CSS 子属性取值说明转一圈的周期animation-duration齿数越多duration越长和角速度成反比转向animation-directionreverse表示从关键帧尾部往回执行等效反方向旋转初始相位animation-delay负值表示动画已经播了一段时间直接从那一帧开始永续旋转animation-iteration-countinfinite让齿轮不停转是否匀速animation-timing-functionlinear保证角速度恒定缺失会忽快忽慢播放启停animation-play-statepaused用于调试咬合位转速比换算公式也很直接。设大齿轮齿数为 z₁小齿轮为 z₂大齿轮转一圈用时 T₁则小齿轮的周期T₂ T₁ × z₂ / z₁例如大齿轮 30 齿、小齿轮 15 齿T₁ 取 6 秒时T₂ 6 × 15 / 30 3 秒。小齿轮齿数越少周期越短转速越快和物理直觉一致。方向则直接取反。这个公式是整个齿轮动画里唯一的硬计算。2.3 为什么不用 JS 或 SVG 轮子常见替代方案是requestAnimationFrame驱动transform或者用 SVG 的animateTransform它们都能实现旋转但在持续滚动场景各有别扭处。JS 方案每帧回调主线程一旦忙碌动画时间轴就抖动在低端手机上尤其明显SVG 的animateTransform不是不能做但它运行在 SVG 坐标系里要和 HTML 文本、布局混排时反而绕。纯 CSS3 动画在浏览器里由合成器线程处理只要动画属性限定在transform/opacity主线程再忙也不太影响帧率。这正是「纯CSS」在机械装饰动效里仍然有存在感的原因它是能量损耗最小的旋转通道。开发者只需要算对duration和delay浏览器负责在独立图层里转圈这是 SVG 和 JS 方案都不容易拿到的收益。3. 用CSS3徒手画齿轮搭出第一组咬合体3.1 齿形绘制方案对比画齿形有三条路。一是内联 SVG 画路径再用 CSS 旋转最精细但引入了额外 DOM二是用多个矩形伪元素拼齿代码重复且定位繁琐三是用repeating-conic-gradient一个background属性就画出齿顶与齿槽的周期图案没有任何额外节点背景跟随元素transform一起旋转坐标系天然统一。关键参数是「一个齿占多少度」。齿轮有 z 个齿每齿在圆周上占360 / z度这段角度再一分为二一半是齿顶、一半是齿槽。以 16 齿为例每齿占 22.5 度齿顶和齿槽各 11.25 度写法如下.gear { width: 120px; height: 120px; border-radius: 50%; background: repeating-conic-gradient( #2d3436 0deg 11.25deg, #dfe6e9 11.25deg 22.5deg ); }这段渐变从正右方向开始深色段是齿顶浅色段是齿槽每 22.5 度完成一次齿距循环。repeating-conic-gradient会把颜色片段沿圆周无限重复所以不管 14 齿还是 32 齿改度数即可不需要新增 DOM 节点。注意齿数与角度的整除关系如果 360 不能被齿数整除渐变会在起点处断裂形成一条可见接缝。接着用伪元素挖空中心。::before覆盖齿根圆让它露出和页面一样的底色视觉上齿与齿之间就有了空隙::after画轴孔.gear { position: relative; /* 上面的尺寸和背景保持不变 */ } .gear::before { content: ; position: absolute; inset: 18%; border-radius: 50%; background: #fafafa; /* 必须与页面背景色一致 */ } .gear::after { content: ; position: absolute; inset: 36%; border-radius: 50%; background: #2d3436; }inset的百分比是相对元素尺寸的18%表示内圆半径约为外圆的 82%这个比例决定齿根的深度。百分比越大齿越短。齿顶是扇形直角齿和真正的渐开线齿形有差别但动画表演里没人会停下来量压力角视觉周期正确就达标。3.2 同齿数齿轮最小咬合 DEMO先放两个 16 齿齿轮直径都是 120px。由于齿数相同、直径相同转速也相同只需要保证方向相反。HTML 结构是两个兄弟节点包在一个定位容器里div classrig same-size div classgear g1/div div classgear g2/div /div.rig { position: relative; width: 240px; height: 140px; background: #fafafa; } .g1 { position: absolute; left: 0; top: 10px; } .g2 { position: absolute; left: 114px; top: 10px; } .g1 { animation: spin 6s linear infinite; } .g2 { animation: spin 6s linear infinite reverse; } keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }两个齿轮的圆心距设为 114px而元素直径是 120px相当于留了 6px 的齿高重叠量齿尖恰好能插进对方的齿槽。这个值需要在浏览器里微调每次增减 2px 观察齿间是否挤压。g2用reverse后视觉上就是从左往右看时两个轮子一个顺时针、一个逆时针啮合关系成立。不要试图让两个同齿数齿轮用不同的duration那只会让它们越转越错位。同齿数意味着周期必须一致唯一的自由度是初始相位由animation-delay微调。3.3 不同齿数转速比与圆心距修正真正的「咬合」感往往来自大小齿轮搭配。把 3.1 的齿轮改成 32 齿大轮和 16 齿小轮大轮直径 160px小轮 80px。大轮周期 6 秒小轮按公式 T₂ T₁ × z₂ / z₁也就是 6 × 16 / 32 3 秒方向取reverse.g-big { width: 160px; height: 160px; background: repeating-conic-gradient( #2d3436 0deg 5.625deg, #dfe6e9 5.625deg 11.25deg ); position: absolute; left: 0; top: 20px; animation: spin 6s linear infinite; } .g-small { width: 80px; height: 80px; background: repeating-conic-gradient( #2d3436 0deg 11.25deg, #dfe6e9 11.25deg 22.5deg ); position: absolute; left: 114px; top: 60px; animation: spin 3s linear infinite reverse; animation-delay: -0.35s; }圆心距不再是两个直径相加的一半。大轮半径 80px小轮 40px理想中心距 120px但齿高重叠要占掉空间这里取 114px 左右让齿尖互相嵌入。animation-delay: -0.35s的负值表示小轮跳过前 0.35 秒的旋转量从某个已转过的角度开始播放作用就是对齐两轮第一帧的咬合相位。这个 0.35 秒怎么来先把动画暂停用浏览器开发者工具手动给元素加transform: rotate(...)调到两齿刚好贴合的位置记下角度差再用delay -T × 角度差 / 360换算。后面第五章会给出不用手动算数的验证技巧。4. 咬合 滚动让齿轮组在画面上滚起来4.1 两种「滚动」别混为一谈标题里的「滚动动画」其实是两层意思一层是齿轮自身持续旋转前面已经实现另一层是整个齿轮组作为刚体在视口里平移比如加载页里一组齿轮从右侧滚进画面或者齿轮组做成页脚的装饰履带。两层滚动要放在不同的元素上。外层容器负责translateX整体位移内层每个齿轮负责自己的rotate。如果把位移和旋转写进同一个元素的transform后者会覆盖前者动画直接失效。这是「滚动 咬合」组合时最常见的翻车点。4.2 齿轮组进场CSS3动画延迟与末态保持给齿轮加进场动画时会遇到 CSS3 动画两个典型问题延迟期间元素是显示还是隐藏以及动画结束后是否保持最后一帧。这两件事由animation-fill-mode控制。多个动画用逗号分隔各自的delay也按逗号对应.gear-a { animation: spin 6s linear infinite, enter 1.2s ease-out both; } .gear-b { animation: spin 3s linear infinite reverse, enter 1.2s ease-out both; animation-delay: 0s, 0.4s; } keyframes enter { from { opacity: 0; transform: scale(0.6); } to { opacity: 1; transform: scale(1); } }both的作用是让enter动画在延迟期间先应用from状态齿轮透明且缩小结束后停留在to状态。如果漏掉它延迟期间元素以默认状态展示视觉上会闪一下动画结束后又跳回opacity: 1的默认值过渡就断了。.gear-b的0s, 0.4s是分别给spin和enter设延迟进场比.gear-a晚 0.4 秒形成一前一后介入的节奏。注意进场动画的transform: scale(...)和自转的transform: rotate(...)属于同一个属性如果写在同一个元素上会互相覆盖。上面这段代码里它们确实都作用在.gear-a上但一个在spin里、一个在enter里同一时刻只有同名列的动画在写transformspin和enter同时激活时会冲突。实际要拆开把enter放在外层包装元素上spin放在齿轮元素上或者让enter只动画opacity。先用包装元素方案div classrig div classgear-a-wrapdiv classgear-a/div/div div classgear-b-wrapdiv classgear-b/div/div /div.gear-a-wrap跑enter.gear-a跑spin。两个动画各管一个元素transform不再打架。这是同时跑位移、旋转、缩放进场时的通用分层原则。4.3 滚动循环执行次数、逆向播放与无缝衔接齿轮组要做成背景履带通常选两种节奏。一种是无缝单向循环一种是在页面里来回滚动的往复效果。无缝单向循环需要内容复制两份容器内部用过长的translateX(-50%)制造接缝。HTML 里放两个完全相同的齿轮组副本卷轴容器宽度设为两倍动画从 0 位移到 -50% 时第二份内容顶到第一份的位置视觉上就接续上了.belt { display: flex; width: max-content; animation: slide 12s linear infinite; } keyframes slide { from { transform: translateX(0); } to { transform: translateX(-50%); } }这里-50%相对的是.belt自身宽度。因为两副本等宽移动自身宽度的一半正好完成一次循环。animation-direction在这个场景不需要写默认normal就一直往一个方向滚。往复效果则在动画上多加一个alternate.belt-bounce { animation: slide 12s linear infinite alternate; }alternate每次循环结束后自动逆向执行配合infinite就成了来回滚。CSS3 动画的执行次数和逆向播放组合起来能覆盖大多数滚动节奏infinite normal永续单向infinite alternate永续往复1 normal播一次停住。需要「播一次停住且停留在终点」时记得在动画末尾加both只写normal infinite停不下来只写forwards又会把延迟阶段的状态弄丢。5. 验证咬合同步把项目整理成可直接跑的源码包5.1 用 CSS 修车闸一个 checkbox 暂停咬合咬合验证最直接的办法是让动画暂停看齿尖是否停在齿槽正中央。虽然实现里没有 JavaScript但验证可以用纯 CSS 的 checkbox hack一个被checked的复选框配合通用兄弟选择器控制后面所有动画的播放状态。把下面这段放进上面任意一节的结构里input typecheckbox idbrake classbrake-input checked label forbrake咬死齿轮/label div classbelt div classrig div classgear-a-wrapdiv classgear-a/div/div div classgear-b-wrapdiv classgear-b/div/div /div div classrig div classgear-a-wrapdiv classgear-a/div/div div classgear-b-wrapdiv classgear-b/div/div /div /div.brake-input:checked ~ .belt * { animation-play-state: paused; }复选框的checked属性能在刷新后依然保持状态默认勾选打开页面就直接是静止的咬合状态。取消勾选齿轮组开始滚动。animation-play-state: paused不重置动画进度只是把当前帧冻结所以检查完松开动画从刚才的位置继续不会跳帧。5.2 咬合不同步的三种症状与快速修法症状原因修法齿尖互相穿透像打架圆心距太小或animation-delay相位没对齐调大两轮left间距用 5.1 暂停法看齿槽间隙调负delay转速忽快忽慢失去机械感animation-timing-function不是linear每个动画显式写linear不要依赖默认ease小齿轮转速明显不对duration比值算错按T₂ T₁ × z₂ / z₁反推齿数比反了是最常见的进场前元素闪烁animation-fill-mode缺失进场动画补both让末态持续生效5.3 把源码打包成 zip目录结构与交付细节一个纯 CSS 齿轮动画项目不需要构建工具推荐按这种最简目录组织gear-bite-roll/ ├── index.html ├── css/ │ └── style.css └── assets/ └── (可选背景图不使用则留空)index.html里用link relstylesheet hrefcss/style.css引入样式所有齿轮、动画、checkbox 调试开关全在 HTML 和 CSS 内完成。打包成 zip 前把调试用的label和:checked规则注释掉或者保留它们并在交付说明里写一句「勾选复选框可暂停动画检查咬合」。因为没有外部字体、图片和接口依赖解压后直接把index.html拖进 Chrome、Edge 或 Firefox 就能运行也不需要起本地服务器。最后提醒一点zip 内的文件名全部用英文小写避免个别解压工具对中文文件名的编码处理不一致导致 HTML 引用路径失效。本文还有配套的精品资源点击获取