├── .eslintrc ├── README.md ├── example.js ├── ipo_example.png ├── package.json └── src ├── checkPoints.js ├── computeBeziers.js └── index.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "indent": [ 4 | 2, 5 | 2 6 | ], 7 | "quotes": [ 8 | 2, 9 | "double" 10 | ], 11 | "linebreak-style": [ 12 | 2, 13 | "unix" 14 | ], 15 | "semi": [ 16 | 2, 17 | "always" 18 | ] 19 | }, 20 | "env": { 21 | "node": true, 22 | "browser": true 23 | }, 24 | "extends": "eslint:recommended" 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | IPO 2 | === 3 | 4 | *IPO*, short for *InterPOlation*, is an easing library allowing to describe complex easings in JSON (multiple point over time can be placed). 5 | 6 | > Inspired from Blender's IPO idea, it is made for animating a value in many ways over time. 7 | 8 | Screenshot running [example.js](./example.js): 9 | 10 | ![](ipo_example.png) 11 | 12 | ```json 13 | [ 14 | { "p": [100, 100], "upper": [10, 20] }, 15 | { "p": [200, 190], "lower": [-10, 0], "upper": [10, 0] }, 16 | { "p": [250, 100], "upper": [30, 0] }, 17 | { "p": [280, 140] }, 18 | { "p": [350, 160] }, 19 | { "p": [400, 50], "lower": [-50, 40], "upper": [100, 0] }, 20 | { "p": [600, 250], "lower": [-140, 0], "upper": [ 40, -40 ] } 21 | ] 22 | ``` 23 | 24 | *(the white dots are the interpolated values, the red point are the control points and their handles, the yellow curve is the SVG continuous curve.)* 25 | 26 | API 27 | === 28 | 29 | ```js 30 | var IPO = require("ipo"); 31 | var ipo = IPO([ ...points... ]); 32 | ipo(42); // Get the curve Y value at X=42 33 | ``` 34 | 35 | ### Format 36 | 37 | `points` is: 38 | 39 | - an Array of Point, where each Point is an object with 40 | - a position `p` which is a `[x, y]` 41 | - (option) `lower`: relative position of a bezier handle for the lower curve interpolation 42 | - (option) `upper`: same for the upper curve interpolation. 43 | 44 | 45 | Under the hood 46 | ============== 47 | 48 | - [`bezier-easing`](https://github.com/gre/bezier-easing) is used to perform bezier interpolation. It provides efficient computation with various optimization techniques. 49 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | var IPO = require("."); 2 | 3 | // for the little story, i've written this library in the airplane on my way to NY 4 | // and everyone is sleeping... this is why everything is dark !!! 5 | document.body.style.background = "black"; 6 | 7 | var points = [ 8 | { "p": [100, 100], "upper": [10, 20] }, 9 | { "p": [200, 190], "lower": [-10, 0], "upper": [10, 0] }, 10 | { "p": [250, 100], "upper": [30, 0] }, 11 | { "p": [280, 140] }, 12 | { "p": [350, 160] }, 13 | { "p": [400, 50], "lower": [-50, 40], "upper": [100, 0] }, 14 | { "p": [600, 250], "lower": [-140, 0], "upper": [ 40, -40 ] } 15 | ]; 16 | var ipo = IPO(points); 17 | 18 | // Quick draw in SVG 19 | 20 | function addPoints (a, b) { 21 | return [ a[0] + b[0], a[1] + b[1] ]; 22 | } 23 | function project (p) { 24 | return [ 1 * p[0], 300 - p[1] ]; 25 | } 26 | 27 | var svg = ""; 28 | 29 | // curve 30 | svg += ""; 38 | 39 | // points and handles 40 | for (i = 0; i < points.length; i++) { 41 | point = points[i]; 42 | p = project(point.p); 43 | var clr = "#f00"; 44 | svg += ""; 45 | [point.lower, point.upper].filter(function (o) { return o; }).map(function (handle) { 46 | handle = project(addPoints(handle, point.p)); 47 | var d = "M "+p+" L"+handle; 48 | svg += ""; 49 | svg += ""; 50 | }); 51 | } 52 | 53 | // interpolation sampling 54 | for (var x=0; x<800; x += 4) { 55 | var y = ipo(x); 56 | p = project([ x, y ]); 57 | svg += ""; 58 | } 59 | 60 | svg += ""; 61 | 62 | document.body.innerHTML = svg; 63 | -------------------------------------------------------------------------------- /ipo_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gre/ipo/79d79a796b8e3345976196d147d63fe95314805d/ipo_example.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ipo", 3 | "version": "2.0.0", 4 | "description": "easing library allowing to describe complex easings in JSON", 5 | "tags": [ 6 | "easing", 7 | "bezier", 8 | "curve", 9 | "ipo", 10 | "interpolation", 11 | "animation" 12 | ], 13 | "main": "src/index.js", 14 | "author": "Gaëtan Renaudeau", 15 | "license": "ISC", 16 | "dependencies": { 17 | "bezier-easing": "2.0.x", 18 | "invariant": "2.2.1" 19 | }, 20 | "devDependencies": { 21 | "eslint": "latest" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/checkPoints.js: -------------------------------------------------------------------------------- 1 | var invariant = require("invariant"); 2 | 3 | module.exports = function (points) { 4 | invariant(points instanceof Array && points.length>0, "points is a non-empty array"); 5 | var curX = -Infinity; 6 | for (var i=0; i curX, "points must be sorted by `p[0]` (x position)"); 10 | curX = x; 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/computeBeziers.js: -------------------------------------------------------------------------------- 1 | var invariant = require("invariant"); 2 | var BezierEasing = require("bezier-easing"); 3 | 4 | module.exports = function (points) { 5 | var prev = points[0], point; 6 | var beziers = []; 7 | for (var i=1; i pX) { 18 | // There is no 2 points to interpolate between, it is just point's y unless an lower/upper is defined, 19 | // in that case, we do a linear interpolation on edges. 20 | var edge = x > pX && point.upper || x < pX && point.lower; 21 | return point.p[1] + (edge && edge[1] !== 0 ? (x-pX) * edge[1] / edge[0] : 0); 22 | } 23 | var bezier = beziers[i - 1]; 24 | if (!bezier) return prev.p[1]; // this happens when two following points have the same Y. 25 | var a = prev.p; 26 | var b = point.p; 27 | var w = b[0] - a[0]; 28 | var h = b[1] - a[1]; 29 | // Get the bezier's value and map it to the points' domain 30 | return a[1] + h * bezier((x - a[0]) / w); 31 | }; 32 | }; 33 | --------------------------------------------------------------------------------