Spaces:
Runtime error
Runtime error
| ; | |
| (function () { | |
| var rebound = {}; | |
| var util = rebound.util = {}; | |
| var concat = Array.prototype.concat; | |
| var slice = Array.prototype.slice; | |
| // Bind a function to a context object. | |
| util.bind = function bind(func, context) { | |
| var args = slice.call(arguments, 2); | |
| return function () { | |
| func.apply(context, concat.call(args, slice.call(arguments))); | |
| }; | |
| }; | |
| // Add all the properties in the source to the target. | |
| util.extend = function extend(target, source) { | |
| for (var key in source) { | |
| if (source.hasOwnProperty(key)) { | |
| target[key] = source[key]; | |
| } | |
| } | |
| }; | |
| // SpringSystem | |
| // ------------ | |
| // **SpringSystem** is a set of Springs that all run on the same physics | |
| // timing loop. To get started with a Rebound animation you first | |
| // create a new SpringSystem and then add springs to it. | |
| var SpringSystem = rebound.SpringSystem = function SpringSystem(looper) { | |
| this._springRegistry = {}; | |
| this._activeSprings = []; | |
| this.listeners = []; | |
| this._idleSpringIndices = []; | |
| this.looper = looper || new AnimationLooper(); | |
| this.looper.springSystem = this; | |
| }; | |
| util.extend(SpringSystem.prototype, { | |
| _springRegistry: null, | |
| _isIdle: true, | |
| _lastTimeMillis: -1, | |
| _activeSprings: null, | |
| listeners: null, | |
| _idleSpringIndices: null, | |
| // A SpringSystem is iterated by a looper. The looper is responsible | |
| // for executing each frame as the SpringSystem is resolved to idle. | |
| // There are three types of Loopers described below AnimationLooper, | |
| // SimulationLooper, and SteppingSimulationLooper. AnimationLooper is | |
| // the default as it is the most useful for common UI animations. | |
| setLooper: function setLooper(looper) { | |
| this.looper = looper; | |
| looper.springSystem = this; | |
| }, | |
| // Add a new spring to this SpringSystem. This Spring will now be solved for | |
| // during the physics iteration loop. By default the spring will use the | |
| // default Origami spring config with 40 tension and 7 friction, but you can | |
| // also provide your own values here. | |
| createSpring: function createSpring(tension, friction) { | |
| var springConfig; | |
| if (tension === undefined || friction === undefined) { | |
| springConfig = SpringConfig.DEFAULT_ORIGAMI_SPRING_CONFIG; | |
| } else { | |
| springConfig = SpringConfig.fromOrigamiTensionAndFriction(tension, friction); | |
| } | |
| return this.createSpringWithConfig(springConfig); | |
| }, | |
| // Add a spring with a specified bounciness and speed. To replicate Origami | |
| // compositions based on PopAnimation patches, use this factory method to | |
| // create matching springs. | |
| createSpringWithBouncinessAndSpeed: function createSpringWithBouncinessAndSpeed(bounciness, speed) { | |
| var springConfig; | |
| if (bounciness === undefined || speed === undefined) { | |
| springConfig = SpringConfig.DEFAULT_ORIGAMI_SPRING_CONFIG; | |
| } else { | |
| springConfig = SpringConfig.fromBouncinessAndSpeed(bounciness, speed); | |
| } | |
| return this.createSpringWithConfig(springConfig); | |
| }, | |
| // Add a spring with the provided SpringConfig. | |
| createSpringWithConfig: function createSpringWithConfig(springConfig) { | |
| var spring = new Spring(this); | |
| this.registerSpring(spring); | |
| spring.setSpringConfig(springConfig); | |
| return spring; | |
| }, | |
| // You can check if a SpringSystem is idle or active by calling | |
| // getIsIdle. If all of the Springs in the SpringSystem are at rest, | |
| // i.e. the physics forces have reached equilibrium, then this | |
| // method will return true. | |
| getIsIdle: function getIsIdle() { | |
| return this._isIdle; | |
| }, | |
| // Retrieve a specific Spring from the SpringSystem by id. This | |
| // can be useful for inspecting the state of a spring before | |
| // or after an integration loop in the SpringSystem executes. | |
| getSpringById: function getSpringById(id) { | |
| return this._springRegistry[id]; | |
| }, | |
| // Get a listing of all the springs registered with this | |
| // SpringSystem. | |
| getAllSprings: function getAllSprings() { | |
| var vals = []; | |
| for (var id in this._springRegistry) { | |
| if (this._springRegistry.hasOwnProperty(id)) { | |
| vals.push(this._springRegistry[id]); | |
| } | |
| } | |
| return vals; | |
| }, | |
| // registerSpring is called automatically as soon as you create | |
| // a Spring with SpringSystem#createSpring. This method sets the | |
| // spring up in the registry so that it can be solved in the | |
| // solver loop. | |
| registerSpring: function registerSpring(spring) { | |
| this._springRegistry[spring.getId()] = spring; | |
| }, | |
| // Deregister a spring with this SpringSystem. The SpringSystem will | |
| // no longer consider this Spring during its integration loop once | |
| // this is called. This is normally done automatically for you when | |
| // you call Spring#destroy. | |
| deregisterSpring: function deregisterSpring(spring) { | |
| removeFirst(this._activeSprings, spring); | |
| delete this._springRegistry[spring.getId()]; | |
| }, | |
| advance: function advance(time, deltaTime) { | |
| while (this._idleSpringIndices.length > 0) { | |
| this._idleSpringIndices.pop(); | |
| }for (var i = 0, len = this._activeSprings.length; i < len; i++) { | |
| var spring = this._activeSprings[i]; | |
| if (spring.systemShouldAdvance()) { | |
| spring.advance(time / 1000.0, deltaTime / 1000.0); | |
| } else { | |
| this._idleSpringIndices.push(this._activeSprings.indexOf(spring)); | |
| } | |
| } | |
| while (this._idleSpringIndices.length > 0) { | |
| var idx = this._idleSpringIndices.pop(); | |
| idx >= 0 && this._activeSprings.splice(idx, 1); | |
| } | |
| }, | |
| // This is our main solver loop called to move the simulation | |
| // forward through time. Before each pass in the solver loop | |
| // onBeforeIntegrate is called on an any listeners that have | |
| // registered themeselves with the SpringSystem. This gives you | |
| // an opportunity to apply any constraints or adjustments to | |
| // the springs that should be enforced before each iteration | |
| // loop. Next the advance method is called to move each Spring in | |
| // the systemShouldAdvance forward to the current time. After the | |
| // integration step runs in advance, onAfterIntegrate is called | |
| // on any listeners that have registered themselves with the | |
| // SpringSystem. This gives you an opportunity to run any post | |
| // integration constraints or adjustments on the Springs in the | |
| // SpringSystem. | |
| loop: function loop(currentTimeMillis) { | |
| var listener; | |
| if (this._lastTimeMillis === -1) { | |
| this._lastTimeMillis = currentTimeMillis - 1; | |
| } | |
| var ellapsedMillis = currentTimeMillis - this._lastTimeMillis; | |
| this._lastTimeMillis = currentTimeMillis; | |
| var i = 0, | |
| len = this.listeners.length; | |
| for (i = 0; i < len; i++) { | |
| listener = this.listeners[i]; | |
| listener.onBeforeIntegrate && listener.onBeforeIntegrate(this); | |
| } | |
| this.advance(currentTimeMillis, ellapsedMillis); | |
| if (this._activeSprings.length === 0) { | |
| this._isIdle = true; | |
| this._lastTimeMillis = -1; | |
| } | |
| for (i = 0; i < len; i++) { | |
| listener = this.listeners[i]; | |
| listener.onAfterIntegrate && listener.onAfterIntegrate(this); | |
| } | |
| if (!this._isIdle) { | |
| this.looper.run(); | |
| } | |
| }, | |
| // activateSpring is used to notify the SpringSystem that a Spring | |
| // has become displaced. The system responds by starting its solver | |
| // loop up if it is currently idle. | |
| activateSpring: function activateSpring(springId) { | |
| var spring = this._springRegistry[springId]; | |
| if (this._activeSprings.indexOf(spring) == -1) { | |
| this._activeSprings.push(spring); | |
| } | |
| if (this.getIsIdle()) { | |
| this._isIdle = false; | |
| this.looper.run(); | |
| } | |
| }, | |
| // Add a listener to the SpringSystem so that you can receive | |
| // before/after integration notifications allowing Springs to be | |
| // constrained or adjusted. | |
| addListener: function addListener(listener) { | |
| this.listeners.push(listener); | |
| }, | |
| // Remove a previously added listener on the SpringSystem. | |
| removeListener: function removeListener(listener) { | |
| removeFirst(this.listeners, listener); | |
| }, | |
| // Remove all previously added listeners on the SpringSystem. | |
| removeAllListeners: function removeAllListeners() { | |
| this.listeners = []; | |
| } | |
| }); | |
| // Spring | |
| // ------ | |
| // **Spring** provides a model of a classical spring acting to | |
| // resolve a body to equilibrium. Springs have configurable | |
| // tension which is a force multipler on the displacement of the | |
| // spring from its rest point or `endValue` as defined by [Hooke's | |
| // law](http://en.wikipedia.org/wiki/Hooke's_law). Springs also have | |
| // configurable friction, which ensures that they do not oscillate | |
| // infinitely. When a Spring is displaced by updating it's resting | |
| // or `currentValue`, the SpringSystems that contain that Spring | |
| // will automatically start looping to solve for equilibrium. As each | |
| // timestep passes, `SpringListener` objects attached to the Spring | |
| // will be notified of the updates providing a way to drive an | |
| // animation off of the spring's resolution curve. | |
| var Spring = rebound.Spring = function Spring(springSystem) { | |
| this._id = 's' + Spring._ID++; | |
| this._springSystem = springSystem; | |
| this.listeners = []; | |
| this._currentState = new PhysicsState(); | |
| this._previousState = new PhysicsState(); | |
| this._tempState = new PhysicsState(); | |
| }; | |
| util.extend(Spring, { | |
| _ID: 0, | |
| MAX_DELTA_TIME_SEC: 0.064, | |
| SOLVER_TIMESTEP_SEC: 0.001 | |
| }); | |
| util.extend(Spring.prototype, { | |
| _id: 0, | |
| _springConfig: null, | |
| _overshootClampingEnabled: false, | |
| _currentState: null, | |
| _previousState: null, | |
| _tempState: null, | |
| _startValue: 0, | |
| _endValue: 0, | |
| _wasAtRest: true, | |
| _restSpeedThreshold: 0.001, | |
| _displacementFromRestThreshold: 0.001, | |
| listeners: null, | |
| _timeAccumulator: 0, | |
| _springSystem: null, | |
| // Remove a Spring from simulation and clear its listeners. | |
| destroy: function destroy() { | |
| this.listeners = []; | |
| this.frames = []; | |
| this._springSystem.deregisterSpring(this); | |
| }, | |
| // Get the id of the spring, which can be used to retrieve it from | |
| // the SpringSystems it participates in later. | |
| getId: function getId() { | |
| return this._id; | |
| }, | |
| // Set the configuration values for this Spring. A SpringConfig | |
| // contains the tension and friction values used to solve for the | |
| // equilibrium of the Spring in the physics loop. | |
| setSpringConfig: function setSpringConfig(springConfig) { | |
| this._springConfig = springConfig; | |
| return this; | |
| }, | |
| // Retrieve the SpringConfig used by this Spring. | |
| getSpringConfig: function getSpringConfig() { | |
| return this._springConfig; | |
| }, | |
| // Set the current position of this Spring. Listeners will be updated | |
| // with this value immediately. If the rest or `endValue` is not | |
| // updated to match this value, then the spring will be dispalced and | |
| // the SpringSystem will start to loop to restore the spring to the | |
| // `endValue`. | |
| // | |
| // A common pattern is to move a Spring around without animation by | |
| // calling. | |
| // | |
| // ``` | |
| // spring.setCurrentValue(n).setAtRest(); | |
| // ``` | |
| // | |
| // This moves the Spring to a new position `n`, sets the endValue | |
| // to `n`, and removes any velocity from the `Spring`. By doing | |
| // this you can allow the `SpringListener` to manage the position | |
| // of UI elements attached to the spring even when moving without | |
| // animation. For example, when dragging an element you can | |
| // update the position of an attached view through a spring | |
| // by calling `spring.setCurrentValue(x)`. When | |
| // the gesture ends you can update the Springs | |
| // velocity and endValue | |
| // `spring.setVelocity(gestureEndVelocity).setEndValue(flingTarget)` | |
| // to cause it to naturally animate the UI element to the resting | |
| // position taking into account existing velocity. The codepaths for | |
| // synchronous movement and spring driven animation can | |
| // be unified using this technique. | |
| setCurrentValue: function setCurrentValue(currentValue, skipSetAtRest) { | |
| this._startValue = currentValue; | |
| this._currentState.position = currentValue; | |
| if (!skipSetAtRest) { | |
| this.setAtRest(); | |
| } | |
| this.notifyPositionUpdated(false, false); | |
| return this; | |
| }, | |
| // Get the position that the most recent animation started at. This | |
| // can be useful for determining the number off oscillations that | |
| // have occurred. | |
| getStartValue: function getStartValue() { | |
| return this._startValue; | |
| }, | |
| // Retrieve the current value of the Spring. | |
| getCurrentValue: function getCurrentValue() { | |
| return this._currentState.position; | |
| }, | |
| // Get the absolute distance of the Spring from it's resting endValue | |
| // position. | |
| getCurrentDisplacementDistance: function getCurrentDisplacementDistance() { | |
| return this.getDisplacementDistanceForState(this._currentState); | |
| }, | |
| getDisplacementDistanceForState: function getDisplacementDistanceForState(state) { | |
| return Math.abs(this._endValue - state.position); | |
| }, | |
| // Set the endValue or resting position of the spring. If this | |
| // value is different than the current value, the SpringSystem will | |
| // be notified and will begin running its solver loop to resolve | |
| // the Spring to equilibrium. Any listeners that are registered | |
| // for onSpringEndStateChange will also be notified of this update | |
| // immediately. | |
| setEndValue: function setEndValue(endValue) { | |
| if (this._endValue == endValue && this.isAtRest()) { | |
| return this; | |
| } | |
| this._startValue = this.getCurrentValue(); | |
| this._endValue = endValue; | |
| this._springSystem.activateSpring(this.getId()); | |
| for (var i = 0, len = this.listeners.length; i < len; i++) { | |
| var listener = this.listeners[i]; | |
| var onChange = listener.onSpringEndStateChange; | |
| onChange && onChange(this); | |
| } | |
| return this; | |
| }, | |
| // Retrieve the endValue or resting position of this spring. | |
| getEndValue: function getEndValue() { | |
| return this._endValue; | |
| }, | |
| // Set the current velocity of the Spring. As previously mentioned, | |
| // this can be useful when you are performing a direct manipulation | |
| // gesture. When a UI element is released you may call setVelocity | |
| // on its animation Spring so that the Spring continues with the | |
| // same velocity as the gesture ended with. The friction, tension, | |
| // and displacement of the Spring will then govern its motion to | |
| // return to rest on a natural feeling curve. | |
| setVelocity: function setVelocity(velocity) { | |
| if (velocity === this._currentState.velocity) { | |
| return this; | |
| } | |
| this._currentState.velocity = velocity; | |
| this._springSystem.activateSpring(this.getId()); | |
| return this; | |
| }, | |
| // Get the current velocity of the Spring. | |
| getVelocity: function getVelocity() { | |
| return this._currentState.velocity; | |
| }, | |
| // Set a threshold value for the movement speed of the Spring below | |
| // which it will be considered to be not moving or resting. | |
| setRestSpeedThreshold: function setRestSpeedThreshold(restSpeedThreshold) { | |
| this._restSpeedThreshold = restSpeedThreshold; | |
| return this; | |
| }, | |
| // Retrieve the rest speed threshold for this Spring. | |
| getRestSpeedThreshold: function getRestSpeedThreshold() { | |
| return this._restSpeedThreshold; | |
| }, | |
| // Set a threshold value for displacement below which the Spring | |
| // will be considered to be not displaced i.e. at its resting | |
| // `endValue`. | |
| setRestDisplacementThreshold: function setRestDisplacementThreshold(displacementFromRestThreshold) { | |
| this._displacementFromRestThreshold = displacementFromRestThreshold; | |
| }, | |
| // Retrieve the rest displacement threshold for this spring. | |
| getRestDisplacementThreshold: function getRestDisplacementThreshold() { | |
| return this._displacementFromRestThreshold; | |
| }, | |
| // Enable overshoot clamping. This means that the Spring will stop | |
| // immediately when it reaches its resting position regardless of | |
| // any existing momentum it may have. This can be useful for certain | |
| // types of animations that should not oscillate such as a scale | |
| // down to 0 or alpha fade. | |
| setOvershootClampingEnabled: function setOvershootClampingEnabled(enabled) { | |
| this._overshootClampingEnabled = enabled; | |
| return this; | |
| }, | |
| // Check if overshoot clamping is enabled for this spring. | |
| isOvershootClampingEnabled: function isOvershootClampingEnabled() { | |
| return this._overshootClampingEnabled; | |
| }, | |
| // Check if the Spring has gone past its end point by comparing | |
| // the direction it was moving in when it started to the current | |
| // position and end value. | |
| isOvershooting: function isOvershooting() { | |
| var start = this._startValue; | |
| var end = this._endValue; | |
| return this._springConfig.tension > 0 && (start < end && this.getCurrentValue() > end || start > end && this.getCurrentValue() < end); | |
| }, | |
| // Spring.advance is the main solver method for the Spring. It takes | |
| // the current time and delta since the last time step and performs | |
| // an RK4 integration to get the new position and velocity state | |
| // for the Spring based on the tension, friction, velocity, and | |
| // displacement of the Spring. | |
| advance: function advance(time, realDeltaTime) { | |
| var isAtRest = this.isAtRest(); | |
| if (isAtRest && this._wasAtRest) { | |
| return; | |
| } | |
| var adjustedDeltaTime = realDeltaTime; | |
| if (realDeltaTime > Spring.MAX_DELTA_TIME_SEC) { | |
| adjustedDeltaTime = Spring.MAX_DELTA_TIME_SEC; | |
| } | |
| this._timeAccumulator += adjustedDeltaTime; | |
| var tension = this._springConfig.tension, | |
| friction = this._springConfig.friction, | |
| position = this._currentState.position, | |
| velocity = this._currentState.velocity, | |
| tempPosition = this._tempState.position, | |
| tempVelocity = this._tempState.velocity, | |
| aVelocity, | |
| aAcceleration, | |
| bVelocity, | |
| bAcceleration, | |
| cVelocity, | |
| cAcceleration, | |
| dVelocity, | |
| dAcceleration, | |
| dxdt, | |
| dvdt; | |
| while (this._timeAccumulator >= Spring.SOLVER_TIMESTEP_SEC) { | |
| this._timeAccumulator -= Spring.SOLVER_TIMESTEP_SEC; | |
| if (this._timeAccumulator < Spring.SOLVER_TIMESTEP_SEC) { | |
| this._previousState.position = position; | |
| this._previousState.velocity = velocity; | |
| } | |
| aVelocity = velocity; | |
| aAcceleration = tension * (this._endValue - tempPosition) - friction * velocity; | |
| tempPosition = position + aVelocity * Spring.SOLVER_TIMESTEP_SEC * 0.5; | |
| tempVelocity = velocity + aAcceleration * Spring.SOLVER_TIMESTEP_SEC * 0.5; | |
| bVelocity = tempVelocity; | |
| bAcceleration = tension * (this._endValue - tempPosition) - friction * tempVelocity; | |
| tempPosition = position + bVelocity * Spring.SOLVER_TIMESTEP_SEC * 0.5; | |
| tempVelocity = velocity + bAcceleration * Spring.SOLVER_TIMESTEP_SEC * 0.5; | |
| cVelocity = tempVelocity; | |
| cAcceleration = tension * (this._endValue - tempPosition) - friction * tempVelocity; | |
| tempPosition = position + cVelocity * Spring.SOLVER_TIMESTEP_SEC * 0.5; | |
| tempVelocity = velocity + cAcceleration * Spring.SOLVER_TIMESTEP_SEC * 0.5; | |
| dVelocity = tempVelocity; | |
| dAcceleration = tension * (this._endValue - tempPosition) - friction * tempVelocity; | |
| dxdt = 1.0 / 6.0 * (aVelocity + 2.0 * (bVelocity + cVelocity) + dVelocity); | |
| dvdt = 1.0 / 6.0 * (aAcceleration + 2.0 * (bAcceleration + cAcceleration) + dAcceleration); | |
| position += dxdt * Spring.SOLVER_TIMESTEP_SEC; | |
| velocity += dvdt * Spring.SOLVER_TIMESTEP_SEC; | |
| } | |
| this._tempState.position = tempPosition; | |
| this._tempState.velocity = tempVelocity; | |
| this._currentState.position = position; | |
| this._currentState.velocity = velocity; | |
| if (this._timeAccumulator > 0) { | |
| this._interpolate(this._timeAccumulator / Spring.SOLVER_TIMESTEP_SEC); | |
| } | |
| if (this.isAtRest() || this._overshootClampingEnabled && this.isOvershooting()) { | |
| if (this._springConfig.tension > 0) { | |
| this._startValue = this._endValue; | |
| this._currentState.position = this._endValue; | |
| } else { | |
| this._endValue = this._currentState.position; | |
| this._startValue = this._endValue; | |
| } | |
| this.setVelocity(0); | |
| isAtRest = true; | |
| } | |
| var notifyActivate = false; | |
| if (this._wasAtRest) { | |
| this._wasAtRest = false; | |
| notifyActivate = true; | |
| } | |
| var notifyAtRest = false; | |
| if (isAtRest) { | |
| this._wasAtRest = true; | |
| notifyAtRest = true; | |
| } | |
| this.notifyPositionUpdated(notifyActivate, notifyAtRest); | |
| }, | |
| notifyPositionUpdated: function notifyPositionUpdated(notifyActivate, notifyAtRest) { | |
| for (var i = 0, len = this.listeners.length; i < len; i++) { | |
| var listener = this.listeners[i]; | |
| if (notifyActivate && listener.onSpringActivate) { | |
| listener.onSpringActivate(this); | |
| } | |
| if (listener.onSpringUpdate) { | |
| listener.onSpringUpdate(this); | |
| } | |
| if (notifyAtRest && listener.onSpringAtRest) { | |
| listener.onSpringAtRest(this); | |
| } | |
| } | |
| }, | |
| // Check if the SpringSystem should advance. Springs are advanced | |
| // a final frame after they reach equilibrium to ensure that the | |
| // currentValue is exactly the requested endValue regardless of the | |
| // displacement threshold. | |
| systemShouldAdvance: function systemShouldAdvance() { | |
| return !this.isAtRest() || !this.wasAtRest(); | |
| }, | |
| wasAtRest: function wasAtRest() { | |
| return this._wasAtRest; | |
| }, | |
| // Check if the Spring is atRest meaning that it's currentValue and | |
| // endValue are the same and that it has no velocity. The previously | |
| // described thresholds for speed and displacement define the bounds | |
| // of this equivalence check. If the Spring has 0 tension, then it will | |
| // be considered at rest whenever its absolute velocity drops below the | |
| // restSpeedThreshold. | |
| isAtRest: function isAtRest() { | |
| return Math.abs(this._currentState.velocity) < this._restSpeedThreshold && (this.getDisplacementDistanceForState(this._currentState) <= this._displacementFromRestThreshold || this._springConfig.tension === 0); | |
| }, | |
| // Force the spring to be at rest at its current position. As | |
| // described in the documentation for setCurrentValue, this method | |
| // makes it easy to do synchronous non-animated updates to ui | |
| // elements that are attached to springs via SpringListeners. | |
| setAtRest: function setAtRest() { | |
| this._endValue = this._currentState.position; | |
| this._tempState.position = this._currentState.position; | |
| this._currentState.velocity = 0; | |
| return this; | |
| }, | |
| _interpolate: function _interpolate(alpha) { | |
| this._currentState.position = this._currentState.position * alpha + this._previousState.position * (1 - alpha); | |
| this._currentState.velocity = this._currentState.velocity * alpha + this._previousState.velocity * (1 - alpha); | |
| }, | |
| getListeners: function getListeners() { | |
| return this.listeners; | |
| }, | |
| addListener: function addListener(newListener) { | |
| this.listeners.push(newListener); | |
| return this; | |
| }, | |
| removeListener: function removeListener(listenerToRemove) { | |
| removeFirst(this.listeners, listenerToRemove); | |
| return this; | |
| }, | |
| removeAllListeners: function removeAllListeners() { | |
| this.listeners = []; | |
| return this; | |
| }, | |
| currentValueIsApproximately: function currentValueIsApproximately(value) { | |
| return Math.abs(this.getCurrentValue() - value) <= this.getRestDisplacementThreshold(); | |
| } | |
| }); | |
| // PhysicsState | |
| // ------------ | |
| // **PhysicsState** consists of a position and velocity. A Spring uses | |
| // this internally to keep track of its current and prior position and | |
| // velocity values. | |
| var PhysicsState = function PhysicsState() {}; | |
| util.extend(PhysicsState.prototype, { | |
| position: 0, | |
| velocity: 0 | |
| }); | |
| // SpringConfig | |
| // ------------ | |
| // **SpringConfig** maintains a set of tension and friction constants | |
| // for a Spring. You can use fromOrigamiTensionAndFriction to convert | |
| // values from the [Origami](http://facebook.github.io/origami/) | |
| // design tool directly to Rebound spring constants. | |
| var SpringConfig = rebound.SpringConfig = function SpringConfig(tension, friction) { | |
| this.tension = tension; | |
| this.friction = friction; | |
| }; | |
| // Loopers | |
| // ------- | |
| // **AnimationLooper** plays each frame of the SpringSystem on animation | |
| // timing loop. This is the default type of looper for a new spring system | |
| // as it is the most common when developing UI. | |
| var AnimationLooper = rebound.AnimationLooper = function AnimationLooper() { | |
| this.springSystem = null; | |
| var _this = this; | |
| var _run = function _run() { | |
| _this.springSystem.loop(Date.now()); | |
| }; | |
| this.run = function () { | |
| util.onFrame(_run); | |
| }; | |
| }; | |
| // **SimulationLooper** resolves the SpringSystem to a resting state in a | |
| // tight and blocking loop. This is useful for synchronously generating | |
| // pre-recorded animations that can then be played on a timing loop later. | |
| // Sometimes this lead to better performance to pre-record a single spring | |
| // curve and use it to drive many animations; however, it can make dynamic | |
| // response to user input a bit trickier to implement. | |
| rebound.SimulationLooper = function SimulationLooper(timestep) { | |
| this.springSystem = null; | |
| var time = 0; | |
| var running = false; | |
| timestep = timestep || 16.667; | |
| this.run = function () { | |
| if (running) { | |
| return; | |
| } | |
| running = true; | |
| while (!this.springSystem.getIsIdle()) { | |
| this.springSystem.loop(time += timestep); | |
| } | |
| running = false; | |
| }; | |
| }; | |
| // **SteppingSimulationLooper** resolves the SpringSystem one step at a | |
| // time controlled by an outside loop. This is useful for testing and | |
| // verifying the behavior of a SpringSystem or if you want to control your own | |
| // timing loop for some reason e.g. slowing down or speeding up the | |
| // simulation. | |
| rebound.SteppingSimulationLooper = function (timestep) { | |
| this.springSystem = null; | |
| var time = 0; | |
| // this.run is NOOP'd here to allow control from the outside using | |
| // this.step. | |
| this.run = function () {}; | |
| // Perform one step toward resolving the SpringSystem. | |
| this.step = function (timestep) { | |
| this.springSystem.loop(time += timestep); | |
| }; | |
| }; | |
| // Math for converting from | |
| // [Origami](http://facebook.github.io/origami/) to | |
| // [Rebound](http://facebook.github.io/rebound). | |
| // You mostly don't need to worry about this, just use | |
| // SpringConfig.fromOrigamiTensionAndFriction(v, v); | |
| var OrigamiValueConverter = rebound.OrigamiValueConverter = { | |
| tensionFromOrigamiValue: function tensionFromOrigamiValue(oValue) { | |
| return (oValue - 30.0) * 3.62 + 194.0; | |
| }, | |
| origamiValueFromTension: function origamiValueFromTension(tension) { | |
| return (tension - 194.0) / 3.62 + 30.0; | |
| }, | |
| frictionFromOrigamiValue: function frictionFromOrigamiValue(oValue) { | |
| return (oValue - 8.0) * 3.0 + 25.0; | |
| }, | |
| origamiFromFriction: function origamiFromFriction(friction) { | |
| return (friction - 25.0) / 3.0 + 8.0; | |
| } | |
| }; | |
| // BouncyConversion provides math for converting from Origami PopAnimation | |
| // config values to regular Origami tension and friction values. If you are | |
| // trying to replicate prototypes made with PopAnimation patches in Origami, | |
| // then you should create your springs with | |
| // SpringSystem.createSpringWithBouncinessAndSpeed, which uses this Math | |
| // internally to create a spring to match the provided PopAnimation | |
| // configuration from Origami. | |
| var BouncyConversion = rebound.BouncyConversion = function (bounciness, speed) { | |
| this.bounciness = bounciness; | |
| this.speed = speed; | |
| var b = this.normalize(bounciness / 1.7, 0, 20.0); | |
| b = this.projectNormal(b, 0.0, 0.8); | |
| var s = this.normalize(speed / 1.7, 0, 20.0); | |
| this.bouncyTension = this.projectNormal(s, 0.5, 200); | |
| this.bouncyFriction = this.quadraticOutInterpolation(b, this.b3Nobounce(this.bouncyTension), 0.01); | |
| }; | |
| util.extend(BouncyConversion.prototype, { | |
| normalize: function normalize(value, startValue, endValue) { | |
| return (value - startValue) / (endValue - startValue); | |
| }, | |
| projectNormal: function projectNormal(n, start, end) { | |
| return start + n * (end - start); | |
| }, | |
| linearInterpolation: function linearInterpolation(t, start, end) { | |
| return t * end + (1.0 - t) * start; | |
| }, | |
| quadraticOutInterpolation: function quadraticOutInterpolation(t, start, end) { | |
| return this.linearInterpolation(2 * t - t * t, start, end); | |
| }, | |
| b3Friction1: function b3Friction1(x) { | |
| return 0.0007 * Math.pow(x, 3) - 0.031 * Math.pow(x, 2) + 0.64 * x + 1.28; | |
| }, | |
| b3Friction2: function b3Friction2(x) { | |
| return 0.000044 * Math.pow(x, 3) - 0.006 * Math.pow(x, 2) + 0.36 * x + 2.; | |
| }, | |
| b3Friction3: function b3Friction3(x) { | |
| return 0.00000045 * Math.pow(x, 3) - 0.000332 * Math.pow(x, 2) + 0.1078 * x + 5.84; | |
| }, | |
| b3Nobounce: function b3Nobounce(tension) { | |
| var friction = 0; | |
| if (tension <= 18) { | |
| friction = this.b3Friction1(tension); | |
| } else if (tension > 18 && tension <= 44) { | |
| friction = this.b3Friction2(tension); | |
| } else { | |
| friction = this.b3Friction3(tension); | |
| } | |
| return friction; | |
| } | |
| }); | |
| util.extend(SpringConfig, { | |
| // Convert an origami Spring tension and friction to Rebound spring | |
| // constants. If you are prototyping a design with Origami, this | |
| // makes it easy to make your springs behave exactly the same in | |
| // Rebound. | |
| fromOrigamiTensionAndFriction: function fromOrigamiTensionAndFriction(tension, friction) { | |
| return new SpringConfig(OrigamiValueConverter.tensionFromOrigamiValue(tension), OrigamiValueConverter.frictionFromOrigamiValue(friction)); | |
| }, | |
| // Convert an origami PopAnimation Spring bounciness and speed to Rebound | |
| // spring constants. If you are using PopAnimation patches in Origami, this | |
| // utility will provide springs that match your prototype. | |
| fromBouncinessAndSpeed: function fromBouncinessAndSpeed(bounciness, speed) { | |
| var bouncyConversion = new rebound.BouncyConversion(bounciness, speed); | |
| return this.fromOrigamiTensionAndFriction(bouncyConversion.bouncyTension, bouncyConversion.bouncyFriction); | |
| }, | |
| // Create a SpringConfig with no tension or a coasting spring with some | |
| // amount of Friction so that it does not coast infininitely. | |
| coastingConfigWithOrigamiFriction: function coastingConfigWithOrigamiFriction(friction) { | |
| return new SpringConfig(0, OrigamiValueConverter.frictionFromOrigamiValue(friction)); | |
| } | |
| }); | |
| SpringConfig.DEFAULT_ORIGAMI_SPRING_CONFIG = SpringConfig.fromOrigamiTensionAndFriction(40, 7); | |
| util.extend(SpringConfig.prototype, { friction: 0, tension: 0 }); | |
| // Here are a couple of function to convert colors between hex codes and RGB | |
| // component values. These are handy when performing color | |
| // tweening animations. | |
| var colorCache = {}; | |
| util.hexToRGB = function (color) { | |
| if (colorCache[color]) { | |
| return colorCache[color]; | |
| } | |
| color = color.replace('#', ''); | |
| if (color.length === 3) { | |
| color = color[0] + color[0] + color[1] + color[1] + color[2] + color[2]; | |
| } | |
| var parts = color.match(/.{2}/g); | |
| var ret = { | |
| r: parseInt(parts[0], 16), | |
| g: parseInt(parts[1], 16), | |
| b: parseInt(parts[2], 16) | |
| }; | |
| colorCache[color] = ret; | |
| return ret; | |
| }; | |
| util.rgbToHex = function (r, g, b) { | |
| r = r.toString(16); | |
| g = g.toString(16); | |
| b = b.toString(16); | |
| r = r.length < 2 ? '0' + r : r; | |
| g = g.length < 2 ? '0' + g : g; | |
| b = b.length < 2 ? '0' + b : b; | |
| return '#' + r + g + b; | |
| }; | |
| var MathUtil = rebound.MathUtil = { | |
| // This helper function does a linear interpolation of a value from | |
| // one range to another. This can be very useful for converting the | |
| // motion of a Spring to a range of UI property values. For example a | |
| // spring moving from position 0 to 1 could be interpolated to move a | |
| // view from pixel 300 to 350 and scale it from 0.5 to 1. The current | |
| // position of the `Spring` just needs to be run through this method | |
| // taking its input range in the _from_ parameters with the property | |
| // animation range in the _to_ parameters. | |
| mapValueInRange: function mapValueInRange(value, fromLow, fromHigh, toLow, toHigh) { | |
| var fromRangeSize = fromHigh - fromLow; | |
| var toRangeSize = toHigh - toLow; | |
| var valueScale = (value - fromLow) / fromRangeSize; | |
| return toLow + valueScale * toRangeSize; | |
| }, | |
| // Interpolate two hex colors in a 0 - 1 range or optionally provide a | |
| // custom range with fromLow,fromHight. The output will be in hex by default | |
| // unless asRGB is true in which case it will be returned as an rgb string. | |
| interpolateColor: function interpolateColor(val, startColor, endColor, fromLow, fromHigh, asRGB) { | |
| fromLow = fromLow === undefined ? 0 : fromLow; | |
| fromHigh = fromHigh === undefined ? 1 : fromHigh; | |
| startColor = util.hexToRGB(startColor); | |
| endColor = util.hexToRGB(endColor); | |
| var r = Math.floor(util.mapValueInRange(val, fromLow, fromHigh, startColor.r, endColor.r)); | |
| var g = Math.floor(util.mapValueInRange(val, fromLow, fromHigh, startColor.g, endColor.g)); | |
| var b = Math.floor(util.mapValueInRange(val, fromLow, fromHigh, startColor.b, endColor.b)); | |
| if (asRGB) { | |
| return 'rgb(' + r + ',' + g + ',' + b + ')'; | |
| } else { | |
| return util.rgbToHex(r, g, b); | |
| } | |
| }, | |
| degreesToRadians: function degreesToRadians(deg) { | |
| return deg * Math.PI / 180; | |
| }, | |
| radiansToDegrees: function radiansToDegrees(rad) { | |
| return rad * 180 / Math.PI; | |
| } | |
| }; | |
| util.extend(util, MathUtil); | |
| // Utilities | |
| // --------- | |
| // Here are a few useful JavaScript utilities. | |
| // Lop off the first occurence of the reference in the Array. | |
| function removeFirst(array, item) { | |
| var idx = array.indexOf(item); | |
| idx != -1 && array.splice(idx, 1); | |
| } | |
| var _onFrame; | |
| if (typeof window !== 'undefined') { | |
| _onFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame || function (callback) { | |
| window.setTimeout(callback, 1000 / 60); | |
| }; | |
| } | |
| if (!_onFrame && typeof process !== 'undefined' && process.title === 'node') { | |
| _onFrame = setImmediate; | |
| } | |
| // Cross browser/node timer functions. | |
| util.onFrame = function onFrame(func) { | |
| return _onFrame(func); | |
| }; | |
| // Export the public api using exports for common js or the window for | |
| // normal browser inclusion. | |
| if (typeof exports != 'undefined') { | |
| util.extend(exports, rebound); | |
| } else if (typeof window != 'undefined') { | |
| window.rebound = rebound; | |
| } | |
| })(); | |
| ; | |
| /** | |
| * Polygon. | |
| * Create a regular polygon and provide api to compute inscribed child. | |
| */ | |
| 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; }; }(); | |
| function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | |
| var Polygon = function () { | |
| function Polygon() { | |
| var radius = arguments.length <= 0 || arguments[0] === undefined ? 100 : arguments[0]; | |
| var sides = arguments.length <= 1 || arguments[1] === undefined ? 3 : arguments[1]; | |
| var depth = arguments.length <= 2 || arguments[2] === undefined ? 0 : arguments[2]; | |
| var colors = arguments[3]; | |
| _classCallCheck(this, Polygon); | |
| this._radius = radius; | |
| this._sides = sides; | |
| this._depth = depth; | |
| this._colors = colors; | |
| this._x = 0; | |
| this._y = 0; | |
| this.rotation = 0; | |
| this.scale = 1; | |
| // Get basePolygon points straight away. | |
| this.points = this._getRegularPolygonPoints(); | |
| } | |
| /** | |
| * Get the points of any regular polygon based on | |
| * the number of sides and radius. | |
| */ | |
| _createClass(Polygon, [{ | |
| key: '_getRegularPolygonPoints', | |
| value: function _getRegularPolygonPoints() { | |
| var points = []; | |
| var i = 0; | |
| while (i < this._sides) { | |
| // Note that sin and cos are inverted in order to draw | |
| // polygon pointing down like: ∇ | |
| var x = -this._radius * Math.sin(i * 2 * Math.PI / this._sides); | |
| var y = this._radius * Math.cos(i * 2 * Math.PI / this._sides); | |
| points.push({ x: x, y: y }); | |
| i++; | |
| } | |
| return points; | |
| } | |
| /** | |
| * Get the inscribed polygon points by calling `getInterpolatedPoint` | |
| * for the points (start, end) of each side. | |
| */ | |
| }, { | |
| key: '_getInscribedPoints', | |
| value: function _getInscribedPoints(points, progress) { | |
| var _this = this; | |
| var inscribedPoints = []; | |
| points.forEach(function (item, i) { | |
| var start = item; | |
| var end = points[i + 1]; | |
| if (!end) { | |
| end = points[0]; | |
| } | |
| var point = _this._getInterpolatedPoint(start, end, progress); | |
| inscribedPoints.push(point); | |
| }); | |
| return inscribedPoints; | |
| } | |
| /** | |
| * Get interpolated point using linear interpolation | |
| * on x and y axis. | |
| */ | |
| }, { | |
| key: '_getInterpolatedPoint', | |
| value: function _getInterpolatedPoint(start, end, progress) { | |
| var Ax = start.x; | |
| var Ay = start.y; | |
| var Bx = end.x; | |
| var By = end.y; | |
| // Linear interpolation formula: | |
| // point = start + (end - start) * progress; | |
| var Cx = Ax + (Bx - Ax) * progress; | |
| var Cy = Ay + (By - Ay) * progress; | |
| return { | |
| x: Cx, | |
| y: Cy | |
| }; | |
| } | |
| /** | |
| * Update children points array. | |
| */ | |
| }, { | |
| key: '_getUpdatedChildren', | |
| value: function _getUpdatedChildren(progress) { | |
| var children = []; | |
| for (var i = 0; i < this._depth; i++) { | |
| // Get basePolygon points on first lap | |
| // then get previous child points. | |
| var points = children[i - 1] || this.points; | |
| var inscribedPoints = this._getInscribedPoints(points, progress); | |
| children.push(inscribedPoints); | |
| } | |
| return children; | |
| } | |
| /** | |
| * Render children, first update children array, | |
| * then loop and draw each child. | |
| */ | |
| }, { | |
| key: 'renderChildren', | |
| value: function renderChildren(context, progress) { | |
| var _this2 = this; | |
| var children = this._getUpdatedChildren(progress); | |
| // child = array of points at a certain progress over the parent sides. | |
| children.forEach(function (points, i) { | |
| // Draw child. | |
| context.beginPath(); | |
| points.forEach(function (point) { | |
| return context.lineTo(point.x, point.y); | |
| }); | |
| context.closePath(); | |
| // Set colors. | |
| var strokeColor = _this2._colors.stroke; | |
| var childColor = _this2._colors.child; | |
| if (strokeColor) { | |
| context.strokeStyle = strokeColor; | |
| context.stroke(); | |
| } | |
| if (childColor) { | |
| var rgb = rebound.util.hexToRGB(childColor); | |
| var alphaUnit = 1 / children.length; | |
| var alpha = alphaUnit + alphaUnit * i; | |
| var rgba = 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + alpha + ')'; | |
| context.fillStyle = rgba; | |
| // Set Shadow. | |
| context.shadowColor = 'rgba(0,0,0, 0.1)'; | |
| context.shadowBlur = 10; | |
| context.shadowOffsetX = 0; | |
| context.shadowOffsetY = 0; | |
| context.fill(); | |
| } | |
| }); | |
| } | |
| /** | |
| * Render. | |
| */ | |
| }, { | |
| key: 'render', | |
| value: function render(context) { | |
| context.save(); | |
| context.translate(this._x, this._y); | |
| if (this.rotation !== 0) { | |
| context.rotate(rebound.MathUtil.degreesToRadians(this.rotation)); | |
| } | |
| if (this.scale !== 1) { | |
| context.scale(this.scale, this.scale); | |
| } | |
| // Draw basePolygon. | |
| context.beginPath(); | |
| this.points.forEach(function (point) { | |
| return context.lineTo(point.x, point.y); | |
| }); | |
| context.closePath(); | |
| // Set colors. | |
| var strokeColor = this._colors.stroke; | |
| var childColor = this._colors.base; | |
| if (strokeColor) { | |
| context.strokeStyle = strokeColor; | |
| context.stroke(); | |
| } | |
| if (childColor) { | |
| context.fillStyle = childColor; | |
| context.fill(); | |
| } | |
| context.restore(); | |
| } | |
| }]); | |
| return Polygon; | |
| }(); | |
| ; | |
| /** | |
| * Spinner. | |
| * Create a canvas element, append it to the body, render polygon with | |
| * inscribed children, provide init and complete methods to control spinner. | |
| */ | |
| 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; }; }(); | |
| function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | |
| var Spinner = function () { | |
| function Spinner(params) { | |
| _classCallCheck(this, Spinner); | |
| var id = params.id, | |
| radius = params.radius, | |
| sides = params.sides, | |
| depth = params.depth, | |
| colors = params.colors, | |
| alwaysForward = params.alwaysForward, | |
| restAt = params.restAt, | |
| renderBase = params.renderBase; | |
| if (sides < 3) { | |
| console.warn('At least 3 sides required.'); | |
| sides = 3; | |
| } | |
| this._canvas = document.createElement('canvas'); | |
| this._canvas.style.backgroundColor = colors.background; | |
| this._canvasW = null; | |
| this._canvasH = null; | |
| this._canvasOpacity = 1; | |
| this._centerX = null; | |
| this._centerY = null; | |
| this._alwaysForward = alwaysForward; | |
| this._restThreshold = restAt; | |
| this._renderBase = renderBase; | |
| this._springRangeLow = 0; | |
| this._springRangeHigh = this._restThreshold || 1; | |
| // Instantiate basePolygon. | |
| this._basePolygon = new Polygon(radius, sides, depth, colors); | |
| this._progress = 0; | |
| this._isAutoSpin = null; | |
| this._isCompleting = null; | |
| } | |
| /** | |
| * Init spinner. | |
| */ | |
| _createClass(Spinner, [{ | |
| key: 'init', | |
| value: function init(spring, autoSpin) { | |
| this._addCanvas(); | |
| this._spring = spring; | |
| this._addSpringListener(); | |
| this._isAutoSpin = autoSpin; | |
| if (autoSpin) { | |
| // Start auto spin. | |
| this._spin(); | |
| } else { | |
| // Render first frame only. | |
| this._spring.setEndValue(0); | |
| this.render(); | |
| } | |
| } | |
| }, { | |
| key: '_addCanvas', | |
| value: function _addCanvas() { | |
| document.body.appendChild(this._canvas); | |
| this._context = this._canvas.getContext('2d'); | |
| this._setCanvasSize(); | |
| } | |
| }, { | |
| key: '_setCanvasSize', | |
| value: function _setCanvasSize() { | |
| this._canvasW = this._canvas.width = window.innerWidth; | |
| this._canvasH = this._canvas.height = window.innerHeight; | |
| this._canvas.style.position = 'fixed'; | |
| this._canvas.style.top = 0; | |
| this._canvas.style.left = 0; | |
| this._centerX = this._canvasW / 2; | |
| this._centerY = this._canvasH / 2; | |
| } | |
| }, { | |
| key: '_addSpringListener', | |
| value: function _addSpringListener() { | |
| var ctx = this; | |
| // Add a listener to the spring. Every time the physics | |
| // solver updates the Spring's value onSpringUpdate will | |
| // be called. | |
| this._spring.addListener({ | |
| onSpringUpdate: function onSpringUpdate(spring) { | |
| var val = spring.getCurrentValue(); | |
| // Input range in the `from` parameters. | |
| var fromLow = 0, | |
| fromHigh = 1, | |
| // Property animation range in the `to` parameters. | |
| toLow = ctx._springRangeLow, | |
| toHigh = ctx._springRangeHigh; | |
| val = rebound.MathUtil.mapValueInRange(val, fromLow, fromHigh, toLow, toHigh); | |
| // Note that the render method is | |
| // called with the spring motion value. | |
| ctx.render(val); | |
| } | |
| }); | |
| } | |
| /** | |
| * Start complete animation. | |
| */ | |
| }, { | |
| key: 'setComplete', | |
| value: function setComplete() { | |
| this._isCompleting = true; | |
| } | |
| }, { | |
| key: '_completeAnimation', | |
| value: function _completeAnimation() { | |
| // Fade out the canvas. | |
| this._canvasOpacity -= 0.1; | |
| this._canvas.style.opacity = this._canvasOpacity; | |
| // Stop animation and remove canvas. | |
| if (this._canvasOpacity <= 0) { | |
| this._isAutoSpin = false; | |
| this._spring.setAtRest(); | |
| this._canvas.remove(); | |
| } | |
| } | |
| /** | |
| * Spin animation. | |
| */ | |
| }, { | |
| key: '_spin', | |
| value: function _spin() { | |
| if (this._alwaysForward) { | |
| var currentValue = this._spring.getCurrentValue(); | |
| // Switch the animation range used to compute the value | |
| // in the `onSpringUpdate`, so to change the reverse animation | |
| // of the spring at a certain threshold. | |
| if (this._restThreshold && currentValue === 1) { | |
| this._switchSpringRange(); | |
| } | |
| // In order to keep the motion going forward | |
| // when spring reach 1 reset to 0 at rest. | |
| if (currentValue === 1) { | |
| this._spring.setCurrentValue(0).setAtRest(); | |
| } | |
| } | |
| // Restart the spinner. | |
| this._spring.setEndValue(this._spring.getCurrentValue() === 1 ? 0 : 1); | |
| } | |
| }, { | |
| key: '_switchSpringRange', | |
| value: function _switchSpringRange() { | |
| var threshold = this._restThreshold; | |
| this._springRangeLow = this._springRangeLow === threshold ? 0 : threshold; | |
| this._springRangeHigh = this._springRangeHigh === threshold ? 1 : threshold; | |
| } | |
| /** | |
| * Render. | |
| */ | |
| }, { | |
| key: 'render', | |
| value: function render(progress) { | |
| // Update progess if present and round to 4th decimal. | |
| if (progress) { | |
| this._progress = Math.round(progress * 10000) / 10000; | |
| } | |
| // Restart the spin. | |
| if (this._isAutoSpin && this._spring.isAtRest()) { | |
| this._spin(); | |
| } | |
| // Complete the animation. | |
| if (this._isCompleting) { | |
| this._completeAnimation(); | |
| } | |
| // Clear canvas and save context. | |
| this._context.clearRect(0, 0, this._canvasW, this._canvasH); | |
| this._context.save(); | |
| // Move to center. | |
| this._context.translate(this._centerX, this._centerY); | |
| this._context.lineWidth = 1.5; | |
| // Render basePolygon. | |
| if (this._renderBase) { | |
| this._basePolygon.render(this._context); | |
| } | |
| // Render inscribed polygons. | |
| this._basePolygon.renderChildren(this._context, this._progress); | |
| this._context.restore(); | |
| } | |
| }]); | |
| return Spinner; | |
| }(); | |
| ; | |
| // Custom SETTINGS for each demo in related index.html | |
| var settings = SETTINGS || { | |
| rebound: { | |
| tension: 2, | |
| friction: 5 | |
| }, | |
| spinner: { | |
| radius: 80, | |
| sides: 3, | |
| depth: 4, | |
| colors: { | |
| background: '#000000', | |
| stroke: '#000000', | |
| base: '#222222', | |
| child: '#FFFFFF' | |
| }, | |
| alwaysForward: true, // When false the spring will reverse normally. | |
| restAt: 0.5, // A number from 0.1 to 0.9 || null for full rotation | |
| renderBase: true // Optionally render basePolygon | |
| } | |
| }; | |
| /** | |
| * Demo. | |
| */ | |
| var demo = { | |
| settings: settings, | |
| spring: null, | |
| spinner: null, | |
| /** | |
| * Initialize Rebound.js with settings. | |
| * Rebound is used to generate a spring which | |
| * is then used to animate the spinner. | |
| * See more: http://facebook.github.io/rebound-js/docs/rebound.html | |
| */ | |
| initRebound: function initRebound() { | |
| var settings = demo.settings.rebound; | |
| // Create a SpringSystem. | |
| var springSystem = new rebound.SpringSystem(); | |
| // Add a spring to the system. | |
| demo.spring = springSystem.createSpring(settings.tension, settings.friction); | |
| }, | |
| /** | |
| * Initialize Spinner with settings. | |
| */ | |
| initSpinner: function initSpinner() { | |
| var settings = demo.settings.spinner; | |
| // Instantiate Spinner. | |
| demo.spinner = new Spinner(settings); | |
| }, | |
| /** | |
| * Initialize demo. | |
| */ | |
| init: function init() { | |
| var spinnerTypeAutoSpin = true; | |
| // Instantiate animation engine and spinner system. | |
| demo.initRebound(); | |
| demo.initSpinner(); | |
| // Init animation with Rebound Spring System. | |
| demo.spinner.init(demo.spring, spinnerTypeAutoSpin); | |
| if (spinnerTypeAutoSpin) { | |
| // Fake loading time, in a real world just call demo.spinner.setComplete(); | |
| // whenever the preload will be completed. | |
| //change the load time | |
| setTimeout(function () { | |
| demo.spinner.setComplete(); | |
| }, 12000); | |
| } else { | |
| // Perform real ajax request. | |
| demo.loadSomething(); | |
| } | |
| }, | |
| /** | |
| * Ajax Request. | |
| */ | |
| loadSomething: function loadSomething() { | |
| var oReq = new XMLHttpRequest(); | |
| oReq.addEventListener('progress', function (oEvent) { | |
| if (oEvent.lengthComputable) { | |
| var percent = Math.ceil(oEvent.loaded / oEvent.total * 100); | |
| console.log('ajax loding percent', percent); | |
| // By setting the end value with the actual loading percentage | |
| // the spinner will progress based on the actual ajax loading time. | |
| demo.spring.setEndValue(percent * 0.01); | |
| } | |
| }); | |
| oReq.addEventListener('load', function (e) { | |
| // Complete the loading animation. | |
| demo.spinner.setComplete(); | |
| }); | |
| oReq.open('GET', '/img/something.jpg'); | |
| oReq.send(); | |
| } | |
| }; | |