必须用 SVG 而非 div + transform,因 SVG 支持 d 属性动画与 stroke-dasharray 精确控制沙粒流经细颈的形变过渡,且渲染更稳、无 iOS 闪烁与缩放失真问题。

纯 CSS 做不出真正“沙子流动”的物理效果,但用 SVG + CSS 动画能稳定模拟沙漏倒计时的视觉节奏——关键不在“画多像”,而在“动得准、不跳、不卡”。
为什么必须用 SVG <path></path> 而不是 div + transform
SVG 的 <path></path> 支持 d 属性动画(需配合 animate 或 CSS @keyframes + offset-path),能精确控制沙子“颈部收缩→上下转移”的路径形变过程;而 div 用 height + rotate 只能做剪影式收腰,无法表现沙粒穿过细颈的过渡感。
- Chrome / Safari 对 SVG
path的stroke-dasharray+stroke-dashoffset动画渲染更稳,比 div 的 layout 触发更轻量 - 用
<path class="sand"></path>单独定义沙子区域,可独立设置fill渐变、opacity微调,不影响玻璃容器样式 - 若强行用 div 模拟,
clip-path: polygon()在 iOS 16+ 上有闪烁问题,且无法响应视口缩放导致比例失真
@keyframes 中该 animating d 还是 transform
直接 animating d 属性不可行(CSS 不支持 path 数据插值),必须退回到 transform + opacity + height 组合控制,但目标元素得是 SVG 内嵌的 <rect></rect> 或 <g></g> 容器。
- 上半沙区:用
<rect class="sand-top"></rect>,height从 90 → 0,transform: translateY(0)→translateY(90px),模拟沙粒下坠位移 - 下半沙区:用
<rect class="sand-bottom"></rect>,height从 0 → 90,transform: translateY(90px)→translateY(0),保持底部锚点不动 - 必须加
transform-origin: top center(上)和transform-origin: bottom center(下),否则translateY会连带整个<g></g>偏移,玻璃框错位
旋转沙漏框架时,transform-origin 设成什么才不跳
整个 SVG 容器翻转 180° 表示“重置计时”,但若 transform-origin 设错,翻转瞬间会横向抽搐。实测有效写法是:
立即学习“前端免费学习笔记(深入)”;
-
.hourglass { transform-origin: center 150px; }—— 第二个值必须等于沙漏总高度的一半(如 viewBox="0 0 200 300",则填150px) - 不能写
50% 50%,Firefox 旧版会把百分比解析为相对于 viewport 而非 viewBox - 动画关键帧里
50% { transform: rotate(180deg); }必须严格卡在中点,否则沙子动画和框架翻转不同步,看起来像“沙子还没流完就翻了”
实际部署时最常漏掉的三件事
动画跑起来很顺,但上线后在安卓 WebView 或低配 iPad 上卡顿,大概率是这三点没关:
- 没给 SVG 父容器加
will-change: transform,只在动画开始前动态加,结束后立刻removeAttribute("style"),长期挂着会吃 GPU 显存 - 没设
svg { overflow: hidden; },沙子translateY超出玻璃框时边缘露白边,尤其在 Android Chrome 120+ 中明显 - 没用
animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1)替代ease-in-out,原生 ease 在沙子“刚启动”和“将停住”两段太拖沓,破坏倒计时紧迫感
沙漏动画真正的难点从来不在“怎么画”,而在于“怎么让它不干扰主线程、不抢焦点、不因父级布局重排突然重绘”。所有参数都得拿真实设备连 DevTools 录制 Rendering Frames 验证,光看 CodePen 是不准的。


















