Skip to content

Commit

Permalink
Merge pull request #189 from Popmotion/spring
Browse files Browse the repository at this point in the history
Adding dedicated `spring` action
  • Loading branch information
mattgperry authored Aug 7, 2017
2 parents d53f7d3 + 2e0f569 commit 77269a7
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 2 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Create unique animations and interactions with tweens, physics and input tracking.

Popmotion is:
- **Tiny:** At ~9kb, it's 75% smaller than GreenSock.
- **Tiny:** At ~10kb, it's 75% smaller than GreenSock.
- **Fast:** Stands up to popular alternatives in [performance tests](http://codepen.io/popmotion/pen/zNYXmR).
- **Compatible:** Full browser support and preloaded with CSS, SVG and SVG path renderers.
- **Composable:** Actions and output functions can be composed to [create complex motion systems](http://codepen.io/popmotion/pen/EZaPxZ).
Expand Down
47 changes: 47 additions & 0 deletions docs/api/action/spring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
title: Spring
description: A UI spring simulation, based on Apple's CASpringAnimation.
category: action
---

# Spring

A highly-accurate spring simulation based on Apple's `CASpringAnimation` implementation.

This simulation offers greater variety and accuracy than the basic spring equations in [`physics`](/api/physics).

`spring(props <Object>)`

## Props
- `stiffness <Number>`: The spring stiffness (default: `100`)
- `damping <Number>`: The strength of the friction force used to dampen motion (default: `10`)
- `mass <Number>`: Mass of the moving object. (default: `1.0`)
- `velocity <Number>`: The initial velocity of the spring. (default: `0.0`)
- `from <Number>`: Start from this number. (default `0`)
- `to <Number>`: End at this number. (default `0`)
- `restDisplacement <Number>`: End the animation if the distance to target is below this value (and `restSpeed`) (default: `0.01`)
- `restSpeed <Number>`: End the animation if the speed drops below this value (and `restDisplacement`) (default: `0.01`)

## Methods

[...Action](/api/action)

## Playground

```javascript
import { spring } from 'popmotion';
```

```marksy
<Example template="Ball">{`
const ball = document.querySelector('.ball');
const ballRenderer = css(ball);
spring({
mass: 2,
stiffness: 1000,
damping: 50,
to: 300
}).start();
`}</Example>
```
5 changes: 5 additions & 0 deletions packages/popmotion-react/stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { linkTo } from '@storybook/addon-links';
import DragSingleChild from './react/DragSingleChild';
import TransitionGroup from './react/TransitionGroup';
import Toggle from './popmotion/Toggle';
import Spring from './popmotion/Spring';
import SpinnableDom from './spinnable/SpinnableDOM';
import SpinnableSvg from './spinnable/SpinnableSVG';
import Timeline from './timeline/Timeline';
Expand Down Expand Up @@ -37,5 +38,9 @@ storiesOf('timeline').add('Timeline stagger', () => <TimelineStagger />);
// Draggable
storiesOf('draggable').add('Drag XY', () => <DragXY />);

// Spring
storiesOf('spring').add('Spring', () => <Spring />);


// Inertia
//storiesOf('inertia').add('Throw to inertia', () => <Inertia />);
41 changes: 41 additions & 0 deletions packages/popmotion-react/stories/popmotion/Spring.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import { spring } from '../../../../lib/popmotion';
import { MotionValue } from '../../src';

const makeSpring = (value, to) => spring({
stiffness: 1000,
damping: 500,
mass: 3,
from: value.get(),
to,
velocity: value.getVelocity(),
onUpdate: value
});

export default () => (
<MotionValue
onStateChange={{
on: ({value}) => {
console.log(value.getVelocity())
makeSpring(value, 800).start();
},
off: ({value}) => {
console.log(value.getVelocity())
makeSpring(value, 0).start();
}
}}
>
{({ v, state, setStateTo }) => (
<div style={{ width: '100vw', height: '100vh' }} onClick={state === 'on' ? setStateTo.off : setStateTo.on}>
<div
style={{
background: 'red',
width: '100px',
height: '100px',
transform: 'translateX(' + v + 'px)'
}}
/>
</div>
)}
</MotionValue>
);
2 changes: 2 additions & 0 deletions site/components/examples/Example.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
trackOffset,
tween,
stagger,
spring,
value,
Renderer,
css,
Expand Down Expand Up @@ -102,6 +103,7 @@ export default ({ children, template, id, isReactComponent=false }) => {
pointer,
trackOffset,
tween,
spring,
stagger,
value,
Renderer,
Expand Down
2 changes: 1 addition & 1 deletion site/templates/homepage/USPs.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export default () => (
</USP>
<USP>
<Header>Tiny</Header>
<Description>At ~9kb, it's <Strong>75% smaller</Strong> than Greensock TweenMax. Great for users on slower connections.</Description>
<Description>At ~10kb, it's <Strong>75% smaller</Strong> than Greensock TweenMax. Great for users on slower connections.</Description>
</USP>
<USP>
<Header>Blazing Fast</Header>
Expand Down
93 changes: 93 additions & 0 deletions src/actions/spring.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
The closed-form damped harmonic oscillating spring.
Or, spring.
This is a direct port of Adam Miskiewicz's (@skevy) React Animated
PR #15322 https://github.com/facebook/react-native/pull/15322/
```
spring({
mass: 2,
damping: 10,
stiffness: 100,
to: 100
}).start();
```
Adam Miskiewicz:
@skevy (twitter.com/skevy, github.com/skevy)
*/
import Action from './';
import { timeSinceLastFrame } from '../framesync';

class Spring extends Action {
static defaultProps = {
stiffness: 100,
damping: 10,
mass: 1.0,
velocity: 0.0,
from: 0.0,
to: 0.0,
restSpeed: 0.01,
restDisplacement: 0.01
};

onStart() {
const { velocity, to, from } = this.props;
this.t = 0;
this.initialVelocity = velocity ? velocity / 1000 : 0.0;
this.isComplete = false;
this.delta = to - from;
}

update() {
const { stiffness, damping, mass, from, to, restSpeed, restDisplacement } = this.props;
const { delta, initialVelocity } = this;

const timeDelta = timeSinceLastFrame() / 1000;
const t = this.t = this.t + timeDelta;

const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
const angularFreq = Math.sqrt(stiffness / mass);
const expoDecay = angularFreq * Math.sqrt(1.0 - (dampingRatio * dampingRatio));

const x0 = 1;
let oscillation = 0.0;

// Underdamped
if (dampingRatio < 1) {
const envelope = Math.exp(-dampingRatio * angularFreq * t);
oscillation = envelope * (((initialVelocity + dampingRatio * angularFreq * x0) / expoDecay) * Math.sin(expoDecay * t) + (x0 * Math.cos(expoDecay * t)));
this.velocity = (envelope * ((Math.cos(expoDecay * t) * (initialVelocity + dampingRatio * angularFreq * x0)) - (expoDecay * x0 * Math.sin(expoDecay * t))) -
((dampingRatio * angularFreq * envelope) * ((((Math.sin(expoDecay * t) * (initialVelocity + dampingRatio * angularFreq * x0)) ) / expoDecay) + (x0 * Math.cos(expoDecay * t)))));

// Critically damped
} else {
const envelope = Math.exp(-angularFreq * t);
oscillation = envelope * (x0 + (initialVelocity + (angularFreq * x0)) * t);
this.velocity = envelope * ((t * initialVelocity * angularFreq) - (t * x0 * (angularFreq * angularFreq)) + initialVelocity);
}

const fraction = 1 - oscillation;
let position = from + fraction * delta;

// Check if simulation is complete
// We do this here instead of `isActionComplete` as it allows us
// to clamp to end during update)
const isBelowVelocityThreshold = Math.abs(this.velocity) <= restSpeed;
const isBelowDisplacementThreshold = Math.abs(to - position) <= restDisplacement;
this.isComplete = isBelowVelocityThreshold && isBelowDisplacementThreshold;

if (this.isComplete) {
position = to;
}

return position;
}

isActionComplete() {
return this.isComplete;
}
}

export default (props) => new Spring(props);
1 change: 1 addition & 0 deletions src/popmotion.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export pointer from './actions/pointer';
export trackOffset from './actions/track-offset';
export tween from './actions/tween';
export stagger from './actions/stagger';
export spring from './actions/spring';
export value from './actions/value';

// Renderers
Expand Down

0 comments on commit 77269a7

Please sign in to comment.