├── .babelrc ├── .eslintrc ├── .gitignore ├── README.md ├── example ├── app.js ├── index.html ├── index.js └── style.css ├── lib ├── Animated.js ├── Easing.js ├── Interpolation.js ├── bezier.js └── index.js ├── package.json ├── server.js ├── src ├── Animated.js ├── Easing.js ├── Interpolation.js ├── bezier.js └── index.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0 3 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "rules" :{ 4 | "quotes": [1, "single"], 5 | "no-unused-vars": [1, {"vars": "all", "args": "all"}], 6 | "strict": [2, "global"] 7 | }, 8 | "env":{ 9 | "browser": true, 10 | "node" : true 11 | } 12 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # osx noise 2 | .DS_Store 3 | profile 4 | 5 | # xcode noise 6 | build/* 7 | *.mode1 8 | *.mode1v3 9 | *.mode2v3 10 | *.perspective 11 | *.perspectivev3 12 | *.pbxuser 13 | *.xcworkspace 14 | xcuserdata 15 | 16 | # svn & cvs 17 | .svn 18 | CVS 19 | node_modules 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | *unofficial* bootleg of Animated for web, the new animation library for reactjs from the fine folks at facebook. this code was ripped from the link http://fooo.fr/~vjeux/fb/animated-docs/ 2 | 3 | `npm install threepointone/react-animated-web-bootleg --save` 4 | 5 | to run example, `npm install && npm start`, then open `http://localhost:3000/example/index.html` -------------------------------------------------------------------------------- /example/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, {Component} from 'react'; 4 | import Animated from '../src'; 5 | 6 | export class App extends Component{ 7 | state = { 8 | anim : new Animated.Value(0) 9 | } 10 | onClick = () => { 11 | Animated.timing(this.state.anim, {toValue: 400}).start(); 12 | } 13 | render(){ 14 | return 18 | click me 19 | 20 | } 21 | } -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {App} from './app'; 3 | 4 | React.render(, document.getElementById('root')); -------------------------------------------------------------------------------- /example/style.css: -------------------------------------------------------------------------------- 1 | /*html, 2 | h1, 3 | h2 { 4 | font-family: 'Roboto', sans-serif; 5 | font-weight: 300 6 | } 7 | *//*.container { 8 | width: 800px; 9 | margin: 0 auto 10 | } 11 | */.circle { 12 | margin: 2px; 13 | width: 50px; 14 | height: 50px; 15 | position: absolute; 16 | display: inline-block; 17 | box-shadow: 0 1px 2px #999; 18 | text-shadow: 0 1px 2px #999; 19 | background-image: url(https://fbcdn-profile-a.akamaihd.net/hprofile-ak-xtf1/v/t1.0-1/p320x320/10958296_10152690932671188_4256540723631642566_n.jpg?oh=a69694fcf15345249df14dea4c3e3066&oe=5659ED51&__gda__=1444749767_96630067bf758b113706aa1c91fde392); 20 | background-size: cover; 21 | line-height: 80px; 22 | vertical-align: bottom; 23 | text-align: center; 24 | color: #fff; 25 | font-size: 10px 26 | } 27 | .circle:nth-child(2) { 28 | background-image: url(https://scontent-cdg2-1.xx.fbcdn.net/hphotos-xtf1/v/t1.0-9/1923173_716264837373_4589103_n.jpg?oh=e0ec3bd50f6567875a44ea6173e466cd&oe=5619375B) 29 | } 30 | .circle:nth-child(3) { 31 | background-image: url(https://scontent-cdg2-1.xx.fbcdn.net/hphotos-xtf1/v/t1.0-9/10170789_2386880322730_6755618519839435556_n.jpg?oh=107da1d6c5a1c7783a0e09ea7b04102f&oe=5657F184) 32 | } 33 | /*div.code { 34 | box-shadow: 0 1px 2px #999; 35 | width: 600px; 36 | padding: 5px; 37 | position: relative; 38 | margin: 0 auto; 39 | margin-bottom: 40px 40 | } 41 | div.code .reset { 42 | float: right 43 | } 44 | div.code pre { 45 | padding: 2px 46 | } 47 | hr { 48 | border: none; 49 | border-bottom: 1px solid #d9d9d9; 50 | margin: 0 51 | } 52 | button { 53 | vertical-align: top 54 | } 55 | .example>span { 56 | color: #333; 57 | font-size: 13px 58 | } 59 | .example { 60 | position: relative; 61 | height: 60px 62 | } 63 | .code pre { 64 | margin: 0; 65 | font-size: 11px; 66 | line-height: 1 67 | } 68 | .highlight { 69 | background: #e4fefd 70 | }*/ -------------------------------------------------------------------------------- /lib/Animated.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule Animated 10 | */ 11 | 'use strict'; 12 | 13 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 14 | 15 | var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 16 | 17 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 18 | 19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 20 | 21 | function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 22 | 23 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 24 | 25 | var _Easing = require('./Easing'); 26 | 27 | var _Easing2 = _interopRequireDefault(_Easing); 28 | 29 | var _interpolation = require('./interpolation'); 30 | 31 | var _interpolation2 = _interopRequireDefault(_interpolation); 32 | 33 | var _react = require('react'); 34 | 35 | var _react2 = _interopRequireDefault(_react); 36 | 37 | // var Animated = (function() { 38 | 39 | // Note(vjeux): this would be better as an interface but flow doesn't 40 | // support them yet 41 | 42 | var Animated = (function () { 43 | function Animated() { 44 | _classCallCheck(this, Animated); 45 | } 46 | 47 | // Important note: start() and stop() will only be called at most once. 48 | // Once an animation has been stopped or finished its course, it will 49 | // not be reused. 50 | 51 | _createClass(Animated, [{ 52 | key: 'attach', 53 | value: function attach() {} 54 | }, { 55 | key: 'detach', 56 | value: function detach() {} 57 | }, { 58 | key: 'getValue', 59 | value: function getValue() {} 60 | }, { 61 | key: 'getAnimatedValue', 62 | value: function getAnimatedValue() { 63 | return this.getValue(); 64 | } 65 | }, { 66 | key: 'addChild', 67 | value: function addChild(child) {} 68 | }, { 69 | key: 'removeChild', 70 | value: function removeChild(child) {} 71 | }, { 72 | key: 'getChildren', 73 | value: function getChildren() { 74 | return []; 75 | } 76 | }]); 77 | 78 | return Animated; 79 | })(); 80 | 81 | var Animation = (function () { 82 | function Animation() { 83 | _classCallCheck(this, Animation); 84 | } 85 | 86 | _createClass(Animation, [{ 87 | key: 'start', 88 | value: function start(fromValue, onUpdate, onEnd, previousAnimation) {} 89 | }, { 90 | key: 'stop', 91 | value: function stop() {} 92 | }]); 93 | 94 | return Animation; 95 | })(); 96 | 97 | var AnimatedWithChildren = (function (_Animated) { 98 | _inherits(AnimatedWithChildren, _Animated); 99 | 100 | function AnimatedWithChildren() { 101 | _classCallCheck(this, AnimatedWithChildren); 102 | 103 | _get(Object.getPrototypeOf(AnimatedWithChildren.prototype), 'constructor', this).call(this); 104 | this._children = []; 105 | } 106 | 107 | /** 108 | * Animated works by building a directed acyclic graph of dependencies 109 | * transparently when you render your Animated components. 110 | * 111 | * new Animated.Value(0) 112 | * .interpolate() .interpolate() new Animated.Value(1) 113 | * opacity translateY scale 114 | * style transform 115 | * View#234 style 116 | * View#123 117 | * 118 | * A) Top Down phase 119 | * When an Animated.Value is updated, we recursively go down through this 120 | * graph in order to find leaf nodes: the views that we flag as needing 121 | * an update. 122 | * 123 | * B) Bottom Up phase 124 | * When a view is flagged as needing an update, we recursively go back up 125 | * in order to build the new value that it needs. The reason why we need 126 | * this two-phases process is to deal with composite props such as 127 | * transform which can receive values from multiple parents. 128 | */ 129 | 130 | _createClass(AnimatedWithChildren, [{ 131 | key: 'addChild', 132 | value: function addChild(child) { 133 | if (this._children.length === 0) { 134 | this.attach(); 135 | } 136 | this._children.push(child); 137 | } 138 | }, { 139 | key: 'removeChild', 140 | value: function removeChild(child) { 141 | var index = this._children.indexOf(child); 142 | if (index === -1) { 143 | console.warn('Trying to remove a child that doesn\'t exist'); 144 | return; 145 | } 146 | this._children.splice(index, 1); 147 | if (this._children.length === 0) { 148 | this.detach(); 149 | } 150 | } 151 | }, { 152 | key: 'getChildren', 153 | value: function getChildren() { 154 | return this._children; 155 | } 156 | }]); 157 | 158 | return AnimatedWithChildren; 159 | })(Animated); 160 | 161 | function _flush(node) { 162 | var animatedStyles = new Set(); 163 | function findAnimatedStyles(theNode) { 164 | if ('update' in theNode) { 165 | animatedStyles.add(theNode); 166 | } else { 167 | theNode.getChildren().forEach(findAnimatedStyles); 168 | } 169 | } 170 | findAnimatedStyles(node); 171 | animatedStyles.forEach(function (animatedStyle) { 172 | return animatedStyle.update(); 173 | }); 174 | } 175 | 176 | var TimingAnimation = (function (_Animation) { 177 | _inherits(TimingAnimation, _Animation); 178 | 179 | function TimingAnimation(config) { 180 | _classCallCheck(this, TimingAnimation); 181 | 182 | _get(Object.getPrototypeOf(TimingAnimation.prototype), 'constructor', this).call(this); 183 | this._toValue = config.toValue; 184 | this._easing = config.easing || _Easing2['default'].inOut(_Easing2['default'].ease); 185 | this._duration = config.duration !== undefined ? config.duration : 500; 186 | this._delay = config.delay || 0; 187 | } 188 | 189 | _createClass(TimingAnimation, [{ 190 | key: 'start', 191 | value: function start(fromValue, onUpdate, onEnd) { 192 | var _this = this; 193 | 194 | this._fromValue = fromValue; 195 | this._onUpdate = onUpdate; 196 | this._onEnd = onEnd; 197 | 198 | var start = function start() { 199 | _this._startTime = Date.now(); 200 | _this._animationFrame = window.requestAnimationFrame(_this.onUpdate.bind(_this)); 201 | }; 202 | if (this._delay) { 203 | this._timeout = setTimeout(start, this._delay); 204 | } else { 205 | start(); 206 | } 207 | } 208 | }, { 209 | key: 'onUpdate', 210 | value: function onUpdate() { 211 | var now = Date.now(); 212 | 213 | if (now > this._startTime + this._duration) { 214 | this._onUpdate(this._fromValue + this._easing(1) * (this._toValue - this._fromValue)); 215 | var onEnd = this._onEnd; 216 | this._onEnd = null; 217 | onEnd && onEnd( /* finished */true); 218 | return; 219 | } 220 | 221 | this._onUpdate(this._fromValue + this._easing((now - this._startTime) / this._duration) * (this._toValue - this._fromValue)); 222 | 223 | this._animationFrame = window.requestAnimationFrame(this.onUpdate.bind(this)); 224 | } 225 | }, { 226 | key: 'stop', 227 | value: function stop() { 228 | clearTimeout(this._timeout); 229 | window.cancelAnimationFrame(this._animationFrame); 230 | var onEnd = this._onEnd; 231 | this._onEnd = null; 232 | onEnd && onEnd( /* finished */false); 233 | } 234 | }]); 235 | 236 | return TimingAnimation; 237 | })(Animation); 238 | 239 | var DecayAnimation = (function (_Animation2) { 240 | _inherits(DecayAnimation, _Animation2); 241 | 242 | function DecayAnimation(config) { 243 | _classCallCheck(this, DecayAnimation); 244 | 245 | _get(Object.getPrototypeOf(DecayAnimation.prototype), 'constructor', this).call(this); 246 | this._deceleration = config.deceleration || 0.998; 247 | this._velocity = config.velocity; 248 | } 249 | 250 | _createClass(DecayAnimation, [{ 251 | key: 'start', 252 | value: function start(fromValue, onUpdate, onEnd) { 253 | this._lastValue = fromValue; 254 | this._fromValue = fromValue; 255 | this._onUpdate = onUpdate; 256 | this._onEnd = onEnd; 257 | this._startTime = Date.now(); 258 | this._animationFrame = window.requestAnimationFrame(this.onUpdate.bind(this)); 259 | } 260 | }, { 261 | key: 'onUpdate', 262 | value: function onUpdate() { 263 | var now = Date.now(); 264 | 265 | var value = this._fromValue + this._velocity / (1 - this._deceleration) * (1 - Math.exp(-(1 - this._deceleration) * (now - this._startTime))); 266 | 267 | this._onUpdate(value); 268 | 269 | if (Math.abs(this._lastValue - value) < 0.1) { 270 | var onEnd = this._onEnd; 271 | this._onEnd = null; 272 | onEnd && onEnd( /* finished */true); 273 | return; 274 | } 275 | 276 | this._lastValue = value; 277 | this._animationFrame = window.requestAnimationFrame(this.onUpdate.bind(this)); 278 | } 279 | }, { 280 | key: 'stop', 281 | value: function stop() { 282 | window.cancelAnimationFrame(this._animationFrame); 283 | var onEnd = this._onEnd; 284 | this._onEnd = null; 285 | onEnd && onEnd( /* finished */false); 286 | } 287 | }]); 288 | 289 | return DecayAnimation; 290 | })(Animation); 291 | 292 | function withDefault(value, defaultValue) { 293 | if (value === undefined || value === null) { 294 | return defaultValue; 295 | } 296 | return value; 297 | } 298 | 299 | function tensionFromOrigamiValue(oValue) { 300 | return (oValue - 30.0) * 3.62 + 194.0; 301 | } 302 | function frictionFromOrigamiValue(oValue) { 303 | return (oValue - 8.0) * 3.0 + 25.0; 304 | } 305 | 306 | var fromOrigamiTensionAndFriction = function fromOrigamiTensionAndFriction(tension, friction) { 307 | return { 308 | tension: tensionFromOrigamiValue(tension), 309 | friction: frictionFromOrigamiValue(friction) 310 | }; 311 | }; 312 | 313 | var fromBouncinessAndSpeed = function fromBouncinessAndSpeed(bounciness, speed) { 314 | function normalize(value, startValue, endValue) { 315 | return (value - startValue) / (endValue - startValue); 316 | } 317 | function projectNormal(n, start, end) { 318 | return start + n * (end - start); 319 | } 320 | function linearInterpolation(t, start, end) { 321 | return t * end + (1.0 - t) * start; 322 | } 323 | function quadraticOutInterpolation(t, start, end) { 324 | return linearInterpolation(2 * t - t * t, start, end); 325 | } 326 | function b3Friction1(x) { 327 | return 0.0007 * Math.pow(x, 3) - 0.031 * Math.pow(x, 2) + 0.64 * x + 1.28; 328 | } 329 | function b3Friction2(x) { 330 | return 0.000044 * Math.pow(x, 3) - 0.006 * Math.pow(x, 2) + 0.36 * x + 2.; 331 | } 332 | function b3Friction3(x) { 333 | return 0.00000045 * Math.pow(x, 3) - 0.000332 * Math.pow(x, 2) + 0.1078 * x + 5.84; 334 | } 335 | function b3Nobounce(tension) { 336 | if (tension <= 18) { 337 | return b3Friction1(tension); 338 | } else if (tension > 18 && tension <= 44) { 339 | return b3Friction2(tension); 340 | } else { 341 | return b3Friction3(tension); 342 | } 343 | } 344 | 345 | var b = normalize(bounciness / 1.7, 0, 20.0); 346 | b = projectNormal(b, 0.0, 0.8); 347 | var s = normalize(speed / 1.7, 0, 20.0); 348 | var bouncyTension = projectNormal(s, 0.5, 200); 349 | var bouncyFriction = quadraticOutInterpolation(b, b3Nobounce(bouncyTension), 0.01); 350 | 351 | return { 352 | tension: tensionFromOrigamiValue(bouncyTension), 353 | friction: frictionFromOrigamiValue(bouncyFriction) 354 | }; 355 | }; 356 | 357 | var SpringAnimation = (function (_Animation3) { 358 | _inherits(SpringAnimation, _Animation3); 359 | 360 | function SpringAnimation(config) { 361 | _classCallCheck(this, SpringAnimation); 362 | 363 | _get(Object.getPrototypeOf(SpringAnimation.prototype), 'constructor', this).call(this); 364 | 365 | this._overshootClamping = withDefault(config.overshootClamping, false); 366 | this._restDisplacementThreshold = withDefault(config.restDisplacementThreshold, 0.001); 367 | this._restSpeedThreshold = withDefault(config.restSpeedThreshold, 0.001); 368 | this._lastVelocity = withDefault(config.velocity, 0); 369 | this._tempVelocity = this._lastVelocity; 370 | this._toValue = config.toValue; 371 | 372 | var springConfig; 373 | if (config.bounciness !== undefined || config.speed !== undefined) { 374 | invariant(config.tension === undefined && config.friction === undefined, 'You can only define bounciness/speed or tension/friction but not both'); 375 | springConfig = fromBouncinessAndSpeed(withDefault(config.bounciness, 8), withDefault(config.speed, 12)); 376 | } else { 377 | springConfig = fromOrigamiTensionAndFriction(withDefault(config.tension, 40), withDefault(config.friction, 7)); 378 | } 379 | this._tension = springConfig.tension; 380 | this._friction = springConfig.friction; 381 | } 382 | 383 | _createClass(SpringAnimation, [{ 384 | key: 'start', 385 | value: function start(fromValue, onUpdate, onEnd, previousAnimation) { 386 | this._active = true; 387 | this._startPosition = fromValue; 388 | this._lastPosition = this._startPosition; 389 | this._tempPosition = this._lastPosition; 390 | 391 | this._onUpdate = onUpdate; 392 | this._onEnd = onEnd; 393 | this._lastTime = Date.now(); 394 | 395 | if (previousAnimation instanceof SpringAnimation) { 396 | var internalState = previousAnimation.getInternalState(); 397 | this._lastPosition = internalState.lastPosition; 398 | this._tempPosition = internalState.tempPosition; 399 | this._lastVelocity = internalState.lastVelocity; 400 | this._tempVelocity = internalState.tempVelocity; 401 | this._lastTime = internalState.lastTime; 402 | } 403 | 404 | this.onUpdate(); 405 | } 406 | }, { 407 | key: 'getInternalState', 408 | value: function getInternalState() { 409 | return { 410 | lastPosition: this._lastPosition, 411 | tempPosition: this._tempPosition, 412 | lastVelocity: this._lastVelocity, 413 | tempVelocity: this._tempVelocity, 414 | lastTime: this._lastTime 415 | }; 416 | } 417 | }, { 418 | key: 'onUpdate', 419 | value: function onUpdate() { 420 | if (!this._active) { 421 | return; 422 | } 423 | var now = Date.now(); 424 | 425 | var position = this._lastPosition; 426 | var velocity = this._lastVelocity; 427 | 428 | var tempPosition = position; 429 | var tempVelocity = velocity; 430 | 431 | var TIMESTEP_MSEC = 4; 432 | var numSteps = Math.floor((now - this._lastTime) / TIMESTEP_MSEC); 433 | for (var i = 0; i < numSteps; ++i) { 434 | // Velocity is based on seconds instead of milliseconds 435 | var step = TIMESTEP_MSEC / 1000; 436 | 437 | var aVelocity = velocity; 438 | var aAcceleration = this._tension * (this._toValue - tempPosition) - this._friction * tempVelocity; 439 | tempPosition = position + aVelocity * step / 2; 440 | tempVelocity = velocity + aAcceleration * step / 2; 441 | 442 | var bVelocity = tempVelocity; 443 | var bAcceleration = this._tension * (this._toValue - tempPosition) - this._friction * tempVelocity; 444 | tempPosition = position + bVelocity * step / 2; 445 | tempVelocity = velocity + bAcceleration * step / 2; 446 | 447 | var cVelocity = tempVelocity; 448 | var cAcceleration = this._tension * (this._toValue - tempPosition) - this._friction * tempVelocity; 449 | tempPosition = position + cVelocity * step; 450 | tempVelocity = velocity + cAcceleration * step; 451 | 452 | var dVelocity = tempVelocity; 453 | var dAcceleration = this._tension * (this._toValue - tempPosition) - this._friction * tempVelocity; 454 | 455 | var dxdt = (aVelocity + 2 * (bVelocity + cVelocity) + dVelocity) / 6; 456 | var dvdt = (aAcceleration + 2 * (bAcceleration + cAcceleration) + dAcceleration) / 6; 457 | 458 | position += dxdt * step; 459 | velocity += dvdt * step; 460 | } 461 | 462 | this._lastTime = now; 463 | this._tempPosition = tempPosition; 464 | this._tempVelocity = tempVelocity; 465 | this._lastPosition = position; 466 | this._lastVelocity = velocity; 467 | 468 | this._onUpdate(position); 469 | 470 | // Conditions for stopping the spring animation 471 | var isOvershooting = false; 472 | if (this._overshootClamping && this._tension !== 0) { 473 | if (this._startPosition < this._toValue) { 474 | isOvershooting = position > this._toValue; 475 | } else { 476 | isOvershooting = position < this._toValue; 477 | } 478 | } 479 | var isVelocity = Math.abs(velocity) <= this._restSpeedThreshold; 480 | var isDisplacement = true; 481 | if (this._tension !== 0) { 482 | isDisplacement = Math.abs(this._toValue - position) <= this._restDisplacementThreshold; 483 | } 484 | if (isOvershooting || isVelocity && isDisplacement) { 485 | var onEnd = this._onEnd; 486 | this._onEnd = null; 487 | onEnd && onEnd( /* finished */true); 488 | return; 489 | } 490 | this._animationFrame = window.requestAnimationFrame(this.onUpdate.bind(this)); 491 | } 492 | }, { 493 | key: 'stop', 494 | value: function stop() { 495 | this._active = false; 496 | window.cancelAnimationFrame(this._animationFrame); 497 | var onEnd = this._onEnd; 498 | this._onEnd = null; 499 | onEnd && onEnd( /* finished */false); 500 | } 501 | }]); 502 | 503 | return SpringAnimation; 504 | })(Animation); 505 | 506 | var _uniqueId = 1; 507 | 508 | var AnimatedValue = (function (_AnimatedWithChildren) { 509 | _inherits(AnimatedValue, _AnimatedWithChildren); 510 | 511 | function AnimatedValue(value) { 512 | _classCallCheck(this, AnimatedValue); 513 | 514 | _get(Object.getPrototypeOf(AnimatedValue.prototype), 'constructor', this).call(this); 515 | this._value = value; 516 | this._offset = 0; 517 | this._animation = null; 518 | this._listeners = {}; 519 | } 520 | 521 | _createClass(AnimatedValue, [{ 522 | key: 'detach', 523 | value: function detach() { 524 | this.stopAnimation(); 525 | } 526 | }, { 527 | key: 'getValue', 528 | value: function getValue() { 529 | return this._value + this._offset; 530 | } 531 | }, { 532 | key: 'setValue', 533 | value: function setValue(value) { 534 | if (this._animation) { 535 | this._animation.stop(); 536 | this._animation = null; 537 | } 538 | this._updateValue(value); 539 | } 540 | }, { 541 | key: 'getOffset', 542 | value: function getOffset() { 543 | return this._offset; 544 | } 545 | }, { 546 | key: 'setOffset', 547 | value: function setOffset(offset) { 548 | this._offset = offset; 549 | } 550 | }, { 551 | key: 'addListener', 552 | value: function addListener(callback) { 553 | var id = _uniqueId++; 554 | this._listeners[id] = callback; 555 | return id; 556 | } 557 | }, { 558 | key: 'removeListener', 559 | value: function removeListener(id) { 560 | delete this._listeners[id]; 561 | } 562 | }, { 563 | key: 'animate', 564 | value: function animate(animation, callback) { 565 | var _this2 = this; 566 | 567 | var previousAnimation = this._animation; 568 | this._animation && this._animation.stop(); 569 | this._animation = animation; 570 | animation.start(this._value, function (value) { 571 | _this2._updateValue(value); 572 | }, function (finished) { 573 | _this2._animation = null; 574 | callback && callback(finished); 575 | }, previousAnimation); 576 | } 577 | }, { 578 | key: 'stopAnimation', 579 | value: function stopAnimation(callback) { 580 | this.stopTracking(); 581 | this._animation && this._animation.stop(); 582 | callback && callback(this._value); 583 | } 584 | }, { 585 | key: 'stopTracking', 586 | value: function stopTracking() { 587 | this._tracking && this._tracking.detach(); 588 | } 589 | }, { 590 | key: 'track', 591 | value: function track(tracking) { 592 | this.stopTracking(); 593 | this._tracking = tracking; 594 | } 595 | }, { 596 | key: 'interpolate', 597 | value: function interpolate(config) { 598 | return new AnimatedInterpolation(this, _interpolation2['default'].create(config)); 599 | } 600 | }, { 601 | key: '_updateValue', 602 | value: function _updateValue(value) { 603 | if (value === this._value) { 604 | return; 605 | } 606 | this._value = value; 607 | _flush(this); 608 | for (var key in this._listeners) { 609 | this._listeners[key]({ value: this.getValue() }); 610 | } 611 | } 612 | }]); 613 | 614 | return AnimatedValue; 615 | })(AnimatedWithChildren); 616 | 617 | var AnimatedVec2 = (function (_AnimatedWithChildren2) { 618 | _inherits(AnimatedVec2, _AnimatedWithChildren2); 619 | 620 | function AnimatedVec2(value) { 621 | _classCallCheck(this, AnimatedVec2); 622 | 623 | _get(Object.getPrototypeOf(AnimatedVec2.prototype), 'constructor', this).call(this); 624 | value = value || { x: 0, y: 0 }; 625 | if (typeof value.x === 'number') { 626 | this.x = new AnimatedValue(value.x); 627 | this.y = new AnimatedValue(value.y); 628 | } else { 629 | this.x = value.x; 630 | this.y = value.y; 631 | } 632 | this._listeners = {}; 633 | } 634 | 635 | _createClass(AnimatedVec2, [{ 636 | key: 'setValue', 637 | value: function setValue(value) { 638 | this.x.setValue(value.x); 639 | this.y.setValue(value.y); 640 | } 641 | }, { 642 | key: 'setOffset', 643 | value: function setOffset(offset) { 644 | this.x.setOffset(offset.x); 645 | this.y.setOffset(offset.y); 646 | } 647 | }, { 648 | key: 'addListener', 649 | value: function addListener(callback) { 650 | var _this3 = this; 651 | 652 | var id = _uniqueId++; 653 | var jointCallback = function jointCallback(value) { 654 | callback({ x: _this3.x.getValue(), y: _this3.y.getValue() }); 655 | }; 656 | this._listeners[id] = { 657 | x: this.x.addListener(jointCallback), 658 | y: this.y.addListener(jointCallback) 659 | }; 660 | return id; 661 | } 662 | }, { 663 | key: 'removeListener', 664 | value: function removeListener(id) { 665 | this.x.removeListener(this._listeners[id].x); 666 | this.y.removeListener(this._listeners[id].y); 667 | delete this._listeners[id]; 668 | } 669 | }, { 670 | key: 'offset', 671 | value: function offset(theOffset) { 672 | // chunky...perf? 673 | return new AnimatedVec2({ 674 | x: this.x.interpolate({ 675 | inputRange: [0, 1], 676 | outputRange: [theOffset.x, theOffset.x + 1] 677 | }), 678 | y: this.y.interpolate({ 679 | inputRange: [0, 1], 680 | outputRange: [theOffset.y, theOffset.y + 1] 681 | }) 682 | }); 683 | } 684 | }, { 685 | key: 'getLayout', 686 | value: function getLayout() { 687 | return { 688 | left: this.x, 689 | top: this.y 690 | }; 691 | } 692 | }, { 693 | key: 'getTranslateTransform', 694 | value: function getTranslateTransform() { 695 | return [{ translateX: this.x }, { translateY: this.y }]; 696 | } 697 | }]); 698 | 699 | return AnimatedVec2; 700 | })(AnimatedWithChildren); 701 | 702 | var AnimatedInterpolation = (function (_AnimatedWithChildren3) { 703 | _inherits(AnimatedInterpolation, _AnimatedWithChildren3); 704 | 705 | function AnimatedInterpolation(parent, interpolation) { 706 | _classCallCheck(this, AnimatedInterpolation); 707 | 708 | _get(Object.getPrototypeOf(AnimatedInterpolation.prototype), 'constructor', this).call(this); 709 | this._parent = parent; 710 | this._interpolation = interpolation; 711 | } 712 | 713 | _createClass(AnimatedInterpolation, [{ 714 | key: 'getValue', 715 | value: function getValue() { 716 | var parentValue = this._parent.getValue(); 717 | invariant(typeof parentValue === 'number', 'Cannot interpolate an input which is not a number.'); 718 | return this._interpolation(parentValue); 719 | } 720 | }, { 721 | key: 'interpolate', 722 | value: function interpolate(config) { 723 | return new AnimatedInterpolation(this, _interpolation2['default'].create(config)); 724 | } 725 | }, { 726 | key: 'attach', 727 | value: function attach() { 728 | this._parent.addChild(this); 729 | } 730 | }, { 731 | key: 'detach', 732 | value: function detach() { 733 | this._parent.removeChild(this); 734 | } 735 | }]); 736 | 737 | return AnimatedInterpolation; 738 | })(AnimatedWithChildren); 739 | 740 | var AnimatedTransform = (function (_AnimatedWithChildren4) { 741 | _inherits(AnimatedTransform, _AnimatedWithChildren4); 742 | 743 | function AnimatedTransform(transforms) { 744 | _classCallCheck(this, AnimatedTransform); 745 | 746 | _get(Object.getPrototypeOf(AnimatedTransform.prototype), 'constructor', this).call(this); 747 | this._transforms = transforms; 748 | } 749 | 750 | _createClass(AnimatedTransform, [{ 751 | key: 'getValue', 752 | value: function getValue() { 753 | return this._transforms.map(function (transform) { 754 | var result = ''; 755 | for (var key in transform) { 756 | var value = transform[key]; 757 | if (value instanceof Animated) { 758 | result += key + '(' + value.getValue() + ')'; 759 | } else { 760 | result += key + '(' + value.join(',') + ')'; 761 | } 762 | } 763 | return result; 764 | }).join(' '); 765 | } 766 | }, { 767 | key: 'getAnimatedValue', 768 | value: function getAnimatedValue() { 769 | return this._transforms.map(function (transform) { 770 | var result = ''; 771 | for (var key in transform) { 772 | var value = transform[key]; 773 | if (value instanceof Animated) { 774 | result += key + '(' + value.getValue() + ') '; 775 | } else { 776 | // All transform components needed to recompose matrix 777 | result += key + '(' + value.join(',') + ') '; 778 | } 779 | } 780 | return result; 781 | }).join('').trim(); 782 | } 783 | }, { 784 | key: 'attach', 785 | value: function attach() { 786 | var _this4 = this; 787 | 788 | this._transforms.forEach(function (transform) { 789 | for (var key in transform) { 790 | var value = transform[key]; 791 | if (value instanceof Animated) { 792 | value.addChild(_this4); 793 | } 794 | } 795 | }); 796 | } 797 | }, { 798 | key: 'detach', 799 | value: function detach() { 800 | var _this5 = this; 801 | 802 | this._transforms.forEach(function (transform) { 803 | for (var key in transform) { 804 | var value = transform[key]; 805 | if (value instanceof Animated) { 806 | value.removeChild(_this5); 807 | } 808 | } 809 | }); 810 | } 811 | }]); 812 | 813 | return AnimatedTransform; 814 | })(AnimatedWithChildren); 815 | 816 | var AnimatedStyle = (function (_AnimatedWithChildren5) { 817 | _inherits(AnimatedStyle, _AnimatedWithChildren5); 818 | 819 | function AnimatedStyle(style) { 820 | _classCallCheck(this, AnimatedStyle); 821 | 822 | _get(Object.getPrototypeOf(AnimatedStyle.prototype), 'constructor', this).call(this); 823 | style = style || {}; 824 | if (style.transform) { 825 | style = _extends({}, style, { 826 | transform: new AnimatedTransform(style.transform) 827 | }); 828 | } 829 | this._style = style; 830 | } 831 | 832 | _createClass(AnimatedStyle, [{ 833 | key: 'getValue', 834 | value: function getValue() { 835 | var style = {}; 836 | for (var key in this._style) { 837 | var value = this._style[key]; 838 | if (value instanceof Animated) { 839 | style[key] = value.getValue(); 840 | } else { 841 | style[key] = value; 842 | } 843 | } 844 | return style; 845 | } 846 | }, { 847 | key: 'getAnimatedValue', 848 | value: function getAnimatedValue() { 849 | var style = {}; 850 | for (var key in this._style) { 851 | var value = this._style[key]; 852 | if (value instanceof Animated) { 853 | style[key] = value.getAnimatedValue(); 854 | } 855 | } 856 | return style; 857 | } 858 | }, { 859 | key: 'attach', 860 | value: function attach() { 861 | for (var key in this._style) { 862 | var value = this._style[key]; 863 | if (value instanceof Animated) { 864 | value.addChild(this); 865 | } 866 | } 867 | } 868 | }, { 869 | key: 'detach', 870 | value: function detach() { 871 | for (var key in this._style) { 872 | var value = this._style[key]; 873 | if (value instanceof Animated) { 874 | value.removeChild(this); 875 | } 876 | } 877 | } 878 | }]); 879 | 880 | return AnimatedStyle; 881 | })(AnimatedWithChildren); 882 | 883 | var AnimatedProps = (function (_Animated2) { 884 | _inherits(AnimatedProps, _Animated2); 885 | 886 | function AnimatedProps(props, callback) { 887 | _classCallCheck(this, AnimatedProps); 888 | 889 | _get(Object.getPrototypeOf(AnimatedProps.prototype), 'constructor', this).call(this); 890 | if (props.style) { 891 | props = _extends({}, props, { 892 | style: new AnimatedStyle(props.style) 893 | }); 894 | } 895 | this._props = props; 896 | this._callback = callback; 897 | this.attach(); 898 | } 899 | 900 | _createClass(AnimatedProps, [{ 901 | key: 'getValue', 902 | value: function getValue() { 903 | var props = {}; 904 | for (var key in this._props) { 905 | var value = this._props[key]; 906 | if (value instanceof Animated) { 907 | props[key] = value.getValue(); 908 | } else { 909 | props[key] = value; 910 | } 911 | } 912 | return props; 913 | } 914 | }, { 915 | key: 'getAnimatedValue', 916 | value: function getAnimatedValue() { 917 | var props = {}; 918 | for (var key in this._props) { 919 | var value = this._props[key]; 920 | if (value instanceof Animated) { 921 | props[key] = value.getAnimatedValue(); 922 | } 923 | } 924 | return props; 925 | } 926 | }, { 927 | key: 'attach', 928 | value: function attach() { 929 | for (var key in this._props) { 930 | var value = this._props[key]; 931 | if (value instanceof Animated) { 932 | value.addChild(this); 933 | } 934 | } 935 | } 936 | }, { 937 | key: 'detach', 938 | value: function detach() { 939 | for (var key in this._props) { 940 | var value = this._props[key]; 941 | if (value instanceof Animated) { 942 | value.removeChild(this); 943 | } 944 | } 945 | } 946 | }, { 947 | key: 'update', 948 | value: function update() { 949 | this._callback(); 950 | } 951 | }]); 952 | 953 | return AnimatedProps; 954 | })(Animated); 955 | 956 | function createAnimatedComponent(Component) { 957 | var refName = 'node'; 958 | 959 | var AnimatedComponent = (function (_React$Component) { 960 | _inherits(AnimatedComponent, _React$Component); 961 | 962 | function AnimatedComponent() { 963 | _classCallCheck(this, AnimatedComponent); 964 | 965 | _get(Object.getPrototypeOf(AnimatedComponent.prototype), 'constructor', this).apply(this, arguments); 966 | } 967 | 968 | _createClass(AnimatedComponent, [{ 969 | key: 'componentWillUnmount', 970 | value: function componentWillUnmount() { 971 | this._propsAnimated && this._propsAnimated.detach(); 972 | } 973 | }, { 974 | key: 'setNativeProps', 975 | value: function setNativeProps(props) { 976 | this.refs[refName].setNativeProps(props); 977 | } 978 | }, { 979 | key: 'componentWillMount', 980 | value: function componentWillMount() { 981 | this.attachProps(this.props); 982 | } 983 | }, { 984 | key: 'attachProps', 985 | value: function attachProps(nextProps) { 986 | var _this6 = this; 987 | 988 | var oldPropsAnimated = this._propsAnimated; 989 | 990 | // The system is best designed when setNativeProps is implemented. It is 991 | // able to avoid re-rendering and directly set the attributes that 992 | // changed. However, setNativeProps can only be implemented on leaf 993 | // native components. If you want to animate a composite component, you 994 | // need to re-render it. In this case, we have a fallback that uses 995 | // forceUpdate. 996 | var callback = function callback() { 997 | if (_this6.refs[refName].setNativeProps) { 998 | var value = _this6._propsAnimated.getAnimatedValue(); 999 | _this6.refs[refName].setNativeProps(value); 1000 | } else { 1001 | _this6.forceUpdate(); 1002 | } 1003 | }; 1004 | 1005 | this._propsAnimated = new AnimatedProps(nextProps, callback); 1006 | 1007 | // When you call detach, it removes the element from the parent list 1008 | // of children. If it goes to 0, then the parent also detaches itself 1009 | // and so on. 1010 | // An optimization is to attach the new elements and THEN detach the old 1011 | // ones instead of detaching and THEN attaching. 1012 | // This way the intermediate state isn't to go to 0 and trigger 1013 | // this expensive recursive detaching to then re-attach everything on 1014 | // the very next operation. 1015 | oldPropsAnimated && oldPropsAnimated.detach(); 1016 | } 1017 | }, { 1018 | key: 'componentWillReceiveProps', 1019 | value: function componentWillReceiveProps(nextProps) { 1020 | this.attachProps(nextProps); 1021 | } 1022 | }, { 1023 | key: 'render', 1024 | value: function render() { 1025 | return _react2['default'].createElement(Component, _extends({}, this._propsAnimated.getValue(), { 1026 | ref: refName 1027 | })); 1028 | } 1029 | }]); 1030 | 1031 | return AnimatedComponent; 1032 | })(_react2['default'].Component); 1033 | 1034 | return AnimatedComponent; 1035 | } 1036 | 1037 | var AnimatedTracking = (function (_Animated3) { 1038 | _inherits(AnimatedTracking, _Animated3); 1039 | 1040 | function AnimatedTracking(value, parent, animationClass, animationConfig, callback) { 1041 | _classCallCheck(this, AnimatedTracking); 1042 | 1043 | _get(Object.getPrototypeOf(AnimatedTracking.prototype), 'constructor', this).call(this); 1044 | this._value = value; 1045 | this._parent = parent; 1046 | this._animationClass = animationClass; 1047 | this._animationConfig = animationConfig; 1048 | this._callback = callback; 1049 | this.attach(); 1050 | } 1051 | 1052 | _createClass(AnimatedTracking, [{ 1053 | key: 'getValue', 1054 | value: function getValue() { 1055 | return this._parent.getValue(); 1056 | } 1057 | }, { 1058 | key: 'attach', 1059 | value: function attach() { 1060 | this._active = true; 1061 | this._parent.addChild(this); 1062 | } 1063 | }, { 1064 | key: 'detach', 1065 | value: function detach() { 1066 | this._parent.removeChild(this); 1067 | this._active = false; 1068 | } 1069 | }, { 1070 | key: 'update', 1071 | value: function update() { 1072 | if (!this._active) { 1073 | console.warn('calling update on detached AnimatedTracking'); 1074 | return; 1075 | } 1076 | // console.log('AnimatedTracking update with ', 1077 | // {toValue: this._animationConfig.toValue.getValue(), value: this._value.getValue()}); 1078 | this._value.animate(new this._animationClass(_extends({}, this._animationConfig, { 1079 | toValue: this._animationConfig.toValue.getValue() 1080 | })), this._callback); 1081 | } 1082 | }]); 1083 | 1084 | return AnimatedTracking; 1085 | })(Animated); 1086 | 1087 | var maybeVectorAnim = function maybeVectorAnim(value, config, anim) { 1088 | if (value instanceof AnimatedVec2) { 1089 | var configX = _extends({}, config); 1090 | var configY = _extends({}, config); 1091 | for (var key in config) { 1092 | var _config$key = config[key]; 1093 | var x = _config$key.x; 1094 | var y = _config$key.y; 1095 | 1096 | if (x !== undefined && y !== undefined) { 1097 | configX[key] = x; 1098 | configY[key] = y; 1099 | } 1100 | } 1101 | // TODO: Urg, parallel breaks tracking :( 1102 | // return parallel([ 1103 | // anim(value.x, configX), 1104 | // anim(value.y, configY), 1105 | // ]); 1106 | anim(value.x, configX).start(); 1107 | return anim(value.y, configY); 1108 | } 1109 | return null; 1110 | }; 1111 | 1112 | var spring = function spring(value, config) { 1113 | return maybeVectorAnim(value, config, spring) || { 1114 | start: function start(callback) { 1115 | value.stopTracking(); 1116 | if (config.toValue instanceof Animated) { 1117 | value.track(new AnimatedTracking(value, config.toValue, SpringAnimation, config, callback)); 1118 | } else { 1119 | value.animate(new SpringAnimation(config), callback); 1120 | } 1121 | }, 1122 | 1123 | stop: function stop() { 1124 | value.stopAnimation(); 1125 | } 1126 | }; 1127 | }; 1128 | 1129 | var timing = function timing(value, config) { 1130 | return maybeVectorAnim(value, config, timing) || { 1131 | start: function start(callback) { 1132 | value.stopTracking(); 1133 | value.animate(new TimingAnimation(config), callback); 1134 | }, 1135 | 1136 | stop: function stop() { 1137 | value.stopAnimation(); 1138 | } 1139 | }; 1140 | }; 1141 | 1142 | var decay = function decay(value, config) { 1143 | return maybeVectorAnim(value, config, decay) || { 1144 | start: function start(callback) { 1145 | value.stopTracking(); 1146 | value.animate(new DecayAnimation(config), callback); 1147 | }, 1148 | 1149 | stop: function stop() { 1150 | value.stopAnimation(); 1151 | } 1152 | }; 1153 | }; 1154 | 1155 | var sequence = function sequence(animations) { 1156 | var current = 0; 1157 | return { 1158 | start: function start(callback) { 1159 | var onComplete = function onComplete(finished) { 1160 | if (!finished) { 1161 | callback && callback(finished); 1162 | return; 1163 | } 1164 | 1165 | current++; 1166 | 1167 | if (current === animations.length) { 1168 | callback && callback( /* finished */true); 1169 | return; 1170 | } 1171 | 1172 | animations[current].start(onComplete); 1173 | }; 1174 | 1175 | if (animations.length === 0) { 1176 | callback && callback( /* finished */true); 1177 | } else { 1178 | animations[current].start(onComplete); 1179 | } 1180 | }, 1181 | 1182 | stop: function stop() { 1183 | if (current < animations.length) { 1184 | animations[current].stop(); 1185 | } 1186 | } 1187 | }; 1188 | }; 1189 | 1190 | var parallel = function parallel(animations) { 1191 | var doneCount = 0; 1192 | // Variable to make sure we only call stop() at most once 1193 | var hasBeenStopped = false; 1194 | 1195 | var result = { 1196 | start: function start(callback) { 1197 | if (doneCount === animations.length) { 1198 | callback && callback( /* finished */true); 1199 | return; 1200 | } 1201 | 1202 | animations.forEach(function (animation, idx) { 1203 | animation.start(function (finished) { 1204 | doneCount++; 1205 | if (doneCount === animations.length) { 1206 | callback && callback(finished); 1207 | return; 1208 | } 1209 | 1210 | if (!finished && !hasBeenStopped) { 1211 | result.stop(); 1212 | } 1213 | }); 1214 | }); 1215 | }, 1216 | 1217 | stop: function stop() { 1218 | hasBeenStopped = true; 1219 | animations.forEach(function (animation) { 1220 | animation.stop(); 1221 | }); 1222 | } 1223 | }; 1224 | 1225 | return result; 1226 | }; 1227 | 1228 | var delay = function delay(time) { 1229 | // Would be nice to make a specialized implementation. 1230 | return timing(new AnimatedValue(0), { toValue: 0, delay: time, duration: 0 }); 1231 | }; 1232 | 1233 | var stagger = function stagger(time, animations) { 1234 | return parallel(animations.map(function (animation, i) { 1235 | return sequence([delay(time * i), animation]); 1236 | })); 1237 | }; 1238 | 1239 | /** 1240 | * Takes an array of mappings and extracts values from each arg accordingly, 1241 | * then calls setValue on the mapped outputs. e.g. 1242 | * 1243 | * onScroll={this.AnimatedEvent( 1244 | * [{nativeEvent: {contentOffset: {x: this._scrollX}}}] 1245 | * {listener, updatePeriod: 100} // optional listener invoked every 100ms 1246 | * ) 1247 | * ... 1248 | * onPanResponderMove: this.AnimatedEvent([ 1249 | * null, // raw event arg 1250 | * {dx: this._panX}, // gestureState arg 1251 | * ]), 1252 | * 1253 | */ 1254 | var event = function event(argMapping, config) { 1255 | var lastUpdate = 0; 1256 | var timer; 1257 | var isEnabled = true; 1258 | if (config && config.ref) { 1259 | config.ref({ 1260 | enable: function enable() { 1261 | isEnabled = true; 1262 | }, 1263 | disable: function disable() { 1264 | isEnabled = false; 1265 | clearTimeout(timer); 1266 | timer = null; 1267 | } 1268 | }); 1269 | } 1270 | var lastArgs; 1271 | return function () { 1272 | lastArgs = arguments; 1273 | if (!isEnabled) { 1274 | clearTimeout(timer); 1275 | timer = null; 1276 | return; 1277 | } 1278 | var traverse = function traverse(recMapping, recEvt, key) { 1279 | if (recMapping instanceof AnimatedValue || recMapping instanceof AnimatedInterpolation) { 1280 | invariant(typeof recEvt === 'number', 'Bad event element of type ' + typeof recEvt + ' for key ' + key); 1281 | recMapping.setValue(recEvt); 1282 | return; 1283 | } 1284 | invariant(typeof recMapping === 'object', 'Bad mapping of type ' + typeof recMapping + ' for key ' + key); 1285 | invariant(typeof recEvt === 'object', 'Bad event of type ' + typeof recEvt + ' for key ' + key); 1286 | for (var key in recMapping) { 1287 | traverse(recMapping[key], recEvt[key], key); 1288 | } 1289 | }; 1290 | argMapping.forEach(function (mapping, idx) { 1291 | traverse(mapping, lastArgs[idx], null); 1292 | }); 1293 | if (config && config.listener && !timer) { 1294 | var cb = function cb() { 1295 | lastUpdate = Date.now(); 1296 | timer = null; 1297 | config.listener.apply(null, lastArgs); 1298 | }; 1299 | if (config.updatePeriod) { 1300 | timer = setTimeout(cb, config.updatePeriod - Date.now() + lastUpdate); 1301 | } else { 1302 | cb(); 1303 | } 1304 | } 1305 | }; 1306 | }; 1307 | 1308 | module.exports = { 1309 | delay: delay, 1310 | sequence: sequence, 1311 | parallel: parallel, 1312 | stagger: stagger, 1313 | 1314 | decay: decay, 1315 | timing: timing, 1316 | spring: spring, 1317 | 1318 | event: event, 1319 | 1320 | Value: AnimatedValue, 1321 | Vec2: AnimatedVec2, 1322 | __PropsOnlyForTests: AnimatedProps, 1323 | div: createAnimatedComponent('div'), 1324 | createAnimatedComponent: createAnimatedComponent 1325 | }; 1326 | 1327 | // })(); -------------------------------------------------------------------------------- /lib/Easing.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule Easing 10 | */ 11 | 'use strict'; 12 | 13 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 16 | 17 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 18 | 19 | var _bezier2 = require('./bezier'); 20 | 21 | var _bezier3 = _interopRequireDefault(_bezier2); 22 | 23 | /** 24 | * This class implements common easing functions. The math is pretty obscure, 25 | * but this cool website has nice visual illustrations of what they represent: 26 | * http://xaedes.de/dev/transitions/ 27 | */ 28 | 29 | var Easing = (function () { 30 | function Easing() { 31 | _classCallCheck(this, Easing); 32 | } 33 | 34 | _createClass(Easing, null, [{ 35 | key: 'step0', 36 | value: function step0(n) { 37 | return n > 0 ? 1 : 0; 38 | } 39 | }, { 40 | key: 'step1', 41 | value: function step1(n) { 42 | return n >= 1 ? 1 : 0; 43 | } 44 | }, { 45 | key: 'linear', 46 | value: function linear(t) { 47 | return t; 48 | } 49 | }, { 50 | key: 'ease', 51 | value: (function (_ease) { 52 | function ease(_x) { 53 | return _ease.apply(this, arguments); 54 | } 55 | 56 | ease.toString = function () { 57 | return _ease.toString(); 58 | }; 59 | 60 | return ease; 61 | })(function (t) { 62 | return ease(t); 63 | }) 64 | }, { 65 | key: 'quad', 66 | value: function quad(t) { 67 | return t * t; 68 | } 69 | }, { 70 | key: 'cubic', 71 | value: function cubic(t) { 72 | return t * t * t; 73 | } 74 | }, { 75 | key: 'poly', 76 | value: function poly(n) { 77 | return function (t) { 78 | return Math.pow(t, n); 79 | }; 80 | } 81 | }, { 82 | key: 'sin', 83 | value: function sin(t) { 84 | return 1 - Math.cos(t * Math.PI / 2); 85 | } 86 | }, { 87 | key: 'circle', 88 | value: function circle(t) { 89 | return 1 - Math.sqrt(1 - t * t); 90 | } 91 | }, { 92 | key: 'exp', 93 | value: function exp(t) { 94 | return Math.pow(2, 10 * (t - 1)); 95 | } 96 | }, { 97 | key: 'elastic', 98 | value: function elastic(a, p) { 99 | var tau = Math.PI * 2; 100 | // flow isn't smart enough to figure out that s is always assigned to a 101 | // number before being used in the returned function 102 | var s; 103 | if (arguments.length < 2) { 104 | p = 0.45; 105 | } 106 | if (arguments.length) { 107 | s = p / tau * Math.asin(1 / a); 108 | } else { 109 | a = 1; 110 | s = p / 4; 111 | } 112 | return function (t) { 113 | return 1 + a * Math.pow(2, -10 * t) * Math.sin((t - s) * tau / p); 114 | }; 115 | } 116 | }, { 117 | key: 'back', 118 | value: function back(s) { 119 | if (s === undefined) { 120 | s = 1.70158; 121 | } 122 | return function (t) { 123 | return t * t * ((s + 1) * t - s); 124 | }; 125 | } 126 | }, { 127 | key: 'bounce', 128 | value: function bounce(t) { 129 | if (t < 1 / 2.75) { 130 | return 7.5625 * t * t; 131 | } 132 | 133 | if (t < 2 / 2.75) { 134 | t -= 1.5 / 2.75; 135 | return 7.5625 * t * t + 0.75; 136 | } 137 | 138 | if (t < 2.5 / 2.75) { 139 | t -= 2.25 / 2.75; 140 | return 7.5625 * t * t + 0.9375; 141 | } 142 | 143 | t -= 2.625 / 2.75; 144 | return 7.5625 * t * t + 0.984375; 145 | } 146 | }, { 147 | key: 'bezier', 148 | value: function bezier(x1, y1, x2, y2, epsilon) { 149 | if (epsilon === undefined) { 150 | // epsilon determines the precision of the solved values 151 | // a good approximation is: 152 | var duration = 500; // duration of animation in milliseconds. 153 | epsilon = 1000 / 60 / duration / 4; 154 | } 155 | 156 | return (0, _bezier3['default'])(x1, y1, x2, y2, epsilon); 157 | } 158 | }, { 159 | key: 'in', 160 | value: function _in(easing) { 161 | return easing; 162 | } 163 | }, { 164 | key: 'out', 165 | value: function out(easing) { 166 | return function (t) { 167 | return 1 - easing(1 - t); 168 | }; 169 | } 170 | }, { 171 | key: 'inOut', 172 | value: function inOut(easing) { 173 | return function (t) { 174 | if (t < 0.5) { 175 | return easing(t * 2) / 2; 176 | } 177 | return 1 - easing((1 - t) * 2) / 2; 178 | }; 179 | } 180 | }]); 181 | 182 | return Easing; 183 | })(); 184 | 185 | var ease = Easing.bezier(0.42, 0, 1, 1); 186 | 187 | module.exports = Easing; -------------------------------------------------------------------------------- /lib/Interpolation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule Interpolation 10 | * 11 | */ 12 | // 'use strict'; 13 | 14 | 'use strict'; 15 | 16 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 17 | 18 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 19 | 20 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 21 | 22 | var linear = function linear(t) { 23 | return t; 24 | }; 25 | 26 | /** 27 | * Very handy helper to map input ranges to output ranges with an easing 28 | * function and custom behavior outside of the ranges. 29 | */ 30 | 31 | var Interpolation = (function () { 32 | function Interpolation() { 33 | _classCallCheck(this, Interpolation); 34 | } 35 | 36 | _createClass(Interpolation, null, [{ 37 | key: 'create', 38 | value: function create(config) { 39 | 40 | if (config.outputRange && typeof config.outputRange[0] === 'string') { 41 | return createInterpolationFromStringOutputRange(config); 42 | } 43 | 44 | var outputRange = config.outputRange; 45 | checkInfiniteRange('outputRange', outputRange); 46 | 47 | var inputRange = config.inputRange; 48 | checkInfiniteRange('inputRange', inputRange); 49 | checkValidInputRange(inputRange); 50 | 51 | invariant(inputRange.length === outputRange.length, 'inputRange (' + inputRange.length + ') and outputRange (' + outputRange.length + ') must have the same length'); 52 | 53 | var easing = config.easing || linear; 54 | 55 | var extrapolateLeft = 'extend'; 56 | if (config.extrapolateLeft !== undefined) { 57 | extrapolateLeft = config.extrapolateLeft; 58 | } else if (config.extrapolate !== undefined) { 59 | extrapolateLeft = config.extrapolate; 60 | } 61 | 62 | var extrapolateRight = 'extend'; 63 | if (config.extrapolateRight !== undefined) { 64 | extrapolateRight = config.extrapolateRight; 65 | } else if (config.extrapolate !== undefined) { 66 | extrapolateRight = config.extrapolate; 67 | } 68 | 69 | return function (input) { 70 | invariant(typeof input === 'number', 'Cannot interpolation an input which is not a number'); 71 | 72 | var range = findRange(input, inputRange); 73 | return interpolate(input, inputRange[range], inputRange[range + 1], outputRange[range], outputRange[range + 1], easing, extrapolateLeft, extrapolateRight); 74 | }; 75 | } 76 | }]); 77 | 78 | return Interpolation; 79 | })(); 80 | 81 | function interpolate(input, inputMin, inputMax, outputMin, outputMax, easing, extrapolateLeft, extrapolateRight) { 82 | var result = input; 83 | 84 | // Extrapolate 85 | if (result < inputMin) { 86 | if (extrapolateLeft === 'identity') { 87 | return result; 88 | } else if (extrapolateLeft === 'clamp') { 89 | result = inputMin; 90 | } else if (extrapolateLeft === 'extend') { 91 | // noop 92 | } 93 | } 94 | 95 | if (result > inputMax) { 96 | if (extrapolateRight === 'identity') { 97 | return result; 98 | } else if (extrapolateRight === 'clamp') { 99 | result = inputMax; 100 | } else if (extrapolateRight === 'extend') { 101 | // noop 102 | } 103 | } 104 | 105 | if (outputMin === outputMax) { 106 | return outputMin; 107 | } 108 | 109 | if (inputMin === inputMax) { 110 | if (input <= inputMin) { 111 | return outputMin; 112 | } 113 | return outputMax; 114 | } 115 | 116 | // Input Range 117 | if (inputMin === -Infinity) { 118 | result = -result; 119 | } else if (inputMax === Infinity) { 120 | result = result - inputMin; 121 | } else { 122 | result = (result - inputMin) / (inputMax - inputMin); 123 | } 124 | 125 | // Easing 126 | result = easing(result); 127 | 128 | // Output Range 129 | if (outputMin === -Infinity) { 130 | result = -result; 131 | } else if (outputMax === Infinity) { 132 | result = result + outputMin; 133 | } else { 134 | result = result * (outputMax - outputMin) + outputMin; 135 | } 136 | 137 | return result; 138 | } 139 | 140 | var stringShapeRegex = /[0-9\.-]+/g; 141 | 142 | /** 143 | * Supports string shapes by extracting numbers so new values can be computed, 144 | * and recombines those values into new strings of the same shape. Supports 145 | * things like: 146 | * 147 | * rgba(123, 42, 99, 0.36) // colors 148 | * -45deg // values with units 149 | */ 150 | function createInterpolationFromStringOutputRange(config) { 151 | var outputRange = config.outputRange; 152 | invariant(outputRange.length >= 2, 'Bad output range'); 153 | checkPattern(outputRange); 154 | 155 | // ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.5)'] 156 | // -> 157 | // [ 158 | // [0, 50], 159 | // [100, 150], 160 | // [200, 250], 161 | // [0, 0.5], 162 | // ] 163 | var outputRanges = outputRange[0].match(stringShapeRegex).map(function () { 164 | return []; 165 | }); 166 | outputRange.forEach(function (value) { 167 | value.match(stringShapeRegex).forEach(function (number, i) { 168 | outputRanges[i].push(+number); 169 | }); 170 | }); 171 | 172 | var interpolations = outputRange[0].match(stringShapeRegex).map(function (value, i) { 173 | return Interpolation.create(_extends({}, config, { 174 | outputRange: outputRanges[i] 175 | })); 176 | }); 177 | 178 | return function (input) { 179 | var i = 0; 180 | // 'rgba(0, 100, 200, 0)' 181 | // -> 182 | // 'rgba(${interpolations[0](input)}, ${interpolations[1](input)}, ...' 183 | return outputRange[0].replace(stringShapeRegex, function () { 184 | return String(interpolations[i++](input)); 185 | }); 186 | }; 187 | } 188 | 189 | function checkPattern(arr) { 190 | var pattern = arr[0].replace(stringShapeRegex, ''); 191 | for (var i = 1; i < arr.length; ++i) { 192 | invariant(pattern === arr[i].replace(stringShapeRegex, ''), 'invalid pattern ' + arr[0] + ' and ' + arr[i]); 193 | } 194 | } 195 | 196 | function findRange(input, inputRange) { 197 | for (var i = 1; i < inputRange.length - 1; ++i) { 198 | if (inputRange[i] >= input) { 199 | break; 200 | } 201 | } 202 | return i - 1; 203 | } 204 | 205 | function checkValidInputRange(arr) { 206 | invariant(arr.length >= 2, 'inputRange must have at least 2 elements'); 207 | for (var i = 1; i < arr.length; ++i) { 208 | invariant(arr[i] >= arr[i - 1], 209 | /* $FlowFixMe(>=0.13.0) - In the addition expression below this comment, 210 | * one or both of the operands may be something that doesn't cleanly 211 | * convert to a string, like undefined, null, and object, etc. If you really 212 | * mean this implicit string conversion, you can do something like 213 | * String(myThing) 214 | */ 215 | 'inputRange must be monolithically increasing ' + arr); 216 | } 217 | } 218 | 219 | function checkInfiniteRange(name, arr) { 220 | invariant(arr.length >= 2, name + ' must have at least 2 elements'); 221 | invariant(arr.length !== 2 || arr[0] !== -Infinity || arr[1] !== Infinity, 222 | /* $FlowFixMe(>=0.13.0) - In the addition expression below this comment, 223 | * one or both of the operands may be something that doesn't cleanly convert 224 | * to a string, like undefined, null, and object, etc. If you really mean 225 | * this implicit string conversion, you can do something like 226 | * String(myThing) 227 | */ 228 | name + 'cannot be ]-infinity;+infinity[ ' + arr); 229 | } 230 | 231 | module.exports = Interpolation; -------------------------------------------------------------------------------- /lib/bezier.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (x1, y1, x2, y2, epsilon) { 4 | var curveX = function curveX(t) { 5 | var v = 1 - t; 6 | return 3 * v * v * t * x1 + 3 * v * t * t * x2 + t * t * t; 7 | }; 8 | var curveY = function curveY(t) { 9 | var v = 1 - t; 10 | return 3 * v * v * t * y1 + 3 * v * t * t * y2 + t * t * t; 11 | }; 12 | var derivativeCurveX = function derivativeCurveX(t) { 13 | var v = 1 - t; 14 | return 3 * (2 * (t - 1) * t + v * v) * x1 + 3 * (-t * t * t + 2 * v * t) * x2; 15 | }; 16 | return function (t) { 17 | var x = t, 18 | t0, 19 | t1, 20 | t2, 21 | x2, 22 | d2, 23 | i; 24 | for (t2 = x, i = 0; i < 8; i++) { 25 | x2 = curveX(t2) - x; 26 | if (Math.abs(x2) < epsilon) return curveY(t2); 27 | d2 = derivativeCurveX(t2); 28 | if (Math.abs(d2) < 1e-6) break; 29 | t2 = t2 - x2 / d2; 30 | } 31 | t0 = 0, t1 = 1, t2 = x; 32 | if (t2 < t0) return curveY(t0); 33 | if (t2 > t1) return curveY(t1); 34 | while (t0 < t1) { 35 | x2 = curveX(t2); 36 | if (Math.abs(x2 - x) < epsilon) return curveY(t2); 37 | if (x > x2) t0 = t2;else t1 = t2; 38 | t2 = (t1 - t0) * .5 + t0; 39 | } 40 | return curveY(t2); 41 | }; 42 | }; -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./Animated'); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-animated-web-bootleg", 3 | "version": "1.0.0", 4 | "description": "bootleg of code from http://fooo.fr/~vjeux/fb/animated-docs/ until they release ", 5 | "main": "./lib/index.js", 6 | "scripts": { 7 | "start": "babel-node server.js", 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "build": "babel src -d lib" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "babel": "^5.8.23", 15 | "babel-core": "^5.8.23", 16 | "babel-eslint": "^4.1.1", 17 | "babel-loader": "^5.3.2", 18 | "eslint": "^1.3.1", 19 | "react": "^0.13.3", 20 | "react-hot-loader": "^1.3.0", 21 | "webpack": "^1.12.1", 22 | "webpack-dev-server": "^1.10.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack'; 2 | import WebpackDevServer from 'webpack-dev-server'; 3 | import config from './webpack.config'; 4 | 5 | const isHot = !!process.env.HOT; 6 | 7 | new WebpackDevServer(webpack(config), { 8 | publicPath: config.output.publicPath, 9 | hot: isHot, 10 | historyApiFallback: true 11 | }).listen(3000, 'localhost', err => console.log(err || 'webpack at localhost:3000')); 12 | -------------------------------------------------------------------------------- /src/Animated.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule Animated 10 | */ 11 | 'use strict'; 12 | 13 | import Easing from './Easing'; 14 | import Interpolation from './interpolation'; 15 | import React from 'react'; 16 | 17 | // var Animated = (function() { 18 | 19 | // Note(vjeux): this would be better as an interface but flow doesn't 20 | // support them yet 21 | class Animated { 22 | attach(): void {} 23 | detach(): void {} 24 | getValue(): any {} 25 | getAnimatedValue(): any { return this.getValue(); } 26 | addChild(child: Animated) {} 27 | removeChild(child: Animated) {} 28 | getChildren(): Array { return []; } 29 | } 30 | 31 | // Important note: start() and stop() will only be called at most once. 32 | // Once an animation has been stopped or finished its course, it will 33 | // not be reused. 34 | class Animation { 35 | start( 36 | fromValue: number, 37 | onUpdate: (value: number) => void, 38 | onEnd: ?((finished: bool) => void), 39 | previousAnimation: ?Animation 40 | ): void {} 41 | stop(): void {} 42 | } 43 | 44 | class AnimatedWithChildren extends Animated { 45 | _children: Array; 46 | 47 | constructor() { 48 | super(); 49 | this._children = []; 50 | } 51 | 52 | addChild(child: Animated): void { 53 | if (this._children.length === 0) { 54 | this.attach(); 55 | } 56 | this._children.push(child); 57 | } 58 | 59 | removeChild(child: Animated): void { 60 | var index = this._children.indexOf(child); 61 | if (index === -1) { 62 | console.warn('Trying to remove a child that doesn\'t exist'); 63 | return; 64 | } 65 | this._children.splice(index, 1); 66 | if (this._children.length === 0) { 67 | this.detach(); 68 | } 69 | } 70 | 71 | getChildren(): Array { 72 | return this._children; 73 | } 74 | } 75 | 76 | /** 77 | * Animated works by building a directed acyclic graph of dependencies 78 | * transparently when you render your Animated components. 79 | * 80 | * new Animated.Value(0) 81 | * .interpolate() .interpolate() new Animated.Value(1) 82 | * opacity translateY scale 83 | * style transform 84 | * View#234 style 85 | * View#123 86 | * 87 | * A) Top Down phase 88 | * When an Animated.Value is updated, we recursively go down through this 89 | * graph in order to find leaf nodes: the views that we flag as needing 90 | * an update. 91 | * 92 | * B) Bottom Up phase 93 | * When a view is flagged as needing an update, we recursively go back up 94 | * in order to build the new value that it needs. The reason why we need 95 | * this two-phases process is to deal with composite props such as 96 | * transform which can receive values from multiple parents. 97 | */ 98 | function _flush(node: AnimatedValue): void { 99 | var animatedStyles = new Set(); 100 | function findAnimatedStyles(theNode) { 101 | if ('update' in theNode) { 102 | animatedStyles.add(theNode); 103 | } else { 104 | theNode.getChildren().forEach(findAnimatedStyles); 105 | } 106 | } 107 | findAnimatedStyles(node); 108 | animatedStyles.forEach(animatedStyle => animatedStyle.update()); 109 | } 110 | 111 | type TimingAnimationConfig = { 112 | toValue: number; 113 | easing?: (value: number) => number; 114 | duration?: number; 115 | delay?: number; 116 | }; 117 | 118 | class TimingAnimation extends Animation { 119 | _startTime: number; 120 | _fromValue: number; 121 | _toValue: number; 122 | _duration: number; 123 | _delay: number; 124 | _easing: (value: number) => number; 125 | _onUpdate: (value: number) => void; 126 | _onEnd: ?((finished: bool) => void); 127 | _animationFrame: any; 128 | _timeout: any; 129 | 130 | constructor( 131 | config: TimingAnimationConfig 132 | ) { 133 | super(); 134 | this._toValue = config.toValue; 135 | this._easing = config.easing || Easing.inOut(Easing.ease); 136 | this._duration = config.duration !== undefined ? config.duration : 500; 137 | this._delay = config.delay || 0; 138 | } 139 | 140 | start( 141 | fromValue: number, 142 | onUpdate: (value: number) => void, 143 | onEnd: ?((finished: bool) => void) 144 | ): void { 145 | this._fromValue = fromValue; 146 | this._onUpdate = onUpdate; 147 | this._onEnd = onEnd; 148 | 149 | var start = () => { 150 | this._startTime = Date.now(); 151 | this._animationFrame = window.requestAnimationFrame(this.onUpdate.bind(this)); 152 | }; 153 | if (this._delay) { 154 | this._timeout = setTimeout(start, this._delay); 155 | } else { 156 | start(); 157 | } 158 | } 159 | 160 | onUpdate(): void { 161 | var now = Date.now(); 162 | 163 | if (now > this._startTime + this._duration) { 164 | this._onUpdate( 165 | this._fromValue + this._easing(1) * (this._toValue - this._fromValue) 166 | ); 167 | var onEnd = this._onEnd; 168 | this._onEnd = null; 169 | onEnd && onEnd(/* finished */ true); 170 | return; 171 | } 172 | 173 | this._onUpdate( 174 | this._fromValue + 175 | this._easing((now - this._startTime) / this._duration) * 176 | (this._toValue - this._fromValue) 177 | ); 178 | 179 | this._animationFrame = window.requestAnimationFrame(this.onUpdate.bind(this)); 180 | } 181 | 182 | stop(): void { 183 | clearTimeout(this._timeout); 184 | window.cancelAnimationFrame(this._animationFrame); 185 | var onEnd = this._onEnd; 186 | this._onEnd = null; 187 | onEnd && onEnd(/* finished */ false); 188 | } 189 | } 190 | 191 | type DecayAnimationConfig = { 192 | velocity: number; 193 | deceleration?: number; 194 | }; 195 | 196 | class DecayAnimation extends Animation { 197 | _startTime: number; 198 | _lastValue: number; 199 | _fromValue: number; 200 | _deceleration: number; 201 | _velocity: number; 202 | _onUpdate: (value: number) => void; 203 | _onEnd: ?((finished: bool) => void); 204 | _animationFrame: any; 205 | 206 | constructor( 207 | config: DecayAnimationConfig 208 | ) { 209 | super(); 210 | this._deceleration = config.deceleration || 0.998; 211 | this._velocity = config.velocity; 212 | } 213 | 214 | start( 215 | fromValue: number, 216 | onUpdate: (value: number) => void, 217 | onEnd: ?((finished: bool) => void) 218 | ): void { 219 | this._lastValue = fromValue; 220 | this._fromValue = fromValue; 221 | this._onUpdate = onUpdate; 222 | this._onEnd = onEnd; 223 | this._startTime = Date.now(); 224 | this._animationFrame = window.requestAnimationFrame(this.onUpdate.bind(this)); 225 | } 226 | 227 | onUpdate(): void { 228 | var now = Date.now(); 229 | 230 | var value = this._fromValue + 231 | (this._velocity / (1 - this._deceleration)) * 232 | (1 - Math.exp(-(1 - this._deceleration) * (now - this._startTime))); 233 | 234 | this._onUpdate(value); 235 | 236 | if (Math.abs(this._lastValue - value) < 0.1) { 237 | var onEnd = this._onEnd; 238 | this._onEnd = null; 239 | onEnd && onEnd(/* finished */ true); 240 | return; 241 | } 242 | 243 | this._lastValue = value; 244 | this._animationFrame = window.requestAnimationFrame(this.onUpdate.bind(this)); 245 | } 246 | 247 | stop(): void { 248 | window.cancelAnimationFrame(this._animationFrame); 249 | var onEnd = this._onEnd; 250 | this._onEnd = null; 251 | onEnd && onEnd(/* finished */ false); 252 | } 253 | } 254 | 255 | type SpringAnimationConfig = { 256 | toValue: number; 257 | overshootClamping?: bool; 258 | restDisplacementThreshold?: number; 259 | restSpeedThreshold?: number; 260 | velocity?: number; 261 | bounciness?: number; 262 | speed?: number; 263 | tension?: number; 264 | friction?: number; 265 | }; 266 | 267 | function withDefault(value: ?T, defaultValue: T): T { 268 | if (value === undefined || value === null) { 269 | return defaultValue; 270 | } 271 | return value; 272 | } 273 | 274 | 275 | function tensionFromOrigamiValue(oValue) { 276 | return (oValue - 30.0) * 3.62 + 194.0; 277 | } 278 | function frictionFromOrigamiValue(oValue) { 279 | return (oValue - 8.0) * 3.0 + 25.0; 280 | } 281 | 282 | var fromOrigamiTensionAndFriction = function(tension, friction) { 283 | return { 284 | tension: tensionFromOrigamiValue(tension), 285 | friction: frictionFromOrigamiValue(friction) 286 | }; 287 | } 288 | 289 | var fromBouncinessAndSpeed = function(bounciness, speed) { 290 | function normalize(value, startValue, endValue) { 291 | return (value - startValue) / (endValue - startValue); 292 | } 293 | function projectNormal(n, start, end) { 294 | return start + (n * (end - start)); 295 | } 296 | function linearInterpolation(t, start, end) { 297 | return t * end + (1.0 - t) * start; 298 | } 299 | function quadraticOutInterpolation(t, start, end) { 300 | return linearInterpolation(2 * t - t * t, start, end); 301 | } 302 | function b3Friction1(x) { 303 | return (0.0007 * Math.pow(x, 3)) - 304 | (0.031 * Math.pow(x, 2)) + 0.64 * x + 1.28; 305 | } 306 | function b3Friction2(x) { 307 | return (0.000044 * Math.pow(x, 3)) - 308 | (0.006 * Math.pow(x, 2)) + 0.36 * x + 2.; 309 | } 310 | function b3Friction3(x) { 311 | return (0.00000045 * Math.pow(x, 3)) - 312 | (0.000332 * Math.pow(x, 2)) + 0.1078 * x + 5.84; 313 | } 314 | function b3Nobounce(tension) { 315 | if (tension <= 18) { 316 | return b3Friction1(tension); 317 | } else if (tension > 18 && tension <= 44) { 318 | return b3Friction2(tension); 319 | } else { 320 | return b3Friction3(tension); 321 | } 322 | } 323 | 324 | var b = normalize(bounciness / 1.7, 0, 20.0); 325 | b = projectNormal(b, 0.0, 0.8); 326 | var s = normalize(speed / 1.7, 0, 20.0); 327 | var bouncyTension = projectNormal(s, 0.5, 200) 328 | var bouncyFriction = quadraticOutInterpolation( 329 | b, 330 | b3Nobounce(bouncyTension), 331 | 0.01 332 | ); 333 | 334 | return { 335 | tension: tensionFromOrigamiValue(bouncyTension), 336 | friction: frictionFromOrigamiValue(bouncyFriction) 337 | }; 338 | } 339 | 340 | class SpringAnimation extends Animation { 341 | _overshootClamping: bool; 342 | _restDisplacementThreshold: number; 343 | _restSpeedThreshold: number; 344 | _lastVelocity: number; 345 | _tempVelocity: number; 346 | _startPosition: number; 347 | _lastPosition: number; 348 | _tempPosition: number; 349 | _fromValue: number; 350 | _toValue: number; 351 | _tension: number; 352 | _friction: number; 353 | _lastTime: number; 354 | _onUpdate: (value: number) => void; 355 | _onEnd: ?((finished: bool) => void); 356 | _animationFrame: any; 357 | _active: bool; 358 | 359 | constructor( 360 | config: SpringAnimationConfig 361 | ) { 362 | super(); 363 | 364 | this._overshootClamping = withDefault(config.overshootClamping, false); 365 | this._restDisplacementThreshold = withDefault(config.restDisplacementThreshold, 0.001); 366 | this._restSpeedThreshold = withDefault(config.restSpeedThreshold, 0.001); 367 | this._lastVelocity = withDefault(config.velocity, 0); 368 | this._tempVelocity = this._lastVelocity; 369 | this._toValue = config.toValue; 370 | 371 | var springConfig; 372 | if (config.bounciness !== undefined || config.speed !== undefined) { 373 | invariant( 374 | config.tension === undefined && config.friction === undefined, 375 | 'You can only define bounciness/speed or tension/friction but not both' 376 | ); 377 | springConfig = fromBouncinessAndSpeed( 378 | withDefault(config.bounciness, 8), 379 | withDefault(config.speed, 12) 380 | ); 381 | } else { 382 | springConfig = fromOrigamiTensionAndFriction( 383 | withDefault(config.tension, 40), 384 | withDefault(config.friction, 7) 385 | ); 386 | } 387 | this._tension = springConfig.tension; 388 | this._friction = springConfig.friction; 389 | } 390 | 391 | start( 392 | fromValue: number, 393 | onUpdate: (value: number) => void, 394 | onEnd: ?((finished: bool) => void), 395 | previousAnimation: ?Animation 396 | ): void { 397 | this._active = true; 398 | this._startPosition = fromValue; 399 | this._lastPosition = this._startPosition; 400 | this._tempPosition = this._lastPosition; 401 | 402 | this._onUpdate = onUpdate; 403 | this._onEnd = onEnd; 404 | this._lastTime = Date.now(); 405 | 406 | if (previousAnimation instanceof SpringAnimation) { 407 | var internalState = previousAnimation.getInternalState(); 408 | this._lastPosition = internalState.lastPosition; 409 | this._tempPosition = internalState.tempPosition; 410 | this._lastVelocity = internalState.lastVelocity; 411 | this._tempVelocity = internalState.tempVelocity; 412 | this._lastTime = internalState.lastTime; 413 | } 414 | 415 | this.onUpdate(); 416 | } 417 | 418 | getInternalState(): any { 419 | return { 420 | lastPosition: this._lastPosition, 421 | tempPosition: this._tempPosition, 422 | lastVelocity: this._lastVelocity, 423 | tempVelocity: this._tempVelocity, 424 | lastTime: this._lastTime, 425 | }; 426 | } 427 | 428 | onUpdate(): void { 429 | if (!this._active) { 430 | return; 431 | } 432 | var now = Date.now(); 433 | 434 | var position = this._lastPosition; 435 | var velocity = this._lastVelocity; 436 | 437 | var tempPosition = position; 438 | var tempVelocity = velocity; 439 | 440 | var TIMESTEP_MSEC = 4; 441 | var numSteps = Math.floor((now - this._lastTime) / TIMESTEP_MSEC); 442 | for (var i = 0; i < numSteps; ++i) { 443 | // Velocity is based on seconds instead of milliseconds 444 | var step = TIMESTEP_MSEC / 1000; 445 | 446 | var aVelocity = velocity; 447 | var aAcceleration = this._tension * (this._toValue - tempPosition) - this._friction * tempVelocity; 448 | tempPosition = position + aVelocity * step / 2; 449 | tempVelocity = velocity + aAcceleration * step / 2; 450 | 451 | var bVelocity = tempVelocity; 452 | var bAcceleration = this._tension * (this._toValue - tempPosition) - this._friction * tempVelocity; 453 | tempPosition = position + bVelocity * step / 2; 454 | tempVelocity = velocity + bAcceleration * step / 2; 455 | 456 | var cVelocity = tempVelocity; 457 | var cAcceleration = this._tension * (this._toValue - tempPosition) - this._friction * tempVelocity; 458 | tempPosition = position + cVelocity * step; 459 | tempVelocity = velocity + cAcceleration * step; 460 | 461 | var dVelocity = tempVelocity; 462 | var dAcceleration = this._tension * (this._toValue - tempPosition) - this._friction * tempVelocity; 463 | 464 | var dxdt = (aVelocity + 2 * (bVelocity + cVelocity) + dVelocity) / 6; 465 | var dvdt = (aAcceleration + 2 * (bAcceleration + cAcceleration) + dAcceleration) / 6; 466 | 467 | position += dxdt * step; 468 | velocity += dvdt * step; 469 | } 470 | 471 | this._lastTime = now; 472 | this._tempPosition = tempPosition; 473 | this._tempVelocity = tempVelocity; 474 | this._lastPosition = position; 475 | this._lastVelocity = velocity; 476 | 477 | this._onUpdate(position); 478 | 479 | // Conditions for stopping the spring animation 480 | var isOvershooting = false; 481 | if (this._overshootClamping && this._tension !== 0) { 482 | if (this._startPosition < this._toValue) { 483 | isOvershooting = position > this._toValue; 484 | } else { 485 | isOvershooting = position < this._toValue; 486 | } 487 | } 488 | var isVelocity = Math.abs(velocity) <= this._restSpeedThreshold; 489 | var isDisplacement = true; 490 | if (this._tension !== 0) { 491 | isDisplacement = Math.abs(this._toValue - position) <= this._restDisplacementThreshold; 492 | } 493 | if (isOvershooting || (isVelocity && isDisplacement)) { 494 | var onEnd = this._onEnd; 495 | this._onEnd = null; 496 | onEnd && onEnd(/* finished */ true); 497 | return; 498 | } 499 | this._animationFrame = window.requestAnimationFrame(this.onUpdate.bind(this)); 500 | } 501 | 502 | stop(): void { 503 | this._active = false; 504 | window.cancelAnimationFrame(this._animationFrame); 505 | var onEnd = this._onEnd; 506 | this._onEnd = null; 507 | onEnd && onEnd(/* finished */ false); 508 | } 509 | } 510 | 511 | type ValueListenerCallback = (state: {value: number}) => void; 512 | 513 | var _uniqueId = 1; 514 | 515 | class AnimatedValue extends AnimatedWithChildren { 516 | _value: number; 517 | _offset: number; 518 | _animation: ?Animation; 519 | _listeners: {[key: number]: ValueListenerCallback}; 520 | 521 | constructor(value: number) { 522 | super(); 523 | this._value = value; 524 | this._offset = 0; 525 | this._animation = null; 526 | this._listeners = {}; 527 | } 528 | 529 | detach() { 530 | this.stopAnimation(); 531 | } 532 | 533 | getValue(): number { 534 | return this._value + this._offset; 535 | } 536 | 537 | setValue(value: number): void { 538 | if (this._animation) { 539 | this._animation.stop(); 540 | this._animation = null; 541 | } 542 | this._updateValue(value); 543 | } 544 | 545 | getOffset(): number { 546 | return this._offset; 547 | } 548 | 549 | setOffset(offset: number): void { 550 | this._offset = offset; 551 | } 552 | 553 | addListener(callback: ValueListenerCallback): number { 554 | var id = _uniqueId++; 555 | this._listeners[id] = callback; 556 | return id; 557 | } 558 | 559 | removeListener(id: number): void { 560 | delete this._listeners[id]; 561 | } 562 | 563 | animate(animation: Animation, callback: ?((finished: bool) => void)): void { 564 | var previousAnimation = this._animation; 565 | this._animation && this._animation.stop(); 566 | this._animation = animation; 567 | animation.start( 568 | this._value, 569 | (value) => { 570 | this._updateValue(value); 571 | }, 572 | (finished) => { 573 | this._animation = null; 574 | callback && callback(finished); 575 | }, 576 | previousAnimation 577 | ); 578 | } 579 | 580 | stopAnimation(callback?: ?() => number): void { 581 | this.stopTracking(); 582 | this._animation && this._animation.stop(); 583 | callback && callback(this._value); 584 | } 585 | 586 | stopTracking(): void { 587 | this._tracking && this._tracking.detach(); 588 | } 589 | 590 | track(tracking: Animation): void { 591 | this.stopTracking(); 592 | this._tracking = tracking; 593 | } 594 | 595 | interpolate(config: InterpolationConfigType): AnimatedInterpolation { 596 | return new AnimatedInterpolation(this, Interpolation.create(config)); 597 | } 598 | 599 | _updateValue(value: number): void { 600 | if (value === this._value) { 601 | return; 602 | } 603 | this._value = value; 604 | _flush(this); 605 | for (var key in this._listeners) { 606 | this._listeners[key]({value: this.getValue()}); 607 | } 608 | } 609 | } 610 | 611 | type Vec2ListenerCallback = (state: {x: number; y: number}) => void; 612 | class AnimatedVec2 extends AnimatedWithChildren { 613 | x: AnimatedValue; 614 | y: AnimatedValue; 615 | _listeners: {[key: number]: Vec2ListenerCallback}; 616 | 617 | constructor(value?: {x: number; y: number}) { 618 | super(); 619 | value = value || {x: 0, y: 0}; 620 | if (typeof value.x === 'number') { 621 | this.x = new AnimatedValue(value.x); 622 | this.y = new AnimatedValue(value.y); 623 | } else { 624 | this.x = value.x; 625 | this.y = value.y; 626 | } 627 | this._listeners = {}; 628 | } 629 | 630 | setValue(value: {x: number; y: number}) { 631 | this.x.setValue(value.x); 632 | this.y.setValue(value.y); 633 | } 634 | 635 | setOffset(offset: {x: number; y: number}) { 636 | this.x.setOffset(offset.x); 637 | this.y.setOffset(offset.y); 638 | } 639 | 640 | addListener(callback: Vec2ListenerCallback): number { 641 | var id = _uniqueId++; 642 | var jointCallback = (value) => { 643 | callback({x: this.x.getValue(), y: this.y.getValue()}); 644 | }; 645 | this._listeners[id] = { 646 | x: this.x.addListener(jointCallback), 647 | y: this.y.addListener(jointCallback), 648 | }; 649 | return id; 650 | } 651 | 652 | removeListener(id: number): void { 653 | this.x.removeListener(this._listeners[id].x); 654 | this.y.removeListener(this._listeners[id].y); 655 | delete this._listeners[id]; 656 | } 657 | 658 | offset(theOffset) { // chunky...perf? 659 | return new AnimatedVec2({ 660 | x: this.x.interpolate({ 661 | inputRange: [0, 1], 662 | outputRange: [theOffset.x, theOffset.x + 1], 663 | }), 664 | y: this.y.interpolate({ 665 | inputRange: [0, 1], 666 | outputRange: [theOffset.y, theOffset.y + 1], 667 | }), 668 | }); 669 | } 670 | 671 | getLayout() { 672 | return { 673 | left: this.x, 674 | top: this.y, 675 | }; 676 | } 677 | 678 | getTranslateTransform() { 679 | return [ 680 | {translateX: this.x}, 681 | {translateY: this.y} 682 | ]; 683 | } 684 | } 685 | 686 | class AnimatedInterpolation extends AnimatedWithChildren { 687 | _parent: Animated; 688 | _interpolation: (input: number) => number | string; 689 | 690 | constructor(parent: Animated, interpolation: (input: number) => number | string) { 691 | super(); 692 | this._parent = parent; 693 | this._interpolation = interpolation; 694 | } 695 | 696 | getValue(): number | string { 697 | var parentValue: number = this._parent.getValue(); 698 | invariant( 699 | typeof parentValue === 'number', 700 | 'Cannot interpolate an input which is not a number.' 701 | ); 702 | return this._interpolation(parentValue); 703 | } 704 | 705 | interpolate(config: InterpolationConfigType): AnimatedInterpolation { 706 | return new AnimatedInterpolation(this, Interpolation.create(config)); 707 | } 708 | 709 | attach(): void { 710 | this._parent.addChild(this); 711 | } 712 | 713 | detach(): void { 714 | this._parent.removeChild(this); 715 | } 716 | } 717 | 718 | class AnimatedTransform extends AnimatedWithChildren { 719 | _transforms: Array; 720 | 721 | constructor(transforms: Array) { 722 | super(); 723 | this._transforms = transforms; 724 | } 725 | 726 | getValue(): Array { 727 | return this._transforms.map(transform => { 728 | var result = ''; 729 | for (var key in transform) { 730 | var value = transform[key]; 731 | if (value instanceof Animated) { 732 | result += key + '(' + value.getValue() + ')'; 733 | } else { 734 | result += key + '(' + value.join(',') + ')'; 735 | } 736 | } 737 | return result; 738 | }).join(' '); 739 | } 740 | 741 | getAnimatedValue(): Array { 742 | return this._transforms.map(transform => { 743 | var result = ''; 744 | for (var key in transform) { 745 | var value = transform[key]; 746 | if (value instanceof Animated) { 747 | result += key + '(' + value.getValue() + ') '; 748 | } else { 749 | // All transform components needed to recompose matrix 750 | result += key + '(' + value.join(',') + ') '; 751 | } 752 | } 753 | return result; 754 | }).join('').trim(); 755 | } 756 | 757 | attach(): void { 758 | this._transforms.forEach(transform => { 759 | for (var key in transform) { 760 | var value = transform[key]; 761 | if (value instanceof Animated) { 762 | value.addChild(this); 763 | } 764 | } 765 | }); 766 | } 767 | 768 | detach(): void { 769 | this._transforms.forEach(transform => { 770 | for (var key in transform) { 771 | var value = transform[key]; 772 | if (value instanceof Animated) { 773 | value.removeChild(this); 774 | } 775 | } 776 | }); 777 | } 778 | } 779 | 780 | class AnimatedStyle extends AnimatedWithChildren { 781 | _style: Object; 782 | 783 | constructor(style: any) { 784 | super(); 785 | style = style || {}; 786 | if (style.transform) { 787 | style = { 788 | ...style, 789 | transform: new AnimatedTransform(style.transform), 790 | }; 791 | } 792 | this._style = style; 793 | } 794 | 795 | getValue(): Object { 796 | var style = {}; 797 | for (var key in this._style) { 798 | var value = this._style[key]; 799 | if (value instanceof Animated) { 800 | style[key] = value.getValue(); 801 | } else { 802 | style[key] = value; 803 | } 804 | } 805 | return style; 806 | } 807 | 808 | getAnimatedValue(): Object { 809 | var style = {}; 810 | for (var key in this._style) { 811 | var value = this._style[key]; 812 | if (value instanceof Animated) { 813 | style[key] = value.getAnimatedValue(); 814 | } 815 | } 816 | return style; 817 | } 818 | 819 | attach(): void { 820 | for (var key in this._style) { 821 | var value = this._style[key]; 822 | if (value instanceof Animated) { 823 | value.addChild(this); 824 | } 825 | } 826 | } 827 | 828 | detach(): void { 829 | for (var key in this._style) { 830 | var value = this._style[key]; 831 | if (value instanceof Animated) { 832 | value.removeChild(this); 833 | } 834 | } 835 | } 836 | } 837 | 838 | class AnimatedProps extends Animated { 839 | _props: Object; 840 | _callback: () => void; 841 | 842 | constructor( 843 | props: Object, 844 | callback: () => void 845 | ) { 846 | super(); 847 | if (props.style) { 848 | props = { 849 | ...props, 850 | style: new AnimatedStyle(props.style), 851 | }; 852 | } 853 | this._props = props; 854 | this._callback = callback; 855 | this.attach(); 856 | } 857 | 858 | getValue(): Object { 859 | var props = {}; 860 | for (var key in this._props) { 861 | var value = this._props[key]; 862 | if (value instanceof Animated) { 863 | props[key] = value.getValue(); 864 | } else { 865 | props[key] = value; 866 | } 867 | } 868 | return props; 869 | } 870 | 871 | getAnimatedValue(): Object { 872 | var props = {}; 873 | for (var key in this._props) { 874 | var value = this._props[key]; 875 | if (value instanceof Animated) { 876 | props[key] = value.getAnimatedValue(); 877 | } 878 | } 879 | return props; 880 | } 881 | 882 | attach(): void { 883 | for (var key in this._props) { 884 | var value = this._props[key]; 885 | if (value instanceof Animated) { 886 | value.addChild(this); 887 | } 888 | } 889 | } 890 | 891 | detach(): void { 892 | for (var key in this._props) { 893 | var value = this._props[key]; 894 | if (value instanceof Animated) { 895 | value.removeChild(this); 896 | } 897 | } 898 | } 899 | 900 | update(): void { 901 | this._callback(); 902 | } 903 | } 904 | 905 | function createAnimatedComponent(Component: any): any { 906 | var refName = 'node'; 907 | 908 | class AnimatedComponent extends React.Component { 909 | _propsAnimated: AnimatedProps; 910 | 911 | componentWillUnmount() { 912 | this._propsAnimated && this._propsAnimated.detach(); 913 | } 914 | 915 | setNativeProps(props) { 916 | this.refs[refName].setNativeProps(props); 917 | } 918 | 919 | componentWillMount() { 920 | this.attachProps(this.props); 921 | } 922 | 923 | attachProps(nextProps) { 924 | var oldPropsAnimated = this._propsAnimated; 925 | 926 | // The system is best designed when setNativeProps is implemented. It is 927 | // able to avoid re-rendering and directly set the attributes that 928 | // changed. However, setNativeProps can only be implemented on leaf 929 | // native components. If you want to animate a composite component, you 930 | // need to re-render it. In this case, we have a fallback that uses 931 | // forceUpdate. 932 | var callback = () => { 933 | if (this.refs[refName].setNativeProps) { 934 | var value = this._propsAnimated.getAnimatedValue(); 935 | this.refs[refName].setNativeProps(value); 936 | } else { 937 | this.forceUpdate(); 938 | } 939 | }; 940 | 941 | this._propsAnimated = new AnimatedProps( 942 | nextProps, 943 | callback 944 | ); 945 | 946 | // When you call detach, it removes the element from the parent list 947 | // of children. If it goes to 0, then the parent also detaches itself 948 | // and so on. 949 | // An optimization is to attach the new elements and THEN detach the old 950 | // ones instead of detaching and THEN attaching. 951 | // This way the intermediate state isn't to go to 0 and trigger 952 | // this expensive recursive detaching to then re-attach everything on 953 | // the very next operation. 954 | oldPropsAnimated && oldPropsAnimated.detach(); 955 | } 956 | 957 | componentWillReceiveProps(nextProps) { 958 | this.attachProps(nextProps); 959 | } 960 | 961 | render() { 962 | return ( 963 | 967 | ); 968 | } 969 | } 970 | 971 | return AnimatedComponent; 972 | } 973 | 974 | class AnimatedTracking extends Animated { 975 | _parent: Animated; 976 | _callback: () => void; 977 | 978 | constructor( 979 | value: AnimatedValue, 980 | parent: Animated, 981 | animationClass: any, 982 | animationConfig: any, 983 | callback: any 984 | ) { 985 | super(); 986 | this._value = value; 987 | this._parent = parent; 988 | this._animationClass = animationClass; 989 | this._animationConfig = animationConfig; 990 | this._callback = callback; 991 | this.attach(); 992 | } 993 | 994 | getValue(): Object { 995 | return this._parent.getValue(); 996 | } 997 | 998 | attach(): void { 999 | this._active = true; 1000 | this._parent.addChild(this); 1001 | } 1002 | 1003 | detach(): void { 1004 | this._parent.removeChild(this); 1005 | this._active = false; 1006 | } 1007 | 1008 | update(): void { 1009 | if (!this._active) { 1010 | console.warn('calling update on detached AnimatedTracking'); 1011 | return; 1012 | } 1013 | // console.log('AnimatedTracking update with ', 1014 | // {toValue: this._animationConfig.toValue.getValue(), value: this._value.getValue()}); 1015 | this._value.animate(new this._animationClass({ 1016 | ...this._animationConfig, 1017 | toValue: (this._animationConfig.toValue: any).getValue(), 1018 | }), this._callback); 1019 | } 1020 | } 1021 | 1022 | type CompositeAnimation = { 1023 | start: (callback?: ?(finished: bool) => void) => void; 1024 | stop: () => void; 1025 | }; 1026 | 1027 | var maybeVectorAnim = function( 1028 | value: AnimatedValue, 1029 | config: Object, 1030 | anim: (value: AnimatedValue, config: Object) => CompositeAnimation 1031 | ): CompositeAnimation { 1032 | if (value instanceof AnimatedVec2) { 1033 | var configX = {...config}; 1034 | var configY = {...config}; 1035 | for (var key in config) { 1036 | var {x, y} = config[key]; 1037 | if (x !== undefined && y !== undefined) { 1038 | configX[key] = x; 1039 | configY[key] = y; 1040 | } 1041 | } 1042 | // TODO: Urg, parallel breaks tracking :( 1043 | // return parallel([ 1044 | // anim(value.x, configX), 1045 | // anim(value.y, configY), 1046 | // ]); 1047 | anim(value.x, configX).start(); 1048 | return anim(value.y, configY); 1049 | } 1050 | return null; 1051 | }; 1052 | 1053 | var spring = function( 1054 | value: AnimatedValue, 1055 | config: SpringAnimationConfig 1056 | ): CompositeAnimation { 1057 | return maybeVectorAnim(value, config, spring) || { 1058 | start: function(callback?: ?(finished: bool) => void): void { 1059 | value.stopTracking(); 1060 | if (config.toValue instanceof Animated) { 1061 | value.track(new AnimatedTracking( 1062 | value, 1063 | config.toValue, 1064 | SpringAnimation, 1065 | config, 1066 | callback 1067 | )); 1068 | } else { 1069 | value.animate(new SpringAnimation(config), callback); 1070 | } 1071 | }, 1072 | 1073 | stop: function(): void { 1074 | value.stopAnimation(); 1075 | }, 1076 | }; 1077 | }; 1078 | 1079 | var timing = function( 1080 | value: AnimatedValue, 1081 | config: TimingAnimationConfig 1082 | ): CompositeAnimation { 1083 | return maybeVectorAnim(value, config, timing) || { 1084 | start: function(callback?: ?(finished: bool) => void): void { 1085 | value.stopTracking(); 1086 | value.animate(new TimingAnimation(config), callback); 1087 | }, 1088 | 1089 | stop: function(): void { 1090 | value.stopAnimation(); 1091 | }, 1092 | }; 1093 | }; 1094 | 1095 | var decay = function( 1096 | value: AnimatedValue, 1097 | config: DecayAnimationConfig 1098 | ): CompositeAnimation { 1099 | return maybeVectorAnim(value, config, decay) || { 1100 | start: function(callback?: ?(finished: bool) => void): void { 1101 | value.stopTracking(); 1102 | value.animate(new DecayAnimation(config), callback); 1103 | }, 1104 | 1105 | stop: function(): void { 1106 | value.stopAnimation(); 1107 | }, 1108 | }; 1109 | }; 1110 | 1111 | var sequence = function( 1112 | animations: Array 1113 | ): CompositeAnimation { 1114 | var current = 0; 1115 | return { 1116 | start: function(callback?: ?(finished: bool) => void) { 1117 | var onComplete = function(finished) { 1118 | if (!finished) { 1119 | callback && callback(finished); 1120 | return; 1121 | } 1122 | 1123 | current++; 1124 | 1125 | if (current === animations.length) { 1126 | callback && callback(/* finished */ true); 1127 | return; 1128 | } 1129 | 1130 | animations[current].start(onComplete); 1131 | }; 1132 | 1133 | if (animations.length === 0) { 1134 | callback && callback(/* finished */ true); 1135 | } else { 1136 | animations[current].start(onComplete); 1137 | } 1138 | }, 1139 | 1140 | stop: function() { 1141 | if (current < animations.length) { 1142 | animations[current].stop(); 1143 | } 1144 | } 1145 | }; 1146 | }; 1147 | 1148 | var parallel = function( 1149 | animations: Array 1150 | ): CompositeAnimation { 1151 | var doneCount = 0; 1152 | // Variable to make sure we only call stop() at most once 1153 | var hasBeenStopped = false; 1154 | 1155 | var result = { 1156 | start: function(callback?: ?(finished: bool) => void) { 1157 | if (doneCount === animations.length) { 1158 | callback && callback(/* finished */ true); 1159 | return; 1160 | } 1161 | 1162 | animations.forEach((animation, idx) => { 1163 | animation.start(finished => { 1164 | doneCount++; 1165 | if (doneCount === animations.length) { 1166 | callback && callback(finished); 1167 | return; 1168 | } 1169 | 1170 | if (!finished && !hasBeenStopped) { 1171 | result.stop(); 1172 | } 1173 | }); 1174 | }); 1175 | }, 1176 | 1177 | stop: function(): void { 1178 | hasBeenStopped = true; 1179 | animations.forEach(animation => { 1180 | animation.stop(); 1181 | }); 1182 | } 1183 | }; 1184 | 1185 | return result; 1186 | }; 1187 | 1188 | var delay = function(time: number): CompositeAnimation { 1189 | // Would be nice to make a specialized implementation. 1190 | return timing(new AnimatedValue(0), {toValue: 0, delay: time, duration: 0}); 1191 | }; 1192 | 1193 | var stagger = function( 1194 | time: number, 1195 | animations: Array 1196 | ): CompositeAnimation { 1197 | return parallel(animations.map((animation, i) => { 1198 | return sequence([ 1199 | delay(time * i), 1200 | animation, 1201 | ]); 1202 | })); 1203 | }; 1204 | 1205 | type Mapping = {[key: string]: Mapping} | AnimatedValue; 1206 | 1207 | /** 1208 | * Takes an array of mappings and extracts values from each arg accordingly, 1209 | * then calls setValue on the mapped outputs. e.g. 1210 | * 1211 | * onScroll={this.AnimatedEvent( 1212 | * [{nativeEvent: {contentOffset: {x: this._scrollX}}}] 1213 | * {listener, updatePeriod: 100} // optional listener invoked every 100ms 1214 | * ) 1215 | * ... 1216 | * onPanResponderMove: this.AnimatedEvent([ 1217 | * null, // raw event arg 1218 | * {dx: this._panX}, // gestureState arg 1219 | * ]), 1220 | * 1221 | */ 1222 | var event = function( 1223 | argMapping: Array, 1224 | config?: any 1225 | ): () => void { 1226 | var lastUpdate = 0; 1227 | var timer; 1228 | var isEnabled = true; 1229 | if (config && config.ref) { 1230 | config.ref({ 1231 | enable: () => { 1232 | isEnabled = true; 1233 | }, 1234 | disable: () => { 1235 | isEnabled = false; 1236 | clearTimeout(timer); 1237 | timer = null; 1238 | }, 1239 | }); 1240 | } 1241 | var lastArgs; 1242 | return function(): void { 1243 | lastArgs = arguments; 1244 | if (!isEnabled) { 1245 | clearTimeout(timer); 1246 | timer = null; 1247 | return; 1248 | } 1249 | var traverse = function(recMapping, recEvt, key) { 1250 | if (recMapping instanceof AnimatedValue 1251 | || recMapping instanceof AnimatedInterpolation) { 1252 | invariant( 1253 | typeof recEvt === 'number', 1254 | 'Bad event element of type ' + typeof recEvt + ' for key ' + key 1255 | ); 1256 | recMapping.setValue(recEvt); 1257 | return; 1258 | } 1259 | invariant( 1260 | typeof recMapping === 'object', 1261 | 'Bad mapping of type ' + typeof recMapping + ' for key ' + key 1262 | ); 1263 | invariant( 1264 | typeof recEvt === 'object', 1265 | 'Bad event of type ' + typeof recEvt + ' for key ' + key 1266 | ); 1267 | for (var key in recMapping) { 1268 | traverse(recMapping[key], recEvt[key], key); 1269 | } 1270 | }; 1271 | argMapping.forEach((mapping, idx) => { 1272 | traverse(mapping, lastArgs[idx], null); 1273 | }); 1274 | if (config && config.listener && !timer) { 1275 | var cb = () => { 1276 | lastUpdate = Date.now(); 1277 | timer = null; 1278 | config.listener.apply(null, lastArgs); 1279 | }; 1280 | if (config.updatePeriod) { 1281 | timer = setTimeout(cb, config.updatePeriod - Date.now() + lastUpdate); 1282 | } else { 1283 | cb(); 1284 | } 1285 | } 1286 | }; 1287 | }; 1288 | 1289 | module.exports = { 1290 | delay, 1291 | sequence, 1292 | parallel, 1293 | stagger, 1294 | 1295 | decay, 1296 | timing, 1297 | spring, 1298 | 1299 | event, 1300 | 1301 | Value: AnimatedValue, 1302 | Vec2: AnimatedVec2, 1303 | __PropsOnlyForTests: AnimatedProps, 1304 | div: createAnimatedComponent('div'), 1305 | createAnimatedComponent, 1306 | }; 1307 | 1308 | // })(); 1309 | -------------------------------------------------------------------------------- /src/Easing.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule Easing 10 | */ 11 | 'use strict'; 12 | import bezier from './bezier'; 13 | 14 | /** 15 | * This class implements common easing functions. The math is pretty obscure, 16 | * but this cool website has nice visual illustrations of what they represent: 17 | * http://xaedes.de/dev/transitions/ 18 | */ 19 | class Easing { 20 | static step0(n) { 21 | return n > 0 ? 1 : 0; 22 | } 23 | 24 | static step1(n) { 25 | return n >= 1 ? 1 : 0; 26 | } 27 | 28 | static linear(t) { 29 | return t; 30 | } 31 | 32 | static ease(t: number): number { 33 | return ease(t); 34 | } 35 | 36 | static quad(t) { 37 | return t * t; 38 | } 39 | 40 | static cubic(t) { 41 | return t * t * t; 42 | } 43 | 44 | static poly(n) { 45 | return (t) => Math.pow(t, n); 46 | } 47 | 48 | static sin(t) { 49 | return 1 - Math.cos(t * Math.PI / 2); 50 | } 51 | 52 | static circle(t) { 53 | return 1 - Math.sqrt(1 - t * t); 54 | } 55 | 56 | static exp(t) { 57 | return Math.pow(2, 10 * (t - 1)); 58 | } 59 | 60 | static elastic(a: number, p: number): (t: number) => number { 61 | var tau = Math.PI * 2; 62 | // flow isn't smart enough to figure out that s is always assigned to a 63 | // number before being used in the returned function 64 | var s: any; 65 | if (arguments.length < 2) { 66 | p = 0.45; 67 | } 68 | if (arguments.length) { 69 | s = p / tau * Math.asin(1 / a); 70 | } else { 71 | a = 1; 72 | s = p / 4; 73 | } 74 | return (t) => 1 + a * Math.pow(2, -10 * t) * Math.sin((t - s) * tau / p); 75 | }; 76 | 77 | static back(s: number): (t: number) => number { 78 | if (s === undefined) { 79 | s = 1.70158; 80 | } 81 | return (t) => t * t * ((s + 1) * t - s); 82 | }; 83 | 84 | static bounce(t: number): number { 85 | if (t < 1 / 2.75) { 86 | return 7.5625 * t * t; 87 | } 88 | 89 | if (t < 2 / 2.75) { 90 | t -= 1.5 / 2.75; 91 | return 7.5625 * t * t + 0.75; 92 | } 93 | 94 | if (t < 2.5 / 2.75) { 95 | t -= 2.25 / 2.75; 96 | return 7.5625 * t * t + 0.9375; 97 | } 98 | 99 | t -= 2.625 / 2.75; 100 | return 7.5625 * t * t + 0.984375; 101 | }; 102 | 103 | static bezier( 104 | x1: number, 105 | y1: number, 106 | x2: number, 107 | y2: number, 108 | epsilon?: ?number 109 | ): (t: number) => number { 110 | if (epsilon === undefined) { 111 | // epsilon determines the precision of the solved values 112 | // a good approximation is: 113 | var duration = 500; // duration of animation in milliseconds. 114 | epsilon = (1000 / 60 / duration) / 4; 115 | } 116 | 117 | return bezier(x1, y1, x2, y2, epsilon); 118 | } 119 | 120 | static in( 121 | easing: (t: number) => number 122 | ): (t: number) => number { 123 | return easing; 124 | } 125 | 126 | static out( 127 | easing: (t: number) => number 128 | ): (t: number) => number { 129 | return (t) => 1 - easing(1 - t); 130 | } 131 | 132 | static inOut( 133 | easing: (t: number) => number 134 | ): (t: number) => number { 135 | return (t) => { 136 | if (t < 0.5) { 137 | return easing(t * 2) / 2; 138 | } 139 | return 1 - easing((1 - t) * 2) / 2; 140 | }; 141 | } 142 | } 143 | 144 | var ease = Easing.bezier(0.42, 0, 1, 1); 145 | 146 | module.exports = Easing; 147 | -------------------------------------------------------------------------------- /src/Interpolation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule Interpolation 10 | * @flow 11 | */ 12 | // 'use strict'; 13 | 14 | var linear = (t) => t; 15 | 16 | /** 17 | * Very handy helper to map input ranges to output ranges with an easing 18 | * function and custom behavior outside of the ranges. 19 | */ 20 | class Interpolation { 21 | static create(config: InterpolationConfigType): (input: number) => number | string { 22 | 23 | if (config.outputRange && typeof config.outputRange[0] === 'string') { 24 | return createInterpolationFromStringOutputRange(config); 25 | } 26 | 27 | var outputRange: Array = (config.outputRange: any); 28 | checkInfiniteRange('outputRange', outputRange); 29 | 30 | var inputRange = config.inputRange; 31 | checkInfiniteRange('inputRange', inputRange); 32 | checkValidInputRange(inputRange); 33 | 34 | invariant( 35 | inputRange.length === outputRange.length, 36 | 'inputRange (' + inputRange.length + ') and outputRange (' + 37 | outputRange.length + ') must have the same length' 38 | ); 39 | 40 | var easing = config.easing || linear; 41 | 42 | var extrapolateLeft: ExtrapolateType = 'extend'; 43 | if (config.extrapolateLeft !== undefined) { 44 | extrapolateLeft = config.extrapolateLeft; 45 | } else if (config.extrapolate !== undefined) { 46 | extrapolateLeft = config.extrapolate; 47 | } 48 | 49 | var extrapolateRight: ExtrapolateType = 'extend'; 50 | if (config.extrapolateRight !== undefined) { 51 | extrapolateRight = config.extrapolateRight; 52 | } else if (config.extrapolate !== undefined) { 53 | extrapolateRight = config.extrapolate; 54 | } 55 | 56 | return (input) => { 57 | invariant( 58 | typeof input === 'number', 59 | 'Cannot interpolation an input which is not a number' 60 | ); 61 | 62 | var range = findRange(input, inputRange); 63 | return interpolate( 64 | input, 65 | inputRange[range], 66 | inputRange[range + 1], 67 | outputRange[range], 68 | outputRange[range + 1], 69 | easing, 70 | extrapolateLeft, 71 | extrapolateRight 72 | ); 73 | }; 74 | } 75 | } 76 | 77 | function interpolate( 78 | input: number, 79 | inputMin: number, 80 | inputMax: number, 81 | outputMin: number, 82 | outputMax: number, 83 | easing: ((input: number) => number), 84 | extrapolateLeft: ExtrapolateType, 85 | extrapolateRight: ExtrapolateType 86 | ) { 87 | var result = input; 88 | 89 | // Extrapolate 90 | if (result < inputMin) { 91 | if (extrapolateLeft === 'identity') { 92 | return result; 93 | } else if (extrapolateLeft === 'clamp') { 94 | result = inputMin; 95 | } else if (extrapolateLeft === 'extend') { 96 | // noop 97 | } 98 | } 99 | 100 | if (result > inputMax) { 101 | if (extrapolateRight === 'identity') { 102 | return result; 103 | } else if (extrapolateRight === 'clamp') { 104 | result = inputMax; 105 | } else if (extrapolateRight === 'extend') { 106 | // noop 107 | } 108 | } 109 | 110 | if (outputMin === outputMax) { 111 | return outputMin; 112 | } 113 | 114 | if (inputMin === inputMax) { 115 | if (input <= inputMin) { 116 | return outputMin; 117 | } 118 | return outputMax; 119 | } 120 | 121 | // Input Range 122 | if (inputMin === -Infinity) { 123 | result = -result; 124 | } else if (inputMax === Infinity) { 125 | result = result - inputMin; 126 | } else { 127 | result = (result - inputMin) / (inputMax - inputMin); 128 | } 129 | 130 | // Easing 131 | result = easing(result); 132 | 133 | // Output Range 134 | if (outputMin === -Infinity) { 135 | result = -result; 136 | } else if (outputMax === Infinity) { 137 | result = result + outputMin; 138 | } else { 139 | result = result * (outputMax - outputMin) + outputMin; 140 | } 141 | 142 | return result; 143 | } 144 | 145 | var stringShapeRegex = /[0-9\.-]+/g; 146 | 147 | /** 148 | * Supports string shapes by extracting numbers so new values can be computed, 149 | * and recombines those values into new strings of the same shape. Supports 150 | * things like: 151 | * 152 | * rgba(123, 42, 99, 0.36) // colors 153 | * -45deg // values with units 154 | */ 155 | function createInterpolationFromStringOutputRange( 156 | config: InterpolationConfigType 157 | ): (input: number) => string { 158 | var outputRange: Array = (config.outputRange: any); 159 | invariant(outputRange.length >= 2, 'Bad output range'); 160 | checkPattern(outputRange); 161 | 162 | // ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.5)'] 163 | // -> 164 | // [ 165 | // [0, 50], 166 | // [100, 150], 167 | // [200, 250], 168 | // [0, 0.5], 169 | // ] 170 | var outputRanges = outputRange[0].match(stringShapeRegex).map(() => []); 171 | outputRange.forEach(value => { 172 | value.match(stringShapeRegex).forEach((number, i) => { 173 | outputRanges[i].push(+number); 174 | }); 175 | }); 176 | 177 | var interpolations = outputRange[0].match(stringShapeRegex).map((value, i) => { 178 | return Interpolation.create({ 179 | ...config, 180 | outputRange: outputRanges[i], 181 | }); 182 | }); 183 | 184 | return (input) => { 185 | var i = 0; 186 | // 'rgba(0, 100, 200, 0)' 187 | // -> 188 | // 'rgba(${interpolations[0](input)}, ${interpolations[1](input)}, ...' 189 | return outputRange[0].replace(stringShapeRegex, () => { 190 | return String(interpolations[i++](input)); 191 | }); 192 | }; 193 | } 194 | 195 | function checkPattern(arr: Array) { 196 | var pattern = arr[0].replace(stringShapeRegex, ''); 197 | for (var i = 1; i < arr.length; ++i) { 198 | invariant( 199 | pattern === arr[i].replace(stringShapeRegex, ''), 200 | 'invalid pattern ' + arr[0] + ' and ' + arr[i] 201 | ); 202 | } 203 | } 204 | 205 | function findRange(input: number, inputRange: Array) { 206 | for (var i = 1; i < inputRange.length - 1; ++i) { 207 | if (inputRange[i] >= input) { 208 | break; 209 | } 210 | } 211 | return i - 1; 212 | } 213 | 214 | function checkValidInputRange(arr: Array) { 215 | invariant(arr.length >= 2, 'inputRange must have at least 2 elements'); 216 | for (var i = 1; i < arr.length; ++i) { 217 | invariant( 218 | arr[i] >= arr[i - 1], 219 | /* $FlowFixMe(>=0.13.0) - In the addition expression below this comment, 220 | * one or both of the operands may be something that doesn't cleanly 221 | * convert to a string, like undefined, null, and object, etc. If you really 222 | * mean this implicit string conversion, you can do something like 223 | * String(myThing) 224 | */ 225 | 'inputRange must be monolithically increasing ' + arr 226 | ); 227 | } 228 | } 229 | 230 | function checkInfiniteRange(name: string, arr: Array) { 231 | invariant(arr.length >= 2, name + ' must have at least 2 elements'); 232 | invariant( 233 | arr.length !== 2 || arr[0] !== -Infinity || arr[1] !== Infinity, 234 | /* $FlowFixMe(>=0.13.0) - In the addition expression below this comment, 235 | * one or both of the operands may be something that doesn't cleanly convert 236 | * to a string, like undefined, null, and object, etc. If you really mean 237 | * this implicit string conversion, you can do something like 238 | * String(myThing) 239 | */ 240 | name + 'cannot be ]-infinity;+infinity[ ' + arr 241 | ); 242 | } 243 | 244 | module.exports = Interpolation; 245 | -------------------------------------------------------------------------------- /src/bezier.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = 4 | function(x1, y1, x2, y2, epsilon) { 5 | var curveX = function(t) { 6 | var v = 1 - t; 7 | return 3 * v * v * t * x1 + 3 * v * t * t * x2 + t * t * t; 8 | }; 9 | var curveY = function(t) { 10 | var v = 1 - t; 11 | return 3 * v * v * t * y1 + 3 * v * t * t * y2 + t * t * t; 12 | }; 13 | var derivativeCurveX = function(t) { 14 | var v = 1 - t; 15 | return 3 * (2 * (t - 1) * t + v * v) * x1 + 3 * (-t * t * t + 2 * v * t) * x2; 16 | }; 17 | return function(t) { 18 | var x = t, 19 | t0, t1, t2, x2, d2, i; 20 | for (t2 = x, i = 0; i < 8; i++) { 21 | x2 = curveX(t2) - x; 22 | if (Math.abs(x2) < epsilon) return curveY(t2); 23 | d2 = derivativeCurveX(t2); 24 | if (Math.abs(d2) < 1e-6) break; 25 | t2 = t2 - x2 / d2; 26 | } 27 | t0 = 0, t1 = 1, t2 = x; 28 | if (t2 < t0) return curveY(t0); 29 | if (t2 > t1) return curveY(t1); 30 | while (t0 < t1) { 31 | x2 = curveX(t2); 32 | if (Math.abs(x2 - x) < epsilon) return curveY(t2); 33 | if (x > x2) t0 = t2; 34 | else t1 = t2; 35 | t2 = (t1 - t0) * .5 + t0; 36 | } 37 | return curveY(t2); 38 | }; 39 | }; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./Animated'); -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack'; 2 | 3 | let config = { 4 | devtool: 'source-map', 5 | target: 'web', 6 | entry: { 7 | example: ['./example/index.js'] 8 | }, 9 | output: { 10 | path: __dirname, 11 | filename: 'example/bundle.js', 12 | publicPath: '/' 13 | }, 14 | module: { 15 | loaders: [{ 16 | test: /\.js$/, 17 | exclude: /node_modules/, 18 | loaders: ['babel-loader'] 19 | }] 20 | }, 21 | resolve: { 22 | extensions: ['', '.js', '.jsx'] 23 | }, 24 | plugins: [] 25 | }; 26 | 27 | if(process.env.HOT){ 28 | config = { 29 | ...config, 30 | devtool: 'eval-source-map', 31 | entry: Object.keys(config.entry).reduce((o, key) => ({...o, [key]: [ 32 | 'webpack-dev-server/client?http://localhost:3000', // WebpackDevServer host and port 33 | 'webpack/hot/only-dev-server' 34 | ].concat(config.entry[key])}), {}), 35 | module: {...config.module, 36 | loaders: [{ 37 | ...config.module.loaders[0], 38 | loaders: [ 39 | 'react-hot' 40 | ].concat(config.module.loaders[0].loaders) 41 | }] 42 | }, 43 | plugins: [ 44 | new webpack.HotModuleReplacementPlugin(), 45 | new webpack.NoErrorsPlugin() 46 | ].concat(config.plugins) 47 | }; 48 | } 49 | 50 | module.exports = config; 51 | --------------------------------------------------------------------------------