UNPKG

28.2 kBJavaScriptView Raw
1'use strict';
2
3const helpers = require('./helpers-d381ec4d.js');
4
5let animationPrefix;
6/**
7 * Web Animations requires hyphenated CSS properties
8 * to be written in camelCase when animating
9 */
10const processKeyframes = (keyframes) => {
11 keyframes.forEach(keyframe => {
12 for (const key in keyframe) {
13 if (keyframe.hasOwnProperty(key)) {
14 const value = keyframe[key];
15 if (key === 'easing') {
16 const newKey = 'animation-timing-function';
17 keyframe[newKey] = value;
18 delete keyframe[key];
19 }
20 else {
21 const newKey = convertCamelCaseToHypen(key);
22 if (newKey !== key) {
23 keyframe[newKey] = value;
24 delete keyframe[key];
25 }
26 }
27 }
28 }
29 });
30 return keyframes;
31};
32const convertCamelCaseToHypen = (str) => {
33 return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
34};
35const getAnimationPrefix = (el) => {
36 if (animationPrefix === undefined) {
37 const supportsUnprefixed = el.style.animationName !== undefined;
38 const supportsWebkitPrefix = el.style.webkitAnimationName !== undefined;
39 animationPrefix = (!supportsUnprefixed && supportsWebkitPrefix) ? '-webkit-' : '';
40 }
41 return animationPrefix;
42};
43const setStyleProperty = (element, propertyName, value) => {
44 const prefix = propertyName.startsWith('animation') ? getAnimationPrefix(element) : '';
45 element.style.setProperty(prefix + propertyName, value);
46};
47const removeStyleProperty = (element, propertyName) => {
48 const prefix = propertyName.startsWith('animation') ? getAnimationPrefix(element) : '';
49 element.style.removeProperty(prefix + propertyName);
50};
51const animationEnd = (el, callback) => {
52 let unRegTrans;
53 const opts = { passive: true };
54 const unregister = () => {
55 if (unRegTrans) {
56 unRegTrans();
57 }
58 };
59 const onTransitionEnd = (ev) => {
60 if (el === ev.target) {
61 unregister();
62 callback(ev);
63 }
64 };
65 if (el) {
66 el.addEventListener('webkitAnimationEnd', onTransitionEnd, opts);
67 el.addEventListener('animationend', onTransitionEnd, opts);
68 unRegTrans = () => {
69 el.removeEventListener('webkitAnimationEnd', onTransitionEnd, opts);
70 el.removeEventListener('animationend', onTransitionEnd, opts);
71 };
72 }
73 return unregister;
74};
75const generateKeyframeRules = (keyframes = []) => {
76 return keyframes.map(keyframe => {
77 const offset = keyframe.offset;
78 const frameString = [];
79 for (const property in keyframe) {
80 if (keyframe.hasOwnProperty(property) && property !== 'offset') {
81 frameString.push(`${property}: ${keyframe[property]};`);
82 }
83 }
84 return `${offset * 100}% { ${frameString.join(' ')} }`;
85 }).join(' ');
86};
87const keyframeIds = [];
88const generateKeyframeName = (keyframeRules) => {
89 let index = keyframeIds.indexOf(keyframeRules);
90 if (index < 0) {
91 index = (keyframeIds.push(keyframeRules) - 1);
92 }
93 return `ion-animation-${index}`;
94};
95const getStyleContainer = (element) => {
96 const rootNode = element.getRootNode();
97 return (rootNode.head || rootNode);
98};
99const createKeyframeStylesheet = (keyframeName, keyframeRules, element) => {
100 const styleContainer = getStyleContainer(element);
101 const keyframePrefix = getAnimationPrefix(element);
102 const existingStylesheet = styleContainer.querySelector('#' + keyframeName);
103 if (existingStylesheet) {
104 return existingStylesheet;
105 }
106 const stylesheet = (element.ownerDocument || document).createElement('style');
107 stylesheet.id = keyframeName;
108 stylesheet.textContent = `@${keyframePrefix}keyframes ${keyframeName} { ${keyframeRules} } @${keyframePrefix}keyframes ${keyframeName}-alt { ${keyframeRules} }`;
109 styleContainer.appendChild(stylesheet);
110 return stylesheet;
111};
112const addClassToArray = (classes = [], className) => {
113 if (className !== undefined) {
114 const classNameToAppend = (Array.isArray(className)) ? className : [className];
115 return [...classes, ...classNameToAppend];
116 }
117 return classes;
118};
119
120const createAnimation = (animationId) => {
121 let _delay;
122 let _duration;
123 let _easing;
124 let _iterations;
125 let _fill;
126 let _direction;
127 let _keyframes = [];
128 let beforeAddClasses = [];
129 let beforeRemoveClasses = [];
130 let initialized = false;
131 let parentAnimation;
132 let beforeStylesValue = {};
133 let afterAddClasses = [];
134 let afterRemoveClasses = [];
135 let afterStylesValue = {};
136 let numAnimationsRunning = 0;
137 let shouldForceLinearEasing = false;
138 let shouldForceSyncPlayback = false;
139 let cssAnimationsTimerFallback;
140 let forceDirectionValue;
141 let forceDurationValue;
142 let forceDelayValue;
143 let willComplete = true;
144 let finished = false;
145 let shouldCalculateNumAnimations = true;
146 let keyframeName;
147 let ani;
148 const id = animationId;
149 const onFinishCallbacks = [];
150 const onFinishOneTimeCallbacks = [];
151 const elements = [];
152 const childAnimations = [];
153 const stylesheets = [];
154 const _beforeAddReadFunctions = [];
155 const _beforeAddWriteFunctions = [];
156 const _afterAddReadFunctions = [];
157 const _afterAddWriteFunctions = [];
158 const webAnimations = [];
159 const supportsAnimationEffect = (typeof AnimationEffect === 'function' || typeof window.AnimationEffect === 'function');
160 const supportsWebAnimations = (typeof Element === 'function') && (typeof Element.prototype.animate === 'function') && supportsAnimationEffect;
161 const ANIMATION_END_FALLBACK_PADDING_MS = 100;
162 const getWebAnimations = () => {
163 return webAnimations;
164 };
165 const destroy = (clearStyleSheets) => {
166 childAnimations.forEach(childAnimation => {
167 childAnimation.destroy(clearStyleSheets);
168 });
169 cleanUp(clearStyleSheets);
170 elements.length = 0;
171 childAnimations.length = 0;
172 _keyframes.length = 0;
173 clearOnFinish();
174 initialized = false;
175 shouldCalculateNumAnimations = true;
176 return ani;
177 };
178 /**
179 * Cancels any Web Animations, removes
180 * any animation properties from the
181 * animation's elements, and removes the
182 * animation's stylesheets from the DOM.
183 */
184 const cleanUp = (clearStyleSheets) => {
185 cleanUpElements();
186 if (clearStyleSheets) {
187 cleanUpStyleSheets();
188 }
189 };
190 const resetFlags = () => {
191 shouldForceLinearEasing = false;
192 shouldForceSyncPlayback = false;
193 shouldCalculateNumAnimations = true;
194 forceDirectionValue = undefined;
195 forceDurationValue = undefined;
196 forceDelayValue = undefined;
197 numAnimationsRunning = 0;
198 finished = false;
199 willComplete = true;
200 };
201 const onFinish = (callback, opts) => {
202 const callbacks = (opts && opts.oneTimeCallback) ? onFinishOneTimeCallbacks : onFinishCallbacks;
203 callbacks.push({ c: callback, o: opts });
204 return ani;
205 };
206 const clearOnFinish = () => {
207 onFinishCallbacks.length = 0;
208 onFinishOneTimeCallbacks.length = 0;
209 return ani;
210 };
211 /**
212 * Cancels any Web Animations and removes
213 * any animation properties from the
214 * the animation's elements.
215 */
216 const cleanUpElements = () => {
217 if (supportsWebAnimations) {
218 webAnimations.forEach(animation => {
219 animation.cancel();
220 });
221 webAnimations.length = 0;
222 }
223 else {
224 const elementsArray = elements.slice();
225 helpers.raf(() => {
226 elementsArray.forEach(element => {
227 removeStyleProperty(element, 'animation-name');
228 removeStyleProperty(element, 'animation-duration');
229 removeStyleProperty(element, 'animation-timing-function');
230 removeStyleProperty(element, 'animation-iteration-count');
231 removeStyleProperty(element, 'animation-delay');
232 removeStyleProperty(element, 'animation-play-state');
233 removeStyleProperty(element, 'animation-fill-mode');
234 removeStyleProperty(element, 'animation-direction');
235 });
236 });
237 }
238 };
239 /**
240 * Removes the animation's stylesheets
241 * from the DOM.
242 */
243 const cleanUpStyleSheets = () => {
244 stylesheets.forEach(stylesheet => {
245 /**
246 * When sharing stylesheets, it's possible
247 * for another animation to have already
248 * cleaned up a particular stylesheet
249 */
250 if (stylesheet && stylesheet.parentNode) {
251 stylesheet.parentNode.removeChild(stylesheet);
252 }
253 });
254 stylesheets.length = 0;
255 };
256 const beforeAddRead = (readFn) => {
257 _beforeAddReadFunctions.push(readFn);
258 return ani;
259 };
260 const beforeAddWrite = (writeFn) => {
261 _beforeAddWriteFunctions.push(writeFn);
262 return ani;
263 };
264 const afterAddRead = (readFn) => {
265 _afterAddReadFunctions.push(readFn);
266 return ani;
267 };
268 const afterAddWrite = (writeFn) => {
269 _afterAddWriteFunctions.push(writeFn);
270 return ani;
271 };
272 const beforeAddClass = (className) => {
273 beforeAddClasses = addClassToArray(beforeAddClasses, className);
274 return ani;
275 };
276 const beforeRemoveClass = (className) => {
277 beforeRemoveClasses = addClassToArray(beforeRemoveClasses, className);
278 return ani;
279 };
280 /**
281 * Set CSS inline styles to the animation's
282 * elements before the animation begins.
283 */
284 const beforeStyles = (styles = {}) => {
285 beforeStylesValue = styles;
286 return ani;
287 };
288 /**
289 * Clear CSS inline styles from the animation's
290 * elements before the animation begins.
291 */
292 const beforeClearStyles = (propertyNames = []) => {
293 for (const property of propertyNames) {
294 beforeStylesValue[property] = '';
295 }
296 return ani;
297 };
298 const afterAddClass = (className) => {
299 afterAddClasses = addClassToArray(afterAddClasses, className);
300 return ani;
301 };
302 const afterRemoveClass = (className) => {
303 afterRemoveClasses = addClassToArray(afterRemoveClasses, className);
304 return ani;
305 };
306 const afterStyles = (styles = {}) => {
307 afterStylesValue = styles;
308 return ani;
309 };
310 const afterClearStyles = (propertyNames = []) => {
311 for (const property of propertyNames) {
312 afterStylesValue[property] = '';
313 }
314 return ani;
315 };
316 const getFill = () => {
317 if (_fill !== undefined) {
318 return _fill;
319 }
320 if (parentAnimation) {
321 return parentAnimation.getFill();
322 }
323 return 'both';
324 };
325 const getDirection = () => {
326 if (forceDirectionValue !== undefined) {
327 return forceDirectionValue;
328 }
329 if (_direction !== undefined) {
330 return _direction;
331 }
332 if (parentAnimation) {
333 return parentAnimation.getDirection();
334 }
335 return 'normal';
336 };
337 const getEasing = () => {
338 if (shouldForceLinearEasing) {
339 return 'linear';
340 }
341 if (_easing !== undefined) {
342 return _easing;
343 }
344 if (parentAnimation) {
345 return parentAnimation.getEasing();
346 }
347 return 'linear';
348 };
349 const getDuration = () => {
350 if (shouldForceSyncPlayback) {
351 return 0;
352 }
353 if (forceDurationValue !== undefined) {
354 return forceDurationValue;
355 }
356 if (_duration !== undefined) {
357 return _duration;
358 }
359 if (parentAnimation) {
360 return parentAnimation.getDuration();
361 }
362 return 0;
363 };
364 const getIterations = () => {
365 if (_iterations !== undefined) {
366 return _iterations;
367 }
368 if (parentAnimation) {
369 return parentAnimation.getIterations();
370 }
371 return 1;
372 };
373 const getDelay = () => {
374 if (forceDelayValue !== undefined) {
375 return forceDelayValue;
376 }
377 if (_delay !== undefined) {
378 return _delay;
379 }
380 if (parentAnimation) {
381 return parentAnimation.getDelay();
382 }
383 return 0;
384 };
385 const getKeyframes = () => {
386 return _keyframes;
387 };
388 const direction = (animationDirection) => {
389 _direction = animationDirection;
390 update(true);
391 return ani;
392 };
393 const fill = (animationFill) => {
394 _fill = animationFill;
395 update(true);
396 return ani;
397 };
398 const delay = (animationDelay) => {
399 _delay = animationDelay;
400 update(true);
401 return ani;
402 };
403 const easing = (animationEasing) => {
404 _easing = animationEasing;
405 update(true);
406 return ani;
407 };
408 const duration = (animationDuration) => {
409 /**
410 * CSS Animation Durations of 0ms work fine on Chrome
411 * but do not run on Safari, so force it to 1ms to
412 * get it to run on both platforms.
413 */
414 if (!supportsWebAnimations && animationDuration === 0) {
415 animationDuration = 1;
416 }
417 _duration = animationDuration;
418 update(true);
419 return ani;
420 };
421 const iterations = (animationIterations) => {
422 _iterations = animationIterations;
423 update(true);
424 return ani;
425 };
426 const parent = (animation) => {
427 parentAnimation = animation;
428 return ani;
429 };
430 const addElement = (el) => {
431 if (el != null) {
432 if (el.nodeType === 1) {
433 elements.push(el);
434 }
435 else if (el.length >= 0) {
436 for (let i = 0; i < el.length; i++) {
437 elements.push(el[i]);
438 }
439 }
440 else {
441 console.error('Invalid addElement value');
442 }
443 }
444 return ani;
445 };
446 const addAnimation = (animationToAdd) => {
447 if (animationToAdd != null) {
448 if (Array.isArray(animationToAdd)) {
449 for (const animation of animationToAdd) {
450 animation.parent(ani);
451 childAnimations.push(animation);
452 }
453 }
454 else {
455 animationToAdd.parent(ani);
456 childAnimations.push(animationToAdd);
457 }
458 }
459 return ani;
460 };
461 const keyframes = (keyframeValues) => {
462 _keyframes = keyframeValues;
463 return ani;
464 };
465 /**
466 * Run all "before" animation hooks.
467 */
468 const beforeAnimation = () => {
469 // Runs all before read callbacks
470 _beforeAddReadFunctions.forEach(callback => callback());
471 // Runs all before write callbacks
472 _beforeAddWriteFunctions.forEach(callback => callback());
473 // Updates styles and classes before animation runs
474 const addClasses = beforeAddClasses;
475 const removeClasses = beforeRemoveClasses;
476 const styles = beforeStylesValue;
477 elements.forEach(el => {
478 const elementClassList = el.classList;
479 addClasses.forEach(c => elementClassList.add(c));
480 removeClasses.forEach(c => elementClassList.remove(c));
481 for (const property in styles) {
482 if (styles.hasOwnProperty(property)) {
483 setStyleProperty(el, property, styles[property]);
484 }
485 }
486 });
487 };
488 /**
489 * Run all "after" animation hooks.
490 */
491 const afterAnimation = () => {
492 clearCSSAnimationsTimeout();
493 // Runs all after read callbacks
494 _afterAddReadFunctions.forEach(callback => callback());
495 // Runs all after write callbacks
496 _afterAddWriteFunctions.forEach(callback => callback());
497 // Updates styles and classes before animation ends
498 const currentStep = willComplete ? 1 : 0;
499 const addClasses = afterAddClasses;
500 const removeClasses = afterRemoveClasses;
501 const styles = afterStylesValue;
502 elements.forEach(el => {
503 const elementClassList = el.classList;
504 addClasses.forEach(c => elementClassList.add(c));
505 removeClasses.forEach(c => elementClassList.remove(c));
506 for (const property in styles) {
507 if (styles.hasOwnProperty(property)) {
508 setStyleProperty(el, property, styles[property]);
509 }
510 }
511 });
512 onFinishCallbacks.forEach(onFinishCallback => {
513 return onFinishCallback.c(currentStep, ani);
514 });
515 onFinishOneTimeCallbacks.forEach(onFinishCallback => {
516 return onFinishCallback.c(currentStep, ani);
517 });
518 onFinishOneTimeCallbacks.length = 0;
519 shouldCalculateNumAnimations = true;
520 if (willComplete) {
521 finished = true;
522 }
523 willComplete = true;
524 };
525 const animationFinish = () => {
526 if (numAnimationsRunning === 0) {
527 return;
528 }
529 numAnimationsRunning--;
530 if (numAnimationsRunning === 0) {
531 afterAnimation();
532 if (parentAnimation) {
533 parentAnimation.animationFinish();
534 }
535 }
536 };
537 const initializeCSSAnimation = (toggleAnimationName = true) => {
538 cleanUpStyleSheets();
539 const processedKeyframes = processKeyframes(_keyframes);
540 elements.forEach(element => {
541 if (processedKeyframes.length > 0) {
542 const keyframeRules = generateKeyframeRules(processedKeyframes);
543 keyframeName = (animationId !== undefined) ? animationId : generateKeyframeName(keyframeRules);
544 const stylesheet = createKeyframeStylesheet(keyframeName, keyframeRules, element);
545 stylesheets.push(stylesheet);
546 setStyleProperty(element, 'animation-duration', `${getDuration()}ms`);
547 setStyleProperty(element, 'animation-timing-function', getEasing());
548 setStyleProperty(element, 'animation-delay', `${getDelay()}ms`);
549 setStyleProperty(element, 'animation-fill-mode', getFill());
550 setStyleProperty(element, 'animation-direction', getDirection());
551 const iterationsCount = (getIterations() === Infinity)
552 ? 'infinite'
553 : getIterations().toString();
554 setStyleProperty(element, 'animation-iteration-count', iterationsCount);
555 setStyleProperty(element, 'animation-play-state', 'paused');
556 if (toggleAnimationName) {
557 setStyleProperty(element, 'animation-name', `${stylesheet.id}-alt`);
558 }
559 helpers.raf(() => {
560 setStyleProperty(element, 'animation-name', stylesheet.id || null);
561 });
562 }
563 });
564 };
565 const initializeWebAnimation = () => {
566 elements.forEach(element => {
567 const animation = element.animate(_keyframes, {
568 id,
569 delay: getDelay(),
570 duration: getDuration(),
571 easing: getEasing(),
572 iterations: getIterations(),
573 fill: getFill(),
574 direction: getDirection()
575 });
576 animation.pause();
577 webAnimations.push(animation);
578 });
579 if (webAnimations.length > 0) {
580 webAnimations[0].onfinish = () => {
581 animationFinish();
582 };
583 }
584 };
585 const initializeAnimation = (toggleAnimationName = true) => {
586 beforeAnimation();
587 if (_keyframes.length > 0) {
588 if (supportsWebAnimations) {
589 initializeWebAnimation();
590 }
591 else {
592 initializeCSSAnimation(toggleAnimationName);
593 }
594 }
595 initialized = true;
596 };
597 const setAnimationStep = (step) => {
598 step = Math.min(Math.max(step, 0), 0.9999);
599 if (supportsWebAnimations) {
600 webAnimations.forEach(animation => {
601 animation.currentTime = animation.effect.getComputedTiming().delay + (getDuration() * step);
602 animation.pause();
603 });
604 }
605 else {
606 const animationDuration = `-${getDuration() * step}ms`;
607 elements.forEach(element => {
608 if (_keyframes.length > 0) {
609 setStyleProperty(element, 'animation-delay', animationDuration);
610 setStyleProperty(element, 'animation-play-state', 'paused');
611 }
612 });
613 }
614 };
615 const updateWebAnimation = (step) => {
616 webAnimations.forEach(animation => {
617 animation.effect.updateTiming({
618 delay: getDelay(),
619 duration: getDuration(),
620 easing: getEasing(),
621 iterations: getIterations(),
622 fill: getFill(),
623 direction: getDirection()
624 });
625 });
626 if (step !== undefined) {
627 setAnimationStep(step);
628 }
629 };
630 const updateCSSAnimation = (toggleAnimationName = true, step) => {
631 helpers.raf(() => {
632 elements.forEach(element => {
633 setStyleProperty(element, 'animation-name', keyframeName || null);
634 setStyleProperty(element, 'animation-duration', `${getDuration()}ms`);
635 setStyleProperty(element, 'animation-timing-function', getEasing());
636 setStyleProperty(element, 'animation-delay', (step !== undefined) ? `-${step * getDuration()}ms` : `${getDelay()}ms`);
637 setStyleProperty(element, 'animation-fill-mode', getFill() || null);
638 setStyleProperty(element, 'animation-direction', getDirection() || null);
639 const iterationsCount = (getIterations() === Infinity)
640 ? 'infinite'
641 : getIterations().toString();
642 setStyleProperty(element, 'animation-iteration-count', iterationsCount);
643 if (toggleAnimationName) {
644 setStyleProperty(element, 'animation-name', `${keyframeName}-alt`);
645 }
646 helpers.raf(() => {
647 setStyleProperty(element, 'animation-name', keyframeName || null);
648 });
649 });
650 });
651 };
652 const update = (deep = false, toggleAnimationName = true, step) => {
653 if (deep) {
654 childAnimations.forEach(animation => {
655 animation.update(deep, toggleAnimationName, step);
656 });
657 }
658 if (supportsWebAnimations) {
659 updateWebAnimation(step);
660 }
661 else {
662 updateCSSAnimation(toggleAnimationName, step);
663 }
664 return ani;
665 };
666 const progressStart = (forceLinearEasing = false, step) => {
667 childAnimations.forEach(animation => {
668 animation.progressStart(forceLinearEasing, step);
669 });
670 pauseAnimation();
671 shouldForceLinearEasing = forceLinearEasing;
672 if (!initialized) {
673 initializeAnimation();
674 }
675 else {
676 update(false, true, step);
677 }
678 return ani;
679 };
680 const progressStep = (step) => {
681 childAnimations.forEach(animation => {
682 animation.progressStep(step);
683 });
684 setAnimationStep(step);
685 return ani;
686 };
687 const progressEnd = (playTo, step, dur) => {
688 shouldForceLinearEasing = false;
689 childAnimations.forEach(animation => {
690 animation.progressEnd(playTo, step, dur);
691 });
692 if (dur !== undefined) {
693 forceDurationValue = dur;
694 }
695 finished = false;
696 // tslint:disable-next-line: strict-boolean-conditions
697 willComplete = true;
698 if (playTo === 0) {
699 forceDirectionValue = (getDirection() === 'reverse') ? 'normal' : 'reverse';
700 if (forceDirectionValue === 'reverse') {
701 willComplete = false;
702 }
703 if (supportsWebAnimations) {
704 update();
705 setAnimationStep(1 - step);
706 }
707 else {
708 forceDelayValue = ((1 - step) * getDuration()) * -1;
709 update(false, false);
710 }
711 }
712 else if (playTo === 1) {
713 if (supportsWebAnimations) {
714 update();
715 setAnimationStep(step);
716 }
717 else {
718 forceDelayValue = (step * getDuration()) * -1;
719 update(false, false);
720 }
721 }
722 if (playTo !== undefined) {
723 onFinish(() => {
724 forceDurationValue = undefined;
725 forceDirectionValue = undefined;
726 forceDelayValue = undefined;
727 }, {
728 oneTimeCallback: true
729 });
730 if (!parentAnimation) {
731 play();
732 }
733 }
734 return ani;
735 };
736 const pauseAnimation = () => {
737 if (initialized) {
738 if (supportsWebAnimations) {
739 webAnimations.forEach(animation => {
740 animation.pause();
741 });
742 }
743 else {
744 elements.forEach(element => {
745 setStyleProperty(element, 'animation-play-state', 'paused');
746 });
747 }
748 }
749 };
750 const pause = () => {
751 childAnimations.forEach(animation => {
752 animation.pause();
753 });
754 pauseAnimation();
755 return ani;
756 };
757 const onAnimationEndFallback = () => {
758 cssAnimationsTimerFallback = undefined;
759 animationFinish();
760 };
761 const clearCSSAnimationsTimeout = () => {
762 if (cssAnimationsTimerFallback) {
763 clearTimeout(cssAnimationsTimerFallback);
764 }
765 };
766 const playCSSAnimations = () => {
767 clearCSSAnimationsTimeout();
768 helpers.raf(() => {
769 elements.forEach(element => {
770 if (_keyframes.length > 0) {
771 setStyleProperty(element, 'animation-play-state', 'running');
772 }
773 });
774 });
775 if (_keyframes.length === 0 || elements.length === 0) {
776 animationFinish();
777 }
778 else {
779 /**
780 * This is a catchall in the event that a CSS Animation did not finish.
781 * The Web Animations API has mechanisms in place for preventing this.
782 * CSS Animations will not fire an `animationend` event
783 * for elements with `display: none`. The Web Animations API
784 * accounts for this, but using raw CSS Animations requires
785 * this workaround.
786 */
787 const animationDelay = getDelay() || 0;
788 const animationDuration = getDuration() || 0;
789 const animationIterations = getIterations() || 1;
790 // No need to set a timeout when animation has infinite iterations
791 if (isFinite(animationIterations)) {
792 cssAnimationsTimerFallback = setTimeout(onAnimationEndFallback, animationDelay + (animationDuration * animationIterations) + ANIMATION_END_FALLBACK_PADDING_MS);
793 }
794 animationEnd(elements[0], () => {
795 clearCSSAnimationsTimeout();
796 /**
797 * Ensure that clean up
798 * is always done a frame
799 * before the onFinish handlers
800 * are fired. Otherwise, there
801 * may be flickering if a new
802 * animation is started on the same
803 * element too quickly
804 *
805 * TODO: Is there a cleaner way to do this?
806 */
807 helpers.raf(() => {
808 clearCSSAnimationPlayState();
809 helpers.raf(animationFinish);
810 });
811 });
812 }
813 };
814 const clearCSSAnimationPlayState = () => {
815 elements.forEach(element => {
816 removeStyleProperty(element, 'animation-duration');
817 removeStyleProperty(element, 'animation-delay');
818 removeStyleProperty(element, 'animation-play-state');
819 });
820 };
821 const playWebAnimations = () => {
822 webAnimations.forEach(animation => {
823 animation.play();
824 });
825 if (_keyframes.length === 0 || elements.length === 0) {
826 animationFinish();
827 }
828 };
829 const resetAnimation = () => {
830 if (supportsWebAnimations) {
831 setAnimationStep(0);
832 updateWebAnimation();
833 }
834 else {
835 updateCSSAnimation();
836 }
837 };
838 const play = (opts) => {
839 return new Promise(resolve => {
840 if (opts && opts.sync) {
841 shouldForceSyncPlayback = true;
842 onFinish(() => shouldForceSyncPlayback = false, { oneTimeCallback: true });
843 }
844 if (!initialized) {
845 initializeAnimation();
846 }
847 if (finished) {
848 resetAnimation();
849 finished = false;
850 }
851 if (shouldCalculateNumAnimations) {
852 numAnimationsRunning = childAnimations.length + 1;
853 shouldCalculateNumAnimations = false;
854 }
855 onFinish(() => resolve(), { oneTimeCallback: true });
856 childAnimations.forEach(animation => {
857 animation.play();
858 });
859 if (supportsWebAnimations) {
860 playWebAnimations();
861 }
862 else {
863 playCSSAnimations();
864 }
865 });
866 };
867 const stop = () => {
868 childAnimations.forEach(animation => {
869 animation.stop();
870 });
871 if (initialized) {
872 cleanUpElements();
873 initialized = false;
874 }
875 resetFlags();
876 };
877 const from = (property, value) => {
878 const firstFrame = _keyframes[0];
879 if (firstFrame !== undefined && (firstFrame.offset === undefined || firstFrame.offset === 0)) {
880 firstFrame[property] = value;
881 }
882 else {
883 _keyframes = [
884 { offset: 0, [property]: value },
885 ..._keyframes
886 ];
887 }
888 return ani;
889 };
890 const to = (property, value) => {
891 const lastFrame = _keyframes[_keyframes.length - 1];
892 if (lastFrame !== undefined && (lastFrame.offset === undefined || lastFrame.offset === 1)) {
893 lastFrame[property] = value;
894 }
895 else {
896 _keyframes = [
897 ..._keyframes,
898 { offset: 1, [property]: value }
899 ];
900 }
901 return ani;
902 };
903 const fromTo = (property, fromValue, toValue) => {
904 return from(property, fromValue).to(property, toValue);
905 };
906 return ani = {
907 parentAnimation,
908 elements,
909 childAnimations,
910 id,
911 animationFinish,
912 from,
913 to,
914 fromTo,
915 parent,
916 play,
917 pause,
918 stop,
919 destroy,
920 keyframes,
921 addAnimation,
922 addElement,
923 update,
924 fill,
925 direction,
926 iterations,
927 duration,
928 easing,
929 delay,
930 getWebAnimations,
931 getKeyframes,
932 getFill,
933 getDirection,
934 getDelay,
935 getIterations,
936 getEasing,
937 getDuration,
938 afterAddRead,
939 afterAddWrite,
940 afterClearStyles,
941 afterStyles,
942 afterRemoveClass,
943 afterAddClass,
944 beforeAddRead,
945 beforeAddWrite,
946 beforeClearStyles,
947 beforeStyles,
948 beforeRemoveClass,
949 beforeAddClass,
950 onFinish,
951 progressStart,
952 progressStep,
953 progressEnd
954 };
955};
956
957exports.createAnimation = createAnimation;