如何根据动画已运行时长计算transform的yScale缩放值
问题根源
当前yScale计算逻辑存在三个核心缺陷,导致动画效果不符合预期:
- 进度值未做归一化处理:硬编码的
0.1 * elapsed计数逻辑与实际动画时长完全脱钩,count到200即终止的判断,和下方elapsed < 1000的帧循环停止条件互相冲突 - 动画起止值未绑定当前状态:展开动画默认从0开始计算缩放值,折叠动画仅做了小于1的边界截断,未处理大于目标缩放值的越界情况,很容易出现画面跳变
- 缺少稳定的时间映射规则:缩放值和流逝时长没有建立固定的线性/缓动映射关系,动画速度会随设备帧率波动
实现方案
核心计算逻辑遵循以下规则:
- 先定义统一的动画总时长,可按需调整,常规交互动画推荐200~400ms
- 每帧优先计算归一化进度值,范围固定在
0~1,数值为1时代表动画完全结束 - 明确当前动画类型对应的起止缩放值:
- 折叠动画:起始值为展开态的
scaleY(例如需求中提到的6),结束值为1 - 展开动画:起始值为1,结束值为展开态的目标
scaleY
- 折叠动画:起始值为展开态的
- 用进度值在起止缩放值之间做插值计算,得到当前帧的
yScale,可按需加入缓动函数优化动画手感 - 动画结束时直接将
yScale赋值为目标值,规避浮点计算带来的误差
修正后的核心代码
替换原有transformAnimation函数即可直接使用:
// 可自定义动画总时长,单位:毫秒 const ANIMATION_DURATION = 300; function transformAnimation(animationType, targetScale) { let startTimestamp; // 绑定当前动画的起止Y轴缩放值 const startYScale = animationType === 'expand' ? 1 : targetScale.y; const endYScale = animationType === 'expand' ? targetScale.y : 1; function step(timestamp) { if (startTimestamp === undefined) startTimestamp = timestamp; const elapsed = timestamp - startTimestamp; // 计算0-1区间的归一化进度 let progress = Math.min(elapsed / ANIMATION_DURATION, 1); // 可选:加入easeOutCubic缓动,动画更自然,不需要可直接删除该行使用线性进度 progress = 1 - Math.pow(1 - progress, 3); // 插值计算当前帧Y轴缩放值 const yScale = startYScale + (endYScale - startYScale) * progress; // 应用容器缩放 dragExpandableContainer.style.transformOrigin = 'bottom left'; dragExpandableContainer.style.transform = `scale(${targetScale.x}, ${yScale})`; // 对内容应用反向缩放,避免内容被拉伸变形 const inverseYScale = 1 / yScale; dragExpandableContents.style.transformOrigin = 'bottom left'; dragExpandableContents.style.transform = `scale(1, ${inverseYScale})`; // 动画未结束则继续请求下一帧 if (progress < 1) { requestAnimationFrame(step); } } requestAnimationFrame(step); }
其他修复建议
原有代码中calculateExpandScale函数硬编码expandedHeight = 100会导致展开缩放值计算错误,建议和折叠逻辑保持一致,取容器实际展开的目标高度,或者直接使用预设的固定展开缩放值(例如需求中的6),避免高度计算偏差导致动画最终状态错位。
内容的提问来源于stack exchange,提问作者Haslo Vardos
相关产品推荐
相关产品推荐

