You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

配置为scrubbing的CAKeyframeAnimation如何实现平滑过渡至停止点?

优化方案:消除CAShapeLayer路径移动卡顿,实现平滑过渡

你的卡顿问题根源在于直接跳变timeOffset值——这相当于让图层瞬间从一个位置跳到另一个位置,没有任何过渡动画,自然会产生卡顿感。下面是两种针对性的优化方案:

方案1:给timeOffset添加过渡动画

不要直接赋值timeOffset,而是用CABasicAnimation让它从当前值平滑过渡到目标值,这样图层会沿着路径自然插值移动:

// 假设当前目标时间偏移量是 targetTimeOffset(= pathAnimation.beginTime + n)
let currentOffset = layer.timeOffset
let timeOffsetAnimation = CABasicAnimation(keyPath: "timeOffset")
timeOffsetAnimation.fromValue = currentOffset
timeOffsetAnimation.toValue = targetTimeOffset
timeOffsetAnimation.duration = 0.2 // 可根据需求调整过渡时长
timeOffsetAnimation.fillMode = .forwards
timeOffsetAnimation.isRemovedOnCompletion = false
layer.add(timeOffsetAnimation, forKey: "timeOffsetTransition")
// 同时更新模型层的值,避免动画结束后回弹
layer.timeOffset = targetTimeOffset

这个方法适合非实时连续控制的场景(比如点击按钮让图层跳到路径上的某个固定点),过渡时长可以根据交互需求调整,完全消除跳变卡顿。

方案2:用CADisplayLink平滑实时更新timeOffset

如果是实时连续控制(比如拖拽滑块、跟随手势移动),建议用CADisplayLink同步屏幕刷新频率来逐步更新timeOffset,确保每帧的偏移量变化均匀:

var displayLink: CADisplayLink?
var currentProgress: Double = 0.0 // 当前路径进度(0~1对应动画duration的0~1)
var targetProgress: Double = 0.0 // 目标路径进度

// 初始化CADisplayLink
func setupDisplayLink() {
    displayLink = CADisplayLink(target: self, selector: #selector(updateLayerPosition))
    displayLink?.add(to: .main, forMode: .common)
}

@objc func updateLayerPosition() {
    // 计算每帧需要移动的进度增量(用缓动函数可以让过渡更自然,比如ease-in-out)
    let progressDelta = (targetProgress - currentProgress) * 0.1
    currentProgress += progressDelta
    
    // 当进度接近目标值时停止增量,避免抖动
    if abs(currentProgress - targetProgress) < 0.001 {
        currentProgress = targetProgress
    }
    
    // 更新timeOffset
    layer.timeOffset = pathAnimation.beginTime + currentProgress * pathAnimation.duration
}

// 调用这个方法设置目标位置(比如用户拖拽时更新targetProgress)
func setTargetPosition(progress: Double) {
    targetProgress = max(0, min(1, progress)) // 限制在0~1范围内
}

这种方法能保证每帧的移动量平滑均匀,完全贴合屏幕刷新节奏,不会出现卡顿或跳帧。

额外注意事项

  • 原代码中layer.speed = 0是正确的,但要确保你操作的是添加了动画的shapeLayer,而不是父layer(你的代码里写的是layer.speed = 0,如果layer是父层,建议改为shapeLayer.speed = 0,避免影响其他子图层)。
  • 如果你不需要保留原动画的其他特性,也可以直接用UIView.animate结合UIBezierPath的point(at:)方法,直接计算路径上的点并做帧动画,这种方式更直观,也能保证平滑:
    UIView.animate(withDuration: 0.2) {
        let targetPoint = bezierCurve.cgPath.point(at: targetProgress)
        shapeLayer.position = targetPoint
    }
    

内容的提问来源于stack exchange,提问作者clearlight

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 16:25:35