-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
057cb30
commit 564f6e5
Showing
5 changed files
with
383 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# Changelog | ||
|
||
All notable changes to this project will be documented in this file. | ||
|
||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), | ||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||
|
||
## Unreleased | ||
|
||
### Added | ||
|
||
- First release of the Mechanic transition helpers |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# @mechanic-design/transition | ||
|
||
Helper functions to make animation easier. | ||
|
||
## Usage | ||
|
||
```js | ||
|
||
import { transition } from "@mechanic-design/transition"; | ||
|
||
export const handler = ({ inputs, frame, done, drawLoop }) => { | ||
const xPos = transition({ | ||
from: 0, | ||
to: 100, | ||
// These can be frames or seconds, depending on what | ||
// you pass as the tick in the next step | ||
duration: 10, | ||
delay: 1, | ||
}); | ||
|
||
drawLoop({ frameCount, timestamp }) => { | ||
ctx.fillRect( | ||
// If you pass the timestamp here, duration and delay | ||
// will be treated as seconds. | ||
// | ||
// If you passed frameCount, duration and delay would | ||
// be considered as frame offsets. | ||
xPos(timestamp), | ||
0, | ||
100, | ||
100, | ||
); | ||
}); | ||
}; | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
{ | ||
"name": "@mechanic-design/transition", | ||
"version": "2.0.0-beta.10", | ||
"description": "A collection of utilities to help with transitions for animations", | ||
"license": "MIT", | ||
"homepage": "https://github.com/designsystemsinternational/mechanic#readme", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/designsystemsinternational/mechanic", | ||
"directory": "packages/transition" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/designsystemsinternational/mechanic/issues", | ||
"email": "i@designsystems.international" | ||
}, | ||
"author": { | ||
"name": "Rune Skjoldborg Madsen", | ||
"email": "i@designsystems.international", | ||
"url": "https://designsystems.international" | ||
}, | ||
"contributors": [ | ||
{ | ||
"name": "Fernando Florenzano Hernández", | ||
"email": "i@designsystems.international", | ||
"url": "https://designsystems.international" | ||
}, | ||
{ | ||
"name": "Martin Bravo", | ||
"email": "i@designsystems.international", | ||
"url": "https://designsystems.international" | ||
}, | ||
{ | ||
"name": "Lucas Dino Nolte", | ||
"email": "lucas@designsystems.international", | ||
"url": "https://designsystems.international" | ||
} | ||
], | ||
"type": "module", | ||
"exports": { | ||
".": "./src/index.js" | ||
}, | ||
"dependencies": { | ||
"@mechanic-design/core": "^2.0.0-beta.10" | ||
}, | ||
"engines": { | ||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
const easeOutBounce = t => { | ||
const scaledTime = t / 1; | ||
|
||
if (scaledTime < 1 / 2.75) { | ||
return 7.5625 * scaledTime * scaledTime; | ||
} else if (scaledTime < 2 / 2.75) { | ||
const scaledTime2 = scaledTime - 1.5 / 2.75; | ||
return 7.5625 * scaledTime2 * scaledTime2 + 0.75; | ||
} else if (scaledTime < 2.5 / 2.75) { | ||
const scaledTime2 = scaledTime - 2.25 / 2.75; | ||
return 7.5625 * scaledTime2 * scaledTime2 + 0.9375; | ||
} else { | ||
const scaledTime2 = scaledTime - 2.625 / 2.75; | ||
return 7.5625 * scaledTime2 * scaledTime2 + 0.984375; | ||
} | ||
}; | ||
|
||
const easeInBounce = t => { | ||
return 1 - easeOutBounce(1 - t); | ||
}; | ||
|
||
/** | ||
* A pre-configured collection of the most commonly used | ||
* easing curves. | ||
* | ||
* Ported from https://gist.github.com/gre/1650294 | ||
* and https://github.com/AndrewRayCode/easing-utils/blob/master/src/easing.js | ||
*/ | ||
export const EasingFunctions = { | ||
// no easing, no acceleration | ||
linear: t => t, | ||
|
||
// accelerating from zero velocity | ||
easeInQuad: t => t * t, | ||
|
||
// decelerating to zero velocity | ||
easeOutQuad: t => t * (2 - t), | ||
|
||
// acceleration until halfway, then deceleration | ||
easeInOutQuad: t => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t), | ||
|
||
// accelerating from zero velocity | ||
easeInCubic: t => t * t * t, | ||
|
||
// decelerating to zero velocity | ||
easeOutCubic: t => --t * t * t + 1, | ||
|
||
// acceleration until halfway, then deceleration | ||
easeInOutCubic: t => | ||
t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1, | ||
|
||
// accelerating from zero velocity | ||
easeInQuart: t => t * t * t * t, | ||
|
||
// decelerating to zero velocity | ||
easeOutQuart: t => 1 - --t * t * t * t, | ||
|
||
// acceleration until halfway, then deceleration | ||
easeInOutQuart: t => (t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t), | ||
|
||
// accelerating from zero velocity | ||
easeInQuint: t => t * t * t * t * t, | ||
|
||
// decelerating to zero velocity | ||
easeOutQuint: t => 1 + --t * t * t * t * t, | ||
|
||
// acceleration until halfway, then deceleration | ||
easeInOutQuint: t => | ||
t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * --t * t * t * t * t, | ||
|
||
// Accelerate exponentially until finish | ||
easeInExpo: t => { | ||
if (t === 0) { | ||
return 0; | ||
} | ||
|
||
return Math.pow(2, 10 * (t - 1)); | ||
}, | ||
|
||
// Initial exponential acceleration slowing to stop | ||
easeOutExpo: t => { | ||
if (t === 1) { | ||
return 1; | ||
} | ||
|
||
return -Math.pow(2, -10 * t) + 1; | ||
}, | ||
|
||
// Exponential acceleration and deceleration | ||
easeInOutExpo: t => { | ||
if (t === 0 || t === 1) { | ||
return t; | ||
} | ||
|
||
const scaledTime = t * 2; | ||
const scaledTime1 = scaledTime - 1; | ||
|
||
if (scaledTime < 1) { | ||
return 0.5 * Math.pow(2, 10 * scaledTime1); | ||
} | ||
|
||
return 0.5 * (-Math.pow(2, -10 * scaledTime1) + 2); | ||
}, | ||
|
||
// Increasing velocity until stop | ||
easeInCirc: t => { | ||
const scaledTime = t / 1; | ||
return -1 * (Math.sqrt(1 - scaledTime * t) - 1); | ||
}, | ||
|
||
// Start fast, decreasing velocity until stop | ||
easeOutCirc: t => { | ||
const t1 = t - 1; | ||
return Math.sqrt(1 - t1 * t1); | ||
}, | ||
|
||
// Fast increase in velocity, fast decrease in velocity | ||
easeInOutCirc: t => { | ||
const scaledTime = t * 2; | ||
const scaledTime1 = scaledTime - 2; | ||
|
||
if (scaledTime < 1) { | ||
return -0.5 * (Math.sqrt(1 - scaledTime * scaledTime) - 1); | ||
} | ||
|
||
return 0.5 * (Math.sqrt(1 - scaledTime1 * scaledTime1) + 1); | ||
}, | ||
|
||
// Slow movement backwards then fast snap to finish | ||
easeInBack: (t, magnitude = 1.70158) => { | ||
return t * t * ((magnitude + 1) * t - magnitude); | ||
}, | ||
|
||
// Fast snap to backwards point then slow resolve to finish | ||
easeOutBack: (t, magnitude = 1.70158) => { | ||
const scaledTime = t / 1 - 1; | ||
|
||
return ( | ||
scaledTime * scaledTime * ((magnitude + 1) * scaledTime + magnitude) + 1 | ||
); | ||
}, | ||
|
||
// Slow movement backwards, fast snap to past finish, slow resolve to finish | ||
easeInOutBack: (t, magnitude = 1.70158) => { | ||
const scaledTime = t * 2; | ||
const scaledTime2 = scaledTime - 2; | ||
|
||
const s = magnitude * 1.525; | ||
|
||
if (scaledTime < 1) { | ||
return 0.5 * scaledTime * scaledTime * ((s + 1) * scaledTime - s); | ||
} | ||
|
||
return 0.5 * (scaledTime2 * scaledTime2 * ((s + 1) * scaledTime2 + s) + 2); | ||
}, | ||
|
||
// Bounces slowly then quickly to finish | ||
easeInElastic: (t, magnitude = 0.7) => { | ||
if (t === 0 || t === 1) { | ||
return t; | ||
} | ||
|
||
const scaledTime = t / 1; | ||
const scaledTime1 = scaledTime - 1; | ||
|
||
const p = 1 - magnitude; | ||
const s = (p / (2 * Math.PI)) * Math.asin(1); | ||
|
||
return -( | ||
Math.pow(2, 10 * scaledTime1) * | ||
Math.sin(((scaledTime1 - s) * (2 * Math.PI)) / p) | ||
); | ||
}, | ||
|
||
// Fast acceleration, bounces to zero | ||
easeOutElastic: (t, magnitude = 0.7) => { | ||
if (t === 0 || t === 1) { | ||
return t; | ||
} | ||
|
||
const p = 1 - magnitude; | ||
const scaledTime = t * 2; | ||
|
||
const s = (p / (2 * Math.PI)) * Math.asin(1); | ||
return ( | ||
Math.pow(2, -10 * scaledTime) * | ||
Math.sin(((scaledTime - s) * (2 * Math.PI)) / p) + | ||
1 | ||
); | ||
}, | ||
|
||
// Slow start and end, two bounces sandwich a fast motion | ||
easeInOutElastic: (t, magnitude = 0.65) => { | ||
if (t === 0 || t === 1) { | ||
return t; | ||
} | ||
|
||
const p = 1 - magnitude; | ||
const scaledTime = t * 2; | ||
const scaledTime1 = scaledTime - 1; | ||
|
||
const s = (p / (2 * Math.PI)) * Math.asin(1); | ||
|
||
if (scaledTime < 1) { | ||
return ( | ||
-0.5 * | ||
(Math.pow(2, 10 * scaledTime1) * | ||
Math.sin(((scaledTime1 - s) * (2 * Math.PI)) / p)) | ||
); | ||
} | ||
|
||
return ( | ||
Math.pow(2, -10 * scaledTime1) * | ||
Math.sin(((scaledTime1 - s) * (2 * Math.PI)) / p) * | ||
0.5 + | ||
1 | ||
); | ||
}, | ||
|
||
// Bounce to completion | ||
easeOutBounce, | ||
|
||
// Bounce increasing in velocity until completion | ||
easeInBounce, | ||
|
||
// Bounce in and bounce out | ||
easeInOutBounce: t => { | ||
if (t < 0.5) { | ||
return easeInBounce(t * 2) * 0.5; | ||
} | ||
|
||
return easeOutBounce(t * 2 - 1) * 0.5 + 0.5; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { EasingFunctions } from "./easings.js"; | ||
|
||
const defaultOptions = { | ||
from: 0, | ||
to: 0, | ||
duration: 0, | ||
delay: 0, | ||
easing: "linear" | ||
}; | ||
|
||
/** | ||
* Turns the user specified easing into a function that can | ||
* be executed by the transition utility. | ||
* | ||
* Easing can either be a string referencing one of the | ||
* pre-built easing functions or a custom function. | ||
* | ||
* @params{string|function} easing | ||
*/ | ||
const resolveEasing = easing => { | ||
// If the user specified a function for the easing, | ||
// we’re using that | ||
if (typeof easing === "function") return easing; | ||
|
||
// If not, we check if we can return a pre-built | ||
// easing function | ||
if (EasingFunctions[easing]) return EasingFunctions[easing]; | ||
|
||
// Lastly we need to throw | ||
throw new Error( | ||
`Unexpected easing (${easing}) passed to transition function. Easing should either be a custom function or one of the pre-built values: ${Object.keys( | ||
EasingFunctions | ||
).join(", ")}.` | ||
); | ||
}; | ||
|
||
export const transition = (_options = {}) => { | ||
const options = Object.assign({}, defaultOptions, _options); | ||
const easing = resolveEasing(options.easing); | ||
const { from, to, duration, delay } = options; | ||
const change = to - from; | ||
|
||
return tick => { | ||
const currentTime = Math.max(0, tick - delay); | ||
|
||
if (currentTime >= duration) return to; | ||
|
||
const t = currentTime / duration; | ||
|
||
return change * easing(t) + from; | ||
}; | ||
}; | ||
|
||
export const namedEasings = Object.keys(EasingFunctions); |