根据摩擦和张力确定 springWithDamping 和 initialSpringVelocity
Determine springWithDamping and initialSpringVelocity based off from friction and tension
我的设计团队使用摩擦力和张力给我们动画参数。例如:
Has a spring affect (280 tension and 20.5 friction) Over 0.3 seconds
不幸的是,我一直在猜测这些值会转换成什么,然后观察它,如果它看起来很接近,我会把它发过去,他们会批准。但是不断构建具有不同值的项目所花费的时间非常耗时。必须有更简单的方法。
我在 Github 上找到了 Framer,这让我相信阻尼可以这样计算:
let damping: CGFloat = 20.5 / (2 * (1 * 280)).squareRoot()
但是,我似乎无法弄清楚如何根据摩擦力和张力来计算速度。有没有更简单的方法可以为这个开发人员节省一些宝贵的时间?
动画示例:
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: damping,
initialSpringVelocity: ???, options: .curveEaseIn, animations: { // Do Stuff })
您说得对,您链接到的代码可用于计算阻尼比(我受宠若惊,因为我是编写它的人;)。不过,您的派生 Swift 代码似乎有错误。我认为应该是(注意括号中的区别):
let damping: CGFloat = 20.5 / (2 * (1 * 280).squareRoot())
只有当你想在开始动画时给对象一些初始速度时才需要速度值。这样做的用例是,如果对象在开始动画时已经在移动(例如,在拖动交互后开始动画时)。
所以如果对象从非移动状态开始动画,您可以只使用 0 作为初始速度。
我对你的设计团队给你的紧张、摩擦 和 持续时间感到困惑。因为 springs 是模拟物理,张力和摩擦力将模拟一个 spring,它将在特定持续时间后停止动画。具有张力 280
和摩擦 20.5
的 spring 导致持续时间接近 0.65
,而不是 0.3
。 (参见 Framer 中的 computeDuration
函数如何根据张力和摩擦计算持续时间)。这是 coffeescript 版本:
# Tries to compute the duration of a spring,
# but can't for certain velocities and if dampingRatio >= 1
# In those cases it will return null
exports.computeDuration = (tension, friction, velocity = 0, mass = 1) ->
dampingRatio = computeDampingRatio(tension, friction)
undampedFrequency = Math.sqrt(tension / mass)
# This is basically duration extracted out of the envelope functions
if dampingRatio < 1
a = Math.sqrt(1 - Math.pow(dampingRatio, 2))
b = velocity / (a * undampedFrequency)
c = dampingRatio / a
d = - ((b - c) / epsilon)
if d <= 0
return null
duration = Math.log(d) / (dampingRatio * undampedFrequency)
else
return null
return duration
您可以为 iOS 使用的 spring 指定持续时间的原因是它根据阻尼比和持续时间计算 spring 的张力和摩擦力.在引擎盖下,它仍将使用张力和摩擦力进行 spring 模拟。要了解该代码在 iOS 中的工作原理,请查看 Framer 中的 computeDerivedCurveOptions
,它是 iOS 使用的代码的直接端口(通过反汇编和分析 iOS 二进制文件)。
我将@Niels 代码翻译成Swift。
import UIKit
func computeDuration(tension: Double, friction: Double, velocity: Double = 0.0, mass: Double = 1.0) -> Double {
let dampingRatio = computeDampingRatio(tension: tension, friction: friction)
let undampedFrequency = sqrt(tension / mass)
let epsilon = 0.001
var duration = 0.0
//This is basically duration extracted out of the envelope functions
if dampingRatio < 1 {
let a = sqrt(1 - pow(dampingRatio, 2))
let b = velocity / (a * undampedFrequency)
let c = dampingRatio / a
let d = -((b - c) / epsilon)
if d <= 0 {
return duration
}
duration = log(d) / (dampingRatio * undampedFrequency)
}
return duration
}
func computeDampingRatio(tension: Double, friction: Double) -> Double {
let damping = friction / sqrt(2 * (1 * tension))
return damping
}
我将其转换为方便的 UIView 扩展,这样您就可以直接调用 UIView.animate 并产生摩擦。
extension UIView {
class func animate(withTension tension: CGFloat, friction: CGFloat, mass: CGFloat = 1.0, delay: TimeInterval = 0, initialSpringVelocity velocity: CGFloat = 0, options: UIView.AnimationOptions = [], animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) {
let damping = friction / sqrt(2 * (1 * tension))
let undampedFrequency = sqrt(tension / mass)
let epsilon: CGFloat = 0.001
var duration: TimeInterval = 0
if damping < 1 {
let a = sqrt(1 - pow(damping, 2))
let b = velocity / (a * undampedFrequency)
let c = damping / a
let d = -((b - c) / epsilon)
if d > 0 {
duration = TimeInterval(log(d) / (damping * undampedFrequency))
}
}
UIView.animate(withDuration: duration, delay: delay, usingSpringWithDamping: damping, initialSpringVelocity: velocity, options: options, animations: animations, completion: completion)
}
}
有一种更简单的方法可以直接使用摩擦力和张力来实现这种动画,无需任何计算。
有UISpringTimingParameters
,我们用UIViewPropertyAnimator
。
- 创建 UISpringTimingParameters
let springParameters = UISpringTimingParameters(
mass: 1.0,
stiffness: 260, // tension
damping: 20, // friction
initialVelocity: .init(dx: 0, dy: 1.0)
)
注意刚度是张力,阻尼是摩擦。
- 创建动画师并添加动画
let animator = UIViewPropertyAnimator(duration: duration, timingParameters: springParameters)
animator.addAnimations {
animations()
}
animator.startAnimation(afterDelay: delay)
所以,可以做一个方便的扩展:
extension UIView {
class func animate(withDuration duration: TimeInterval, tension: CGFloat, friction: CGFloat, mass: CGFloat = 1.0, delay: TimeInterval = 0, initialSpringVelocity velocity: CGVector = .zero, animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) {
let springParameters = UISpringTimingParameters(
mass: mass, stiffness: tension,
damping: friction,
initialVelocity: .init(dx: 0, dy: 1.0)
)
let animator = UIViewPropertyAnimator(duration: duration, timingParameters: springParameters)
animator.addAnimations {
animations()
}
animator.startAnimation(afterDelay: delay)
}
}
我的设计团队使用摩擦力和张力给我们动画参数。例如:
Has a spring affect (280 tension and 20.5 friction) Over 0.3 seconds
不幸的是,我一直在猜测这些值会转换成什么,然后观察它,如果它看起来很接近,我会把它发过去,他们会批准。但是不断构建具有不同值的项目所花费的时间非常耗时。必须有更简单的方法。
我在 Github 上找到了 Framer,这让我相信阻尼可以这样计算:
let damping: CGFloat = 20.5 / (2 * (1 * 280)).squareRoot()
但是,我似乎无法弄清楚如何根据摩擦力和张力来计算速度。有没有更简单的方法可以为这个开发人员节省一些宝贵的时间?
动画示例:
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: damping,
initialSpringVelocity: ???, options: .curveEaseIn, animations: { // Do Stuff })
您说得对,您链接到的代码可用于计算阻尼比(我受宠若惊,因为我是编写它的人;)。不过,您的派生 Swift 代码似乎有错误。我认为应该是(注意括号中的区别):
let damping: CGFloat = 20.5 / (2 * (1 * 280).squareRoot())
只有当你想在开始动画时给对象一些初始速度时才需要速度值。这样做的用例是,如果对象在开始动画时已经在移动(例如,在拖动交互后开始动画时)。
所以如果对象从非移动状态开始动画,您可以只使用 0 作为初始速度。
我对你的设计团队给你的紧张、摩擦 和 持续时间感到困惑。因为 springs 是模拟物理,张力和摩擦力将模拟一个 spring,它将在特定持续时间后停止动画。具有张力 280
和摩擦 20.5
的 spring 导致持续时间接近 0.65
,而不是 0.3
。 (参见 Framer 中的 computeDuration
函数如何根据张力和摩擦计算持续时间)。这是 coffeescript 版本:
# Tries to compute the duration of a spring,
# but can't for certain velocities and if dampingRatio >= 1
# In those cases it will return null
exports.computeDuration = (tension, friction, velocity = 0, mass = 1) ->
dampingRatio = computeDampingRatio(tension, friction)
undampedFrequency = Math.sqrt(tension / mass)
# This is basically duration extracted out of the envelope functions
if dampingRatio < 1
a = Math.sqrt(1 - Math.pow(dampingRatio, 2))
b = velocity / (a * undampedFrequency)
c = dampingRatio / a
d = - ((b - c) / epsilon)
if d <= 0
return null
duration = Math.log(d) / (dampingRatio * undampedFrequency)
else
return null
return duration
您可以为 iOS 使用的 spring 指定持续时间的原因是它根据阻尼比和持续时间计算 spring 的张力和摩擦力.在引擎盖下,它仍将使用张力和摩擦力进行 spring 模拟。要了解该代码在 iOS 中的工作原理,请查看 Framer 中的 computeDerivedCurveOptions
,它是 iOS 使用的代码的直接端口(通过反汇编和分析 iOS 二进制文件)。
我将@Niels 代码翻译成Swift。
import UIKit
func computeDuration(tension: Double, friction: Double, velocity: Double = 0.0, mass: Double = 1.0) -> Double {
let dampingRatio = computeDampingRatio(tension: tension, friction: friction)
let undampedFrequency = sqrt(tension / mass)
let epsilon = 0.001
var duration = 0.0
//This is basically duration extracted out of the envelope functions
if dampingRatio < 1 {
let a = sqrt(1 - pow(dampingRatio, 2))
let b = velocity / (a * undampedFrequency)
let c = dampingRatio / a
let d = -((b - c) / epsilon)
if d <= 0 {
return duration
}
duration = log(d) / (dampingRatio * undampedFrequency)
}
return duration
}
func computeDampingRatio(tension: Double, friction: Double) -> Double {
let damping = friction / sqrt(2 * (1 * tension))
return damping
}
我将其转换为方便的 UIView 扩展,这样您就可以直接调用 UIView.animate 并产生摩擦。
extension UIView {
class func animate(withTension tension: CGFloat, friction: CGFloat, mass: CGFloat = 1.0, delay: TimeInterval = 0, initialSpringVelocity velocity: CGFloat = 0, options: UIView.AnimationOptions = [], animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) {
let damping = friction / sqrt(2 * (1 * tension))
let undampedFrequency = sqrt(tension / mass)
let epsilon: CGFloat = 0.001
var duration: TimeInterval = 0
if damping < 1 {
let a = sqrt(1 - pow(damping, 2))
let b = velocity / (a * undampedFrequency)
let c = damping / a
let d = -((b - c) / epsilon)
if d > 0 {
duration = TimeInterval(log(d) / (damping * undampedFrequency))
}
}
UIView.animate(withDuration: duration, delay: delay, usingSpringWithDamping: damping, initialSpringVelocity: velocity, options: options, animations: animations, completion: completion)
}
}
有一种更简单的方法可以直接使用摩擦力和张力来实现这种动画,无需任何计算。
有UISpringTimingParameters
,我们用UIViewPropertyAnimator
。
- 创建 UISpringTimingParameters
let springParameters = UISpringTimingParameters(
mass: 1.0,
stiffness: 260, // tension
damping: 20, // friction
initialVelocity: .init(dx: 0, dy: 1.0)
)
注意刚度是张力,阻尼是摩擦。
- 创建动画师并添加动画
let animator = UIViewPropertyAnimator(duration: duration, timingParameters: springParameters)
animator.addAnimations {
animations()
}
animator.startAnimation(afterDelay: delay)
所以,可以做一个方便的扩展:
extension UIView {
class func animate(withDuration duration: TimeInterval, tension: CGFloat, friction: CGFloat, mass: CGFloat = 1.0, delay: TimeInterval = 0, initialSpringVelocity velocity: CGVector = .zero, animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) {
let springParameters = UISpringTimingParameters(
mass: mass, stiffness: tension,
damping: friction,
initialVelocity: .init(dx: 0, dy: 1.0)
)
let animator = UIViewPropertyAnimator(duration: duration, timingParameters: springParameters)
animator.addAnimations {
animations()
}
animator.startAnimation(afterDelay: delay)
}
}