// Copyright (c) 2016 Adobe Systems Incorporated. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import eve from './eve.js' import { SnapElement } from './svg.js' import mina from './mina.js' import { is } from './utils.js' let elproto = SnapElement.prototype, Str = String, has = 'hasOwnProperty' function slice(from, to, f) { return function (arr) { let res = arr.slice(from, to) if (res.length == 1) { res = res[0] } return f ? f(res) : res } } let Animation = function (attr, ms, easing, callback) { if (typeof easing == 'function' && !easing.length) { callback = easing easing = mina.linear } this.attr = attr this.dur = ms easing && (this.easing = easing) callback && (this.callback = callback) } export { Animation } /** * Creates an animation object ** * attr (object) attributes of final destination * duration (number) duration of the animation, in milliseconds * easing (function) #optional one of easing functions of @mina or custom one * callback (function) #optional callback function that fires when animation ends */ export function createAnimation(attr, ms, easing, callback) { return new Animation(attr, ms, easing, callback) } /** * Returns a set of animations that may be able to manipulate the current element ** = (object) in format: { anim (object) animation object, mina (object) @mina object, curStatus (number) 0..1 — status of the animation: 0 — just started, 1 — just finished, status (function) gets or sets the status of the animation, stop (function) stops the animation } */ elproto.inAnim = function () { let el = this let res = [] for (let id in el.anims) { if (el.anims.hasOwnProperty(id)) { let a = el.anims[id] res.push({ anim: new Animation(a._attrs, a.dur, a.easing, a._callback), mina: a, curStatus: a.status(), status(val) { return a.status(val) }, stop() { a.stop() } }) } } return res } /** * Runs generic animation of one number into another with a caring function ** - from (number|array) number or array of numbers - to (number|array) number or array of numbers - setter (function) caring function that accepts one number argument - duration (number) duration, in milliseconds - easing (function) #optional easing function from @mina or custom - callback (function) #optional callback function to execute when animation ends = (object) animation object in @mina format { id (string) animation id, consider it read-only, duration (function) gets or sets the duration of the animation, easing (function) easing, speed (function) gets or sets the speed of the animation, status (function) gets or sets the status of the animation, stop (function) stops the animation } let rect = new Snap().rect(0, 0, 10, 10); animate(0, 10, function (val) { rect.attr({ x: val }); }, 1000); // in given context is equivalent to rect.animate({x: 10}, 1000); */ export function animate(from, to, setter, ms, easing, callback) { if (typeof easing == 'function' && !easing.length) { callback = easing easing = mina.linear } let now = mina.time(), anim = mina(from, to, now, now + ms, mina.time, setter, easing) callback && eve.once('mina.finish.' + anim.id, callback) return anim } /*\ * SnapElement.stop [ method ] ** * Stops all the animations for the current element ** = (SnapElement) the current element \*/ elproto.stop = function () { let anims = this.inAnim() for (let i = 0, ii = anims.length; i < ii; i++) { anims[i].stop() } return this } /*\ * SnapElement.animate [ method ] ** * Animates the given attributes of the element ** - attrs (object) key-value pairs of destination attributes - duration (number) duration of the animation in milliseconds - easing (function) #optional easing function from @mina or custom - callback (function) #optional callback function that executes when the animation ends = (SnapElement) the current element \*/ elproto.animate = function (attrs, ms, easing, callback) { if (typeof easing == 'function' && !easing.length) { callback = easing easing = mina.linear } if (attrs instanceof Animation) { callback = attrs.callback easing = attrs.easing ms = attrs.dur attrs = attrs.attr } let fkeys = [], tkeys = [], keys = {}, from, to, f, eq, el = this for (let key in attrs) if (attrs[has](key)) { if (el.equal) { eq = el.equal(key, Str(attrs[key])) from = eq.from to = eq.to f = eq.f } else { from = +el.attr(key) to = +attrs[key] } let len = is(from, 'array') ? from.length : 1 keys[key] = slice(fkeys.length, fkeys.length + len, f) fkeys = fkeys.concat(from) tkeys = tkeys.concat(to) } let now = mina.time(), anim = mina( fkeys, tkeys, now, now + ms, mina.time, function (val) { let attr = {} for (let key in keys) if (keys[has](key)) { attr[key] = keys[key](val) } el.attr(attr) }, easing ) el.anims[anim.id] = anim anim._attrs = attrs anim._callback = callback eve('snap.animcreated.' + el.id, anim) eve.once('mina.finish.' + anim.id, function () { eve.off('mina.*.' + anim.id) delete el.anims[anim.id] callback && callback.call(el) }) eve.once('mina.stop.' + anim.id, function () { eve.off('mina.*.' + anim.id) delete el.anims[anim.id] }) return el }