2021-03-06 17:01:39 +00:00
|
|
|
import { LitElement, customElement, html, property, queryAsync, unsafeCSS } from 'lit-element';
|
|
|
|
import { event, EventEmitter } from '../../internal/event';
|
2021-02-26 14:09:13 +00:00
|
|
|
import styles from 'sass:./animation.scss';
|
2020-12-29 20:21:15 +00:00
|
|
|
import { animations } from './animations';
|
2020-08-10 14:45:46 +00:00
|
|
|
|
2020-08-10 14:04:25 +00:00
|
|
|
/**
|
|
|
|
* @since 2.0
|
2020-09-02 21:45:59 +00:00
|
|
|
* @status stable
|
2020-08-10 14:04:25 +00:00
|
|
|
*
|
|
|
|
* @slot - The element to animate. If multiple elements are to be animated, wrap them in a single container.
|
|
|
|
*/
|
2021-03-06 17:01:39 +00:00
|
|
|
@customElement('sl-animation')
|
|
|
|
export class SlAnimation extends LitElement {
|
|
|
|
static styles = unsafeCSS(styles);
|
2021-02-26 14:09:13 +00:00
|
|
|
|
|
|
|
private animation: Animation;
|
|
|
|
private hasStarted = false;
|
2020-08-10 14:04:25 +00:00
|
|
|
|
2021-03-06 17:01:39 +00:00
|
|
|
@queryAsync('slot') defaultSlot: Promise<HTMLSlotElement>;
|
|
|
|
|
2020-12-29 20:21:15 +00:00
|
|
|
/** The name of the built-in animation to use. For custom animations, use the `keyframes` prop. */
|
2021-03-06 17:01:39 +00:00
|
|
|
@property({ reflect: true }) name = 'none';
|
2020-08-10 14:04:25 +00:00
|
|
|
|
|
|
|
/** The number of milliseconds to delay the start of the animation. */
|
2021-03-06 17:01:39 +00:00
|
|
|
@property({ type: Number }) delay = 0;
|
2020-08-10 14:04:25 +00:00
|
|
|
|
2020-08-11 13:32:10 +00:00
|
|
|
/** Determines the direction of playback as well as the behavior when reaching the end of an iteration. */
|
2021-03-06 17:01:39 +00:00
|
|
|
@property() direction: PlaybackDirection = 'normal';
|
2020-08-10 14:04:25 +00:00
|
|
|
|
|
|
|
/** The number of milliseconds each iteration of the animation takes to complete. */
|
2021-03-06 17:01:39 +00:00
|
|
|
@property({ type: Number }) duration = 1000;
|
2020-08-10 14:04:25 +00:00
|
|
|
|
2020-12-29 20:21:15 +00:00
|
|
|
/**
|
|
|
|
* The easing function to use for the animation. This can be a Shoelace easing function or a custom easing function
|
|
|
|
* such as `cubic-bezier(0, 1, .76, 1.14)`.
|
|
|
|
*/
|
2021-03-06 17:01:39 +00:00
|
|
|
@property() easing = 'linear';
|
2020-08-10 14:04:25 +00:00
|
|
|
|
2020-08-11 13:32:10 +00:00
|
|
|
/** The number of milliseconds to delay after the active period of an animation sequence. */
|
2021-03-06 17:01:39 +00:00
|
|
|
@property({ type: Number }) endDelay = 0;
|
2020-08-10 14:04:25 +00:00
|
|
|
|
2020-08-11 13:32:10 +00:00
|
|
|
/** Sets how the animation applies styles to its target before and after its execution. */
|
2021-03-06 17:01:39 +00:00
|
|
|
@property() fill: FillMode = 'auto';
|
2020-08-10 14:04:25 +00:00
|
|
|
|
2020-08-11 13:32:10 +00:00
|
|
|
/** The number of iterations to run before the animation completes. Defaults to `Infinity`, which loops. */
|
2021-03-06 17:01:39 +00:00
|
|
|
@property({ type: Number }) iterations: number = Infinity;
|
2020-08-10 14:04:25 +00:00
|
|
|
|
2020-08-11 13:32:10 +00:00
|
|
|
/** The offset at which to start the animation, usually between 0 (start) and 1 (end). */
|
2021-03-06 17:01:39 +00:00
|
|
|
@property({ attribute: 'iteration-start', type: Number }) iterationStart = 0;
|
2020-08-10 14:04:25 +00:00
|
|
|
|
2020-08-11 13:32:10 +00:00
|
|
|
/** The keyframes to use for the animation. If this is set, `name` will be ignored. */
|
2021-03-06 17:01:39 +00:00
|
|
|
@property() keyframes: Keyframe[];
|
2020-08-10 14:04:25 +00:00
|
|
|
|
2020-08-11 13:32:10 +00:00
|
|
|
/**
|
|
|
|
* Sets the animation's playback rate. The default is `1`, which plays the animation at a normal speed. Setting this
|
|
|
|
* to `2`, for example, will double the animation's speed. A negative value can be used to reverse the animation. This
|
|
|
|
* value can be changed without causing the animation to restart.
|
|
|
|
*/
|
2021-03-06 17:01:39 +00:00
|
|
|
@property({ attribute: 'playback-rate', type: Number }) playbackRate = 1;
|
2020-08-10 14:04:25 +00:00
|
|
|
|
2020-08-11 13:32:10 +00:00
|
|
|
/** Pauses the animation. The animation will resume when this prop is removed. */
|
2021-03-06 17:01:39 +00:00
|
|
|
@property({ type: Boolean }) pause = false;
|
|
|
|
|
|
|
|
/** Emitted when the animation is canceled. */
|
|
|
|
@event('sl-cancel') slCancel: EventEmitter<void>;
|
|
|
|
|
|
|
|
/** Emitted when the animation finishes. */
|
|
|
|
@event('sl-finish') slFinish: EventEmitter<void>;
|
2020-08-10 14:04:25 +00:00
|
|
|
|
2021-03-06 17:01:39 +00:00
|
|
|
/** Emitted when the animation starts or restarts. */
|
|
|
|
@event('sl-start') slStart: EventEmitter<void>;
|
|
|
|
|
|
|
|
connectedCallback() {
|
|
|
|
super.connectedCallback();
|
2020-08-11 13:32:10 +00:00
|
|
|
this.createAnimation();
|
2020-08-10 14:04:25 +00:00
|
|
|
}
|
|
|
|
|
2021-03-06 17:01:39 +00:00
|
|
|
disconnectedCallback() {
|
|
|
|
super.disconnectedCallback();
|
2020-08-11 13:32:10 +00:00
|
|
|
this.destroyAnimation();
|
2020-08-10 14:04:25 +00:00
|
|
|
}
|
|
|
|
|
2021-03-06 17:01:39 +00:00
|
|
|
update(changedProps: Map<string, any>) {
|
|
|
|
super.update(changedProps);
|
|
|
|
|
|
|
|
if (changedProps.has('pause')) {
|
|
|
|
this.handlePauseChange();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (changedProps.has('playbackRate')) {
|
|
|
|
this.handlePlaybackRateChange();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
[
|
|
|
|
'name',
|
|
|
|
'delay',
|
|
|
|
'direction',
|
|
|
|
'duration',
|
|
|
|
'easing',
|
|
|
|
'endDelay',
|
|
|
|
'fill',
|
|
|
|
'iterations',
|
|
|
|
'iterationsStart',
|
|
|
|
'keyframes'
|
|
|
|
].find(prop => changedProps.has(prop))
|
|
|
|
) {
|
|
|
|
this.createAnimation();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-11 13:32:10 +00:00
|
|
|
handleAnimationFinish() {
|
2021-03-06 17:01:39 +00:00
|
|
|
this.slFinish.emit();
|
2020-08-10 14:04:25 +00:00
|
|
|
}
|
|
|
|
|
2020-08-11 13:32:10 +00:00
|
|
|
handleAnimationCancel() {
|
2021-03-06 17:01:39 +00:00
|
|
|
this.slCancel.emit();
|
|
|
|
}
|
|
|
|
|
|
|
|
handlePauseChange() {
|
|
|
|
if (this.animation) {
|
|
|
|
this.pause ? this.animation.pause() : this.animation.play();
|
|
|
|
|
|
|
|
if (!this.pause && !this.hasStarted) {
|
|
|
|
this.hasStarted = true;
|
|
|
|
this.slStart.emit();
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
2021-02-26 14:09:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
handlePlaybackRateChange() {
|
2021-03-06 17:01:39 +00:00
|
|
|
if (this.animation) {
|
|
|
|
this.animation.playbackRate = this.playbackRate;
|
|
|
|
}
|
2020-08-10 14:04:25 +00:00
|
|
|
}
|
|
|
|
|
2020-09-02 21:45:59 +00:00
|
|
|
handleSlotChange() {
|
|
|
|
this.destroyAnimation();
|
|
|
|
this.createAnimation();
|
|
|
|
}
|
|
|
|
|
2021-03-06 17:01:39 +00:00
|
|
|
async createAnimation() {
|
2021-02-26 14:09:13 +00:00
|
|
|
const easing = animations.easings[this.easing] || this.easing;
|
|
|
|
const keyframes: Keyframe[] = this.keyframes ? this.keyframes : (animations as any)[this.name];
|
2021-03-06 17:01:39 +00:00
|
|
|
const slot = await this.defaultSlot;
|
|
|
|
const element = slot.assignedElements()[0] as HTMLElement;
|
2020-09-02 21:45:59 +00:00
|
|
|
|
|
|
|
if (!element) {
|
2021-03-06 17:01:39 +00:00
|
|
|
return false;
|
2020-09-02 21:45:59 +00:00
|
|
|
}
|
2020-08-10 14:04:25 +00:00
|
|
|
|
2020-08-11 13:32:10 +00:00
|
|
|
this.destroyAnimation();
|
2020-09-02 21:45:59 +00:00
|
|
|
this.animation = element.animate(keyframes, {
|
2020-08-11 13:32:10 +00:00
|
|
|
delay: this.delay,
|
|
|
|
direction: this.direction,
|
|
|
|
duration: this.duration,
|
|
|
|
easing,
|
|
|
|
endDelay: this.endDelay,
|
|
|
|
fill: this.fill,
|
|
|
|
iterationStart: this.iterationStart,
|
|
|
|
iterations: this.iterations
|
|
|
|
});
|
2020-08-12 11:35:00 +00:00
|
|
|
this.animation.playbackRate = this.playbackRate;
|
2020-08-11 13:32:10 +00:00
|
|
|
this.animation.addEventListener('cancel', this.handleAnimationCancel);
|
|
|
|
this.animation.addEventListener('finish', this.handleAnimationFinish);
|
2020-08-10 14:04:25 +00:00
|
|
|
|
2020-08-11 13:32:10 +00:00
|
|
|
if (this.pause) {
|
|
|
|
this.animation.pause();
|
2020-08-11 22:47:02 +00:00
|
|
|
} else {
|
|
|
|
this.hasStarted = true;
|
2021-03-06 17:01:39 +00:00
|
|
|
this.slStart.emit();
|
2020-08-11 13:32:10 +00:00
|
|
|
}
|
2021-03-06 17:01:39 +00:00
|
|
|
|
|
|
|
return true;
|
2020-08-10 14:04:25 +00:00
|
|
|
}
|
|
|
|
|
2020-08-11 13:32:10 +00:00
|
|
|
destroyAnimation() {
|
|
|
|
if (this.animation) {
|
|
|
|
this.animation.cancel();
|
|
|
|
this.animation.removeEventListener('cancel', this.handleAnimationCancel);
|
|
|
|
this.animation.removeEventListener('finish', this.handleAnimationFinish);
|
2020-08-11 22:47:02 +00:00
|
|
|
this.hasStarted = false;
|
2020-08-11 13:32:10 +00:00
|
|
|
}
|
2020-08-10 14:04:25 +00:00
|
|
|
}
|
|
|
|
|
2020-08-11 13:32:10 +00:00
|
|
|
/** Clears all KeyframeEffects caused by this animation and aborts its playback. */
|
2021-02-26 14:09:13 +00:00
|
|
|
cancel() {
|
2020-08-11 13:32:10 +00:00
|
|
|
try {
|
|
|
|
this.animation.cancel();
|
|
|
|
} catch {}
|
2020-08-10 14:04:25 +00:00
|
|
|
}
|
|
|
|
|
2020-08-11 13:32:10 +00:00
|
|
|
/** Sets the playback time to the end of the animation corresponding to the current playback direction. */
|
2021-02-26 14:09:13 +00:00
|
|
|
finish() {
|
2020-08-11 13:32:10 +00:00
|
|
|
try {
|
|
|
|
this.animation.finish();
|
|
|
|
} catch {}
|
2020-08-10 14:04:25 +00:00
|
|
|
}
|
|
|
|
|
2020-08-11 13:32:10 +00:00
|
|
|
/** Gets the current time of the animation in milliseconds. */
|
2021-02-26 14:09:13 +00:00
|
|
|
getCurrentTime() {
|
2020-08-11 13:32:10 +00:00
|
|
|
return this.animation.currentTime;
|
2020-08-10 14:04:25 +00:00
|
|
|
}
|
|
|
|
|
2020-08-11 13:32:10 +00:00
|
|
|
/** Sets the current time of the animation in milliseconds. */
|
2021-02-26 14:09:13 +00:00
|
|
|
setCurrentTime(time: number) {
|
2020-08-11 13:32:10 +00:00
|
|
|
this.animation.currentTime = time;
|
2020-08-10 14:04:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
2021-03-06 17:01:39 +00:00
|
|
|
return html` <slot @slotchange=${this.handleSlotChange}></slot> `;
|
2020-08-10 14:04:25 +00:00
|
|
|
}
|
|
|
|
}
|