Skip to content

Commit 67d47ed

Browse files
author
Adam Miskiewicz
committed
Add closed-form damped harmonic oscillator algorithm to Animated.spring
1 parent e964a7f commit 67d47ed

File tree

11 files changed

+483
-309
lines changed

11 files changed

+483
-309
lines changed

Libraries/Animated/src/AnimatedImplementation.js

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,7 @@ module.exports = {
698698
*
699699
* - `velocity`: Initial velocity. Required.
700700
* - `deceleration`: Rate of decay. Default 0.997.
701+
* - `isInteraction`: Whether or not this animation creates an "interaction handle" on the `InteractionManager`. Default true.
701702
* - `useNativeDriver`: Uses the native driver when true. Default false.
702703
*/
703704
decay,
@@ -712,21 +713,49 @@ module.exports = {
712713
* - `easing`: Easing function to define curve.
713714
* Default is `Easing.inOut(Easing.ease)`.
714715
* - `delay`: Start the animation after delay (milliseconds). Default 0.
716+
* - `isInteraction`: Whether or not this animation creates an "interaction handle" on the `InteractionManager`. Default true.
715717
* - `useNativeDriver`: Uses the native driver when true. Default false.
716718
*/
717719
timing,
718720
/**
719-
* Spring animation based on Rebound and
720-
* [Origami](https://facebook.github.io/origami/). Tracks velocity state to
721-
* create fluid motions as the `toValue` updates, and can be chained together.
721+
* Animates a value according to an analytical spring model based on
722+
* [damped harmonic oscillation](https://en.wikipedia.org/wiki/Harmonic_oscillator#Damped_harmonic_oscillator).
723+
* Tracks velocity state to create fluid motions as the `toValue` updates, and
724+
* can be chained together.
722725
*
723-
* Config is an object that may have the following options. Note that you can
724-
* only define bounciness/speed or tension/friction but not both:
726+
* Config is an object that may have the following options.
727+
*
728+
* Note that you can only define one of bounciness/speed, tension/friction, or
729+
* stiffness/damping/mass, but not more than one:
730+
*
731+
* The friction/tension or bounciness/speed options match the spring model in
732+
* [Facebook Pop](https://github.com/facebook/pop), [Rebound](http://facebook.github.io/rebound/),
733+
* and [Origami](http://origami.design/).
725734
*
726735
* - `friction`: Controls "bounciness"/overshoot. Default 7.
727736
* - `tension`: Controls speed. Default 40.
728737
* - `speed`: Controls speed of the animation. Default 12.
729738
* - `bounciness`: Controls bounciness. Default 8.
739+
*
740+
* Specifying stiffness/damping/mass as parameters makes `Animated.spring` use an
741+
* analytical spring model based on the motion equations of a [damped harmonic
742+
* oscillator](https://en.wikipedia.org/wiki/Harmonic_oscillator#Damped_harmonic_oscillator).
743+
* This behavior is slightly more precise and faithful to the physics behind
744+
* spring dynamics, and closely mimics the implementation in iOS's
745+
* CASpringAnimation primitive.
746+
*
747+
* - `stiffness`: The spring stiffness coefficient. Default 100.
748+
* - `damping`: Defines how the spring’s motion should be damped due to the forces of friction. Default 10.
749+
* - `mass`: The mass of the object attached to the end of the spring. Default 1.
750+
*
751+
* Other configuration options are as follows:
752+
*
753+
* - `velocity`: The initial velocity of the object attached to the spring. Default 0 (object is at rest).
754+
* - `overshootClamping`: Boolean indiciating whether the spring should be clamped and not bounce. Default false.
755+
* - `restDisplacementThreshold`: The threshold of displacement from rest below which the spring should be considered at rest. Default 0.001.
756+
* - `restSpeedThreshold`: The speed at which the spring should be considered at rest in pixels per second. Default 0.001.
757+
* - `delay`: Start the animation after delay (milliseconds). Default 0.
758+
* - `isInteraction`: Whether or not this animation creates an "interaction handle" on the `InteractionManager`. Default true.
730759
* - `useNativeDriver`: Uses the native driver when true. Default false.
731760
*/
732761
spring,

Libraries/Animated/src/SpringConfig.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@
1313
'use strict';
1414

1515
type SpringConfigType = {
16-
tension: number,
17-
friction: number,
16+
stiffness: number,
17+
damping: number,
1818
};
1919

20-
function tensionFromOrigamiValue(oValue) {
20+
function stiffnessFromOrigamiValue(oValue) {
2121
return (oValue - 30) * 3.62 + 194;
2222
}
2323

24-
function frictionFromOrigamiValue(oValue) {
24+
function dampingFromOrigamiValue(oValue) {
2525
return (oValue - 8) * 3 + 25;
2626
}
2727

@@ -30,8 +30,8 @@ function fromOrigamiTensionAndFriction(
3030
friction: number,
3131
): SpringConfigType {
3232
return {
33-
tension: tensionFromOrigamiValue(tension),
34-
friction: frictionFromOrigamiValue(friction)
33+
stiffness: stiffnessFromOrigamiValue(tension),
34+
damping: dampingFromOrigamiValue(friction),
3535
};
3636
}
3737

@@ -91,8 +91,8 @@ function fromBouncinessAndSpeed(
9191
);
9292

9393
return {
94-
tension: tensionFromOrigamiValue(bouncyTension),
95-
friction: frictionFromOrigamiValue(bouncyFriction)
94+
stiffness: stiffnessFromOrigamiValue(bouncyTension),
95+
damping: dampingFromOrigamiValue(bouncyFriction),
9696
};
9797
}
9898

Libraries/Animated/src/__tests__/Animated-test.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ describe('Animated tests', () => {
135135
expect(callback).toBeCalled();
136136
});
137137

138-
it('send toValue when a spring stops', () => {
138+
it('send toValue when an underdamped spring stops', () => {
139139
var anim = new Animated.Value(0);
140140
var listener = jest.fn();
141141
anim.addListener(listener);
@@ -147,6 +147,18 @@ describe('Animated tests', () => {
147147
expect(anim.__getValue()).toBe(15);
148148
});
149149

150+
it('send toValue when a critically damped spring stops', () => {
151+
var anim = new Animated.Value(0);
152+
var listener = jest.fn();
153+
anim.addListener(listener);
154+
Animated.spring(anim, {stiffness: 8000, damping: 2000, toValue: 15}).start();
155+
jest.runAllTimers();
156+
var lastValue = listener.mock.calls[listener.mock.calls.length - 2][0].value;
157+
expect(lastValue).not.toBe(15);
158+
expect(lastValue).toBeCloseTo(15);
159+
expect(anim.__getValue()).toBe(15);
160+
});
161+
150162
it('convert to JSON', () => {
151163
expect(JSON.stringify(new Animated.Value(10))).toBe('10');
152164
});

Libraries/Animated/src/__tests__/AnimatedNative-test.js

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -594,12 +594,32 @@ describe('Native Animated', () => {
594594
jasmine.any(Number),
595595
{
596596
type: 'spring',
597-
friction: 16,
597+
stiffness: 679.08,
598+
damping: 16,
599+
mass: 1,
600+
initialVelocity: 0,
601+
overshootClamping: false,
602+
restDisplacementThreshold: 0.001,
603+
restSpeedThreshold: 0.001,
604+
toValue: 10,
605+
iterations: 1,
606+
},
607+
jasmine.any(Function)
608+
);
609+
610+
Animated.spring(anim, {toValue: 10, stiffness: 1000, damping: 500, mass: 3, useNativeDriver: true}).start();
611+
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
612+
jasmine.any(Number),
613+
jasmine.any(Number),
614+
{
615+
type: 'spring',
616+
stiffness: 1000,
617+
damping: 500,
618+
mass: 3,
598619
initialVelocity: 0,
599620
overshootClamping: false,
600621
restDisplacementThreshold: 0.001,
601622
restSpeedThreshold: 0.001,
602-
tension: 679.08,
603623
toValue: 10,
604624
iterations: 1,
605625
},
@@ -612,12 +632,13 @@ describe('Native Animated', () => {
612632
jasmine.any(Number),
613633
{
614634
type: 'spring',
615-
friction: 23.05223140901191,
635+
damping: 23.05223140901191,
616636
initialVelocity: 0,
617637
overshootClamping: false,
618638
restDisplacementThreshold: 0.001,
619639
restSpeedThreshold: 0.001,
620-
tension: 299.61882352941177,
640+
stiffness: 299.61882352941177,
641+
mass: 1,
621642
toValue: 10,
622643
iterations: 1,
623644
},

0 commit comments

Comments
 (0)