@douyinfe/semi-animation
Version:
animation base library for semi-ui
255 lines (254 loc) • 9.71 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _Event = _interopRequireDefault(require("./utils/Event"));
var _shouldStopAnimation = _interopRequireDefault(require("./shouldStopAnimation"));
var _shouldUseBezier = _interopRequireDefault(require("./shouldUseBezier"));
var _stripStyle = _interopRequireDefault(require("./stripStyle"));
var _stepper = _interopRequireDefault(require("./stepper"));
var _mapToZero = _interopRequireDefault(require("./mapToZero"));
var _wrapValue = _interopRequireDefault(require("./wrapValue"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
const now = () => Date.now();
const msPerFrame = 1000 / 60;
/**
* @summary
*
* Lifecycle hook:
* start, pause, resume, stop, frame, rest
*
* Binding method:
* const animation = new Animation (); animation.on ('start | frame | rest ', () => {});
*/
class Animation extends _Event.default {
constructor() {
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
super();
this._props = Object.assign({}, props);
this._config = Object.assign({}, config);
this.initStates();
}
_wrapConfig(object, config) {
config = config && typeof config === 'object' ? config : this._config;
const ret = {};
for (const key of Object.keys(object)) {
ret[key] = (0, _wrapValue.default)(object[key], config);
}
return ret;
}
initStates(props, config) {
props = props && typeof props === 'object' ? props : this._props;
config = config && typeof config === 'object' ? config : this._config;
const {
from,
to
} = props;
this._from = {};
if (from && typeof from) {
for (const key of Object.keys(from)) {
this._from[key] = typeof from[key] === 'object' && from[key].val ? from[key].val : from[key];
}
}
this._to = this._wrapConfig(to, config);
this._delay = parseInt(config.delay) || 0;
const currentStyle = this._from && (0, _stripStyle.default)(this._from) || (0, _stripStyle.default)(this._to);
const currentVelocity = (0, _mapToZero.default)(currentStyle);
this._currentStyle = Object.assign({}, currentStyle);
this._currentVelocity = Object.assign({}, currentVelocity);
this._lastIdealStyle = Object.assign({}, currentStyle);
this._lastIdealVelocity = Object.assign({}, currentVelocity);
this.resetPlayStates();
this._frameCount = 0;
this._prevTime = 0;
}
animate() {
if (this._timer != null) {
return;
}
this._timer = requestAnimationFrame(timestamp => {
const nowTime = now();
// stop animation and emit onRest event
if ((0, _shouldStopAnimation.default)(this._currentStyle, this._to, this._currentVelocity, this._startedTime || nowTime, nowTime) || this._ended || this._stopped) {
if (this._wasAnimating && !this._ended && !this._stopped) {
// should emit reset in settimeout for delay msPerframe
this._timer = setTimeout(() => {
clearTimeout(this._timer);
this._timer = null;
this._ended = true;
this.emit('rest', this.getCurrentStates());
}, msPerFrame);
}
this.resetPlayStates();
return;
}
if (!this._started) {
this._started = true;
this.emit('start', this.getCurrentStates());
}
this._stopped = false;
this._paused = false;
this._wasAnimating = true;
if (this._startedTime === 0) {
this._startedTime = nowTime;
}
const currentTime = nowTime;
const timeDelta = currentTime - this._prevTime;
this._prevTime = currentTime;
if (currentTime - this._startedTime < this._delay) {
this._timer = null;
this.animate();
}
const newLastIdealStyle = {};
const newLastIdealVelocity = {};
const newCurrentStyle = {};
const newCurrentVelocity = {};
const toKeys = this._to && Object.keys(this._to) || [];
for (const key of toKeys) {
const styleValue = this._to[key];
this._accumulatedTime[key] = typeof this._accumulatedTime[key] !== 'number' ? timeDelta : this._accumulatedTime[key] + timeDelta;
const from = this._from[key] != null && typeof this._from[key] === 'object' ? this._from[key].val : this._from[key];
const to = styleValue.val;
if (typeof styleValue === 'number') {
newCurrentStyle[key] = styleValue;
newCurrentVelocity[key] = 0;
newLastIdealStyle[key] = styleValue;
newLastIdealVelocity[key] = 0;
} else {
let newLastIdealStyleValue = this._lastIdealStyle[key];
let newLastIdealVelocityValue = this._lastIdealVelocity[key];
if ((0, _shouldUseBezier.default)(this._config) || (0, _shouldUseBezier.default)(styleValue)) {
// easing
const {
easing,
duration
} = styleValue;
newLastIdealStyleValue = from + easing((currentTime - this._startedTime) / duration) * (to - from);
if (currentTime >= this._startedTime + duration) {
newLastIdealStyleValue = to;
styleValue.done = true;
}
newLastIdealStyle[key] = newLastIdealStyleValue;
newCurrentStyle[key] = newLastIdealStyleValue;
} else if (to != null && to === this._currentStyle[key]) {
newCurrentStyle[key] = to;
newCurrentVelocity[key] = 0;
newLastIdealStyle[key] = to;
newLastIdealVelocity[key] = 0;
} else {
// spring
const currentFrameCompletion = (this._accumulatedTime[key] - Math.floor(this._accumulatedTime[key] / msPerFrame) * msPerFrame) / msPerFrame;
const framesToCatchUp = Math.floor(this._accumulatedTime[key] / msPerFrame);
for (let i = 0; i < framesToCatchUp; i++) {
[newLastIdealStyleValue, newLastIdealVelocityValue] = (0, _stepper.default)(msPerFrame / 1000, newLastIdealStyleValue, newLastIdealVelocityValue, styleValue.val, styleValue.tension, styleValue.friction, styleValue.precision);
}
const [nextIdealX, nextIdealV] = (0, _stepper.default)(msPerFrame / 1000, newLastIdealStyleValue, newLastIdealVelocityValue, styleValue.val, styleValue.tension, styleValue.friction, styleValue.precision);
newCurrentStyle[key] = newLastIdealStyleValue + (nextIdealX - newLastIdealStyleValue) * currentFrameCompletion;
newCurrentVelocity[key] = newLastIdealVelocityValue + (nextIdealV - newLastIdealVelocityValue) * currentFrameCompletion;
newLastIdealStyle[key] = newLastIdealStyleValue;
newLastIdealVelocity[key] = newLastIdealVelocityValue;
this._accumulatedTime[key] -= framesToCatchUp * msPerFrame;
}
}
}
this._timer = null;
this._currentStyle = Object.assign({}, newCurrentStyle);
this._currentVelocity = Object.assign({}, newCurrentVelocity);
this._lastIdealStyle = Object.assign({}, newLastIdealStyle);
this._lastIdealVelocity = Object.assign({}, newLastIdealVelocity);
// console.log(newCurrentStyle);
if (!this._destroyed) {
this.emit('frame', this.getCurrentStates());
this.animate();
}
});
}
start() {
this._prevTime = now();
this._startedTime = now();
this.animate();
}
end() {
if (!this._ended) {
this._ended = true;
this._currentStyle = this.getFinalStates();
this.emit('frame', this.getFinalStates());
this.emit('rest', this.getFinalStates());
}
this.destroy();
}
pause() {
if (!this._paused) {
this._pausedTime = now();
this._paused = true;
this.emit('pause', this.getCurrentStates());
this.destroy();
this._destroyed = false;
}
}
resume() {
if (this._started && this._paused) {
const nowTime = now();
const pausedDuration = nowTime - this._pausedTime;
this._paused = false;
// should add with pausedDuration
this._startedTime += pausedDuration;
this._prevTime += pausedDuration;
this._pausedTime = 0;
this.emit('resume', this.getCurrentStates());
this.animate();
}
}
stop() {
this.destroy();
if (!this._stopped) {
this._stopped = true;
// this.emit('frame', this.getInitialStates());
this.emit('stop', this.getInitialStates());
this.initStates();
}
}
destroy() {
cancelAnimationFrame(this._timer);
clearTimeout(this._timer);
this._timer = null;
this._destroyed = true;
}
resetPlayStates() {
this._started = false;
this._stopped = false;
this._ended = false;
this._paused = false;
this._destroyed = false;
this._timer = null;
this._wasAnimating = false;
this._accumulatedTime = {};
this._startedTime = 0;
this._pausedTime = 0;
}
reset() {
this.destroy();
this.initStates();
}
reverse() {
this.destroy();
const props = Object.assign({}, this._props);
const [from, to] = [props.to, props.from];
props.from = from;
props.to = to;
this._props = Object.assign({}, props);
this.initStates();
}
getCurrentStates() {
return Object.assign({}, this._currentStyle);
}
getInitialStates() {
return Object.assign({}, (0, _stripStyle.default)(this._props.from));
}
getFinalStates() {
return Object.assign({}, (0, _stripStyle.default)(this._props.to));
}
}
exports.default = Animation;