|
1 | 1 | /**
|
2 | 2 | * http://animejs.com
|
3 | 3 | * JavaScript animation engine
|
4 |
| - * @version v2.1.0 |
| 4 | + * @version v2.2.0 |
5 | 5 | * @author Julian Garnier
|
6 | 6 | * @copyright ©2017 Julian Garnier
|
7 | 7 | * Released under the MIT license
|
|
55 | 55 | const is = {
|
56 | 56 | arr: a => Array.isArray(a),
|
57 | 57 | obj: a => stringContains(Object.prototype.toString.call(a), 'Object'),
|
| 58 | + pth: a => is.obj(a) && a.hasOwnProperty('totalLength'), |
58 | 59 | svg: a => a instanceof SVGElement,
|
59 | 60 | dom: a => a.nodeType || is.svg(a),
|
60 | 61 | str: a => typeof a === 'string',
|
|
229 | 230 |
|
230 | 231 | // Arrays
|
231 | 232 |
|
| 233 | + function filterArray(arr, callback) { |
| 234 | + const len = arr.length; |
| 235 | + const thisArg = arguments.length >= 2 ? arguments[1] : void 0; |
| 236 | + let result = []; |
| 237 | + for (let i = 0; i < len; i++) { |
| 238 | + if (i in arr) { |
| 239 | + const val = arr[i]; |
| 240 | + if (callback.call(thisArg, val, i, arr)) { |
| 241 | + result.push(val); |
| 242 | + } |
| 243 | + } |
| 244 | + } |
| 245 | + return result; |
| 246 | + } |
| 247 | + |
232 | 248 | function flattenArray(arr) {
|
233 | 249 | return arr.reduce((a, b) => a.concat(is.arr(b) ? flattenArray(b) : b), []);
|
234 | 250 | }
|
|
246 | 262 |
|
247 | 263 | // Objects
|
248 | 264 |
|
249 |
| - function objectHas(obj, prop) { |
250 |
| - return obj.hasOwnProperty(prop); |
251 |
| - } |
252 |
| - |
253 | 265 | function cloneObject(o) {
|
254 | 266 | let clone = {};
|
255 | 267 | for (let p in o) clone[p] = o[p];
|
|
258 | 270 |
|
259 | 271 | function replaceObjectProps(o1, o2) {
|
260 | 272 | let o = cloneObject(o1);
|
261 |
| - for (let p in o1) o[p] = objectHas(o2, p) ? o2[p] : o1[p]; |
| 273 | + for (let p in o1) o[p] = o2.hasOwnProperty(p) ? o2[p] : o1[p]; |
262 | 274 | return o;
|
263 | 275 | }
|
264 | 276 |
|
|
270 | 282 |
|
271 | 283 | // Colors
|
272 | 284 |
|
273 |
| - function hexToRgb(hexValue) { |
| 285 | + function rgbToRgba(rgbValue) { |
| 286 | + const rgb = /rgb\((\d+,\s*[\d]+,\s*[\d]+)\)/g.exec(rgbValue); |
| 287 | + return rgb ? `rgba(${rgb[1]},1)` : rgbValue; |
| 288 | + } |
| 289 | + |
| 290 | + function hexToRgba(hexValue) { |
274 | 291 | const rgx = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
275 | 292 | const hex = hexValue.replace(rgx, (m, r, g, b) => r + r + g + g + b + b );
|
276 | 293 | const rgb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
277 | 294 | const r = parseInt(rgb[1], 16);
|
278 | 295 | const g = parseInt(rgb[2], 16);
|
279 | 296 | const b = parseInt(rgb[3], 16);
|
280 |
| - return `rgb(${r},${g},${b})`; |
| 297 | + return `rgba(${r},${g},${b},1)`; |
281 | 298 | }
|
282 | 299 |
|
283 |
| - function hslToRgb(hslValue) { |
284 |
| - const hsl = /hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.exec(hslValue); |
| 300 | + function hslToRgba(hslValue) { |
| 301 | + const hsl = /hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.exec(hslValue) || /hsla\((\d+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)/g.exec(hslValue); |
285 | 302 | const h = parseInt(hsl[1]) / 360;
|
286 | 303 | const s = parseInt(hsl[2]) / 100;
|
287 | 304 | const l = parseInt(hsl[3]) / 100;
|
| 305 | + const a = hsl[4] || 1; |
288 | 306 | function hue2rgb(p, q, t) {
|
289 | 307 | if (t < 0) t += 1;
|
290 | 308 | if (t > 1) t -= 1;
|
|
303 | 321 | g = hue2rgb(p, q, h);
|
304 | 322 | b = hue2rgb(p, q, h - 1/3);
|
305 | 323 | }
|
306 |
| - return `rgb(${r * 255},${g * 255},${b * 255})`; |
| 324 | + return `rgba(${r * 255},${g * 255},${b * 255},${a})`; |
307 | 325 | }
|
308 | 326 |
|
309 | 327 | function colorToRgb(val) {
|
310 |
| - if (is.rgb(val)) return val; |
311 |
| - if (is.hex(val)) return hexToRgb(val); |
312 |
| - if (is.hsl(val)) return hslToRgb(val); |
| 328 | + if (is.rgb(val)) return rgbToRgba(val); |
| 329 | + if (is.hex(val)) return hexToRgba(val); |
| 330 | + if (is.hsl(val)) return hslToRgba(val); |
313 | 331 | }
|
314 | 332 |
|
315 | 333 | // Units
|
|
361 | 379 | props.push(match[1]);
|
362 | 380 | values.push(match[2]);
|
363 | 381 | }
|
364 |
| - const value = values.filter((val, i) => props[i] === propName ); |
| 382 | + const value = filterArray(values, (val, i) => props[i] === propName); |
365 | 383 | return value.length ? value[0] : defaultVal;
|
366 | 384 | }
|
367 | 385 |
|
|
454 | 472 |
|
455 | 473 | // Motion path
|
456 | 474 |
|
457 |
| - function isPath(val) { |
458 |
| - return is.obj(val) && objectHas(val, 'totalLength'); |
459 |
| - } |
460 |
| - |
461 | 475 | function getPath(path, percent) {
|
462 | 476 | const el = is.str(path) ? selectString(path)[0] : path;
|
463 | 477 | const p = percent || 100;
|
|
485 | 499 | }
|
486 | 500 | }
|
487 | 501 |
|
488 |
| - // Decompose / recompose functions adapted from Animate Plus https://github.com/bendc/animateplus |
| 502 | + // Decompose value |
489 | 503 |
|
490 | 504 | function decomposeValue(val, unit) {
|
491 | 505 | const rgx = /-?\d*\.?\d+/g;
|
492 |
| - const value = validateValue((isPath(val) ? val.totalLength : val), unit) + ''; |
| 506 | + const value = validateValue((is.pth(val) ? val.totalLength : val), unit) + ''; |
493 | 507 | return {
|
494 | 508 | original: value,
|
495 | 509 | numbers: value.match(rgx) ? value.match(rgx).map(Number) : [0],
|
496 | 510 | strings: (is.str(val) || unit) ? value.split(rgx) : []
|
497 | 511 | }
|
498 | 512 | }
|
499 | 513 |
|
500 |
| - function recomposeValue(numbers, strings) { |
501 |
| - return (strings.length === 0) ? numbers[0] : strings.reduce((a, b, i) => a + numbers[i - 1] + (b ? b : ' ')); |
502 |
| - } |
503 |
| - |
504 | 514 | // Animatables
|
505 | 515 |
|
506 | 516 | function parseTargets(targets) {
|
507 | 517 | const targetsArray = targets ? (flattenArray(is.arr(targets) ? targets.map(toArray) : toArray(targets))) : [];
|
508 |
| - return targetsArray.filter((item, pos, self) => self.indexOf(item) === pos); |
| 518 | + return filterArray(targetsArray, (item, pos, self) => self.indexOf(item) === pos); |
509 | 519 | }
|
510 | 520 |
|
511 | 521 | function getAnimatables(targets) {
|
|
534 | 544 | // Default delay value should be applied only on the first tween
|
535 | 545 | const delay = !i ? tweenSettings.delay : 0;
|
536 | 546 | // Use path object as a tween value
|
537 |
| - let obj = is.obj(v) && !isPath(v) ? v : {value: v}; |
| 547 | + let obj = is.obj(v) && !is.pth(v) ? v : {value: v}; |
538 | 548 | // Set default delay value
|
539 | 549 | if (is.und(obj.delay)) obj.delay = delay;
|
540 | 550 | return obj;
|
|
545 | 555 | let properties = [];
|
546 | 556 | const settings = mergeObjects(instanceSettings, tweenSettings);
|
547 | 557 | for (let p in params) {
|
548 |
| - if (!objectHas(settings, p) && p !== 'targets') { |
| 558 | + if (!settings.hasOwnProperty(p) && p !== 'targets') { |
549 | 559 | properties.push({
|
550 | 560 | name: p,
|
551 | 561 | offset: settings['offset'],
|
|
587 | 597 | const from = is.arr(tweenValue) ? tweenValue[0] : previousValue;
|
588 | 598 | const to = getRelativeValue(is.arr(tweenValue) ? tweenValue[1] : tweenValue, from);
|
589 | 599 | const unit = getUnit(to) || getUnit(from) || getUnit(originalValue);
|
590 |
| - tween.isPath = isPath(tweenValue); |
591 | 600 | tween.from = decomposeValue(from, unit);
|
592 | 601 | tween.to = decomposeValue(to, unit);
|
593 | 602 | tween.start = previousTween ? previousTween.end : prop.offset;
|
594 | 603 | tween.end = tween.start + tween.delay + tween.duration;
|
595 | 604 | tween.easing = normalizeEasing(tween.easing);
|
596 | 605 | tween.elasticity = (1000 - minMaxValue(tween.elasticity, 1, 999)) / 1000;
|
597 |
| - if (is.col(tween.from.original)) tween.round = 1; |
| 606 | + tween.isPath = is.pth(tweenValue); |
| 607 | + tween.isColor = is.col(tween.from.original); |
| 608 | + if (tween.isColor) tween.round = 1; |
598 | 609 | previousTween = tween;
|
599 | 610 | return tween;
|
600 | 611 | });
|
|
630 | 641 | }
|
631 | 642 |
|
632 | 643 | function getAnimations(animatables, properties) {
|
633 |
| - return flattenArray(animatables.map(animatable => { |
| 644 | + return filterArray(flattenArray(animatables.map(animatable => { |
634 | 645 | return properties.map(prop => {
|
635 | 646 | return createAnimation(animatable, prop);
|
636 | 647 | });
|
637 |
| - })).filter(a => !is.und(a)); |
| 648 | + })), a => !is.und(a)); |
638 | 649 | }
|
639 | 650 |
|
640 | 651 | // Create Instance
|
641 | 652 |
|
642 |
| - function getInstanceTimings(type, animations, tweenSettings) { |
643 |
| - const math = (type === 'delay') ? Math.min : Math.max; |
644 |
| - return animations.length ? math.apply(Math, animations.map(anim => anim[type])) : tweenSettings[type]; |
| 653 | + function getInstanceTimings(type, animations, instanceSettings, tweenSettings) { |
| 654 | + const isDelay = (type === 'delay'); |
| 655 | + if (animations.length) { |
| 656 | + return (isDelay ? Math.min : Math.max).apply(Math, animations.map(anim => anim[type])); |
| 657 | + } else { |
| 658 | + return isDelay ? tweenSettings.delay : instanceSettings.offset + tweenSettings.delay + tweenSettings.duration; |
| 659 | + } |
645 | 660 | }
|
646 | 661 |
|
647 | 662 | function createNewInstance(params) {
|
|
654 | 669 | children: [],
|
655 | 670 | animatables: animatables,
|
656 | 671 | animations: animations,
|
657 |
| - duration: getInstanceTimings('duration', animations, tweenSettings), |
658 |
| - delay: getInstanceTimings('delay', animations, tweenSettings) |
| 672 | + duration: getInstanceTimings('duration', animations, instanceSettings, tweenSettings), |
| 673 | + delay: getInstanceTimings('delay', animations, instanceSettings, tweenSettings) |
659 | 674 | });
|
660 | 675 | }
|
661 | 676 |
|
|
710 | 725 |
|
711 | 726 | function syncInstanceChildren(time) {
|
712 | 727 | const children = instance.children;
|
| 728 | + const childrenLength = children.length; |
713 | 729 | if (time >= instance.currentTime) {
|
714 |
| - for (let i = 0; i < children.length; i++) children[i].seek(time); |
| 730 | + for (let i = 0; i < childrenLength; i++) children[i].seek(time); |
715 | 731 | } else {
|
716 |
| - for (let i = children.length; i--;) children[i].seek(time); |
| 732 | + for (let i = childrenLength; i--;) children[i].seek(time); |
717 | 733 | }
|
718 | 734 | }
|
719 | 735 |
|
720 | 736 | function setAnimationsProgress(insTime) {
|
721 | 737 | let i = 0;
|
722 | 738 | let transforms = {};
|
723 | 739 | const animations = instance.animations;
|
724 |
| - while (i < animations.length) { |
| 740 | + const animationsLength = animations.length; |
| 741 | + while (i < animationsLength) { |
725 | 742 | const anim = animations[i];
|
726 | 743 | const animatable = anim.animatable;
|
727 | 744 | const tweens = anim.tweens;
|
728 |
| - const tween = tweens.filter(t => (insTime < t.end))[0] || tweens[tweens.length - 1]; |
| 745 | + const tweenLength = tweens.length - 1; |
| 746 | + let tween = tweens[tweenLength]; |
| 747 | + // Only check for keyframes if there is more than one tween |
| 748 | + if (tweenLength) tween = filterArray(tweens, t => (insTime < t.end))[0] || tween; |
729 | 749 | const elapsed = minMaxValue(insTime - tween.start - tween.delay, 0, tween.duration) / tween.duration;
|
730 | 750 | const eased = isNaN(elapsed) ? 1 : tween.easing(elapsed, tween.elasticity);
|
| 751 | + const strings = tween.to.strings; |
731 | 752 | const round = tween.round;
|
732 |
| - const progress = recomposeValue(tween.to.numbers.map((number, p) => { |
733 |
| - const start = tween.from.numbers[p]; |
734 |
| - let value = start + eased * (number - start); |
735 |
| - if (tween.isPath) value = getPathProgress(tween.value, value); |
736 |
| - if (round) value = Math.round(value * round) / round; |
737 |
| - return value; |
738 |
| - }), tween.to.strings); |
| 753 | + let numbers = []; |
| 754 | + let progress; |
| 755 | + const toNumbersLength = tween.to.numbers.length; |
| 756 | + for (let n = 0; n < toNumbersLength; n++) { |
| 757 | + let value; |
| 758 | + const toNumber = tween.to.numbers[n]; |
| 759 | + const fromNumber = tween.from.numbers[n]; |
| 760 | + if (!tween.isPath) { |
| 761 | + value = fromNumber + (eased * (toNumber - fromNumber)); |
| 762 | + } else { |
| 763 | + value = getPathProgress(tween.value, eased * toNumber); |
| 764 | + } |
| 765 | + if (round) { |
| 766 | + if (!(tween.isColor && n > 2)) { |
| 767 | + value = Math.round(value * round) / round; |
| 768 | + } |
| 769 | + } |
| 770 | + numbers.push(value); |
| 771 | + } |
| 772 | + // Manual Array.reduce for better performances |
| 773 | + const stringsLength = strings.length; |
| 774 | + if (!stringsLength) { |
| 775 | + progress = numbers[0]; |
| 776 | + } else { |
| 777 | + progress = strings[0]; |
| 778 | + for (let s = 0; s < stringsLength; s++) { |
| 779 | + const a = strings[s]; |
| 780 | + const b = strings[s + 1]; |
| 781 | + const n = numbers[s]; |
| 782 | + if (!isNaN(n)) { |
| 783 | + if (!b) { |
| 784 | + progress += n + ' '; |
| 785 | + } else { |
| 786 | + progress += n + b; |
| 787 | + } |
| 788 | + } |
| 789 | + } |
| 790 | + } |
739 | 791 | setTweenProgress[anim.type](animatable.target, anim.property, progress, transforms, animatable.id);
|
740 | 792 | anim.currentValue = progress;
|
741 | 793 | i++;
|
742 | 794 | }
|
743 |
| - if (transforms) { |
744 |
| - let id; for (id in transforms) { |
| 795 | + const transformsLength = Object.keys(transforms).length; |
| 796 | + if (transformsLength) { |
| 797 | + for (let id = 0; id < transformsLength; id++) { |
745 | 798 | if (!transformString) {
|
746 | 799 | const t = 'transform';
|
747 | 800 | transformString = (getCSSValue(document.body, t) ? t : `-webkit-${t}`);
|
|
766 | 819 | function setInstanceProgress(engineTime) {
|
767 | 820 | const insDuration = instance.duration;
|
768 | 821 | const insOffset = instance.offset;
|
769 |
| - const insDelay = instance.delay; |
| 822 | + const insStart = insOffset + instance.delay; |
770 | 823 | const insCurrentTime = instance.currentTime;
|
771 | 824 | const insReversed = instance.reversed;
|
772 | 825 | const insTime = adjustTime(engineTime);
|
773 | 826 | if (instance.children.length) syncInstanceChildren(insTime);
|
774 |
| - if (insTime >= insDelay) { |
775 |
| - setCallback('run'); |
| 827 | + if (insTime >= insStart || !insDuration) { |
776 | 828 | if (!instance.began) {
|
777 | 829 | instance.began = true;
|
778 | 830 | setCallback('begin');
|
779 | 831 | }
|
| 832 | + setCallback('run'); |
780 | 833 | }
|
781 | 834 | if (insTime > insOffset && insTime < insDuration) {
|
782 | 835 | setAnimationsProgress(insTime);
|
|
785 | 838 | setAnimationsProgress(0);
|
786 | 839 | if (insReversed) countIteration();
|
787 | 840 | }
|
788 |
| - if (insTime >= insDuration && insCurrentTime !== insDuration) { |
| 841 | + if ((insTime >= insDuration && insCurrentTime !== insDuration) || !insDuration) { |
789 | 842 | setAnimationsProgress(insDuration);
|
790 | 843 | if (!insReversed) countIteration();
|
791 | 844 | }
|
|
904 | 957 | const tlDuration = tl.duration;
|
905 | 958 | const insOffset = insParams.offset;
|
906 | 959 | insParams.autoplay = false;
|
| 960 | + insParams.direction = tl.direction; |
907 | 961 | insParams.offset = is.und(insOffset) ? tlDuration : getRelativeValue(insOffset, tlDuration);
|
908 | 962 | tl.began = true;
|
909 | 963 | tl.completed = true;
|
|
922 | 976 | return tl;
|
923 | 977 | }
|
924 | 978 |
|
925 |
| - anime.version = '2.1.0'; |
| 979 | + anime.version = '2.2.0'; |
926 | 980 | anime.speed = 1;
|
927 | 981 | anime.running = activeInstances;
|
928 | 982 | anime.remove = removeTargets;
|
|
0 commit comments