UNPKG

@teamhive/lottie-player

Version:

Lottie animation web component.

979 lines (978 loc) 32.2 kB
import { h } from "@stencil/core"; import lottie from 'lottie-web/build/player/lottie_svg'; import { parseSrc } from '../../utils/parse-src'; // import { PlayerEvents } from '../../utils/player-events'; import { PlayerState } from '../../utils/player-state'; import { PlayMode } from '../../utils/player-mode'; export class LottiePlayer { constructor() { /** * Play mode. */ this.mode = PlayMode.Normal; /** * Autoplay animation on load */ this.autoplay = false; /** * Background color. */ this.background = 'transparent'; /** * Show controls. */ this.controls = false; /** * Direction of animation */ this.direction = 1; /** * Whether to play on mouse hover */ this.hover = false; /** * Whether to loop animation */ this.loop = false; /** * Renderer to use. */ this.renderer = 'svg'; /** * Animation speed. */ this.speed = 1; /** * Player state. */ this.currentState = PlayerState.Loading; this.intermission = 1; this._counter = 0; } componentDidLoad() { // Add intersection observer for detecting component being out-of-view. if ('IntersectionObserver' in window) { this._io = new IntersectionObserver((entries) => { if (entries[0].isIntersecting) { if (this.currentState === PlayerState.Frozen) { this.play(); } } else if (this.currentState === PlayerState.Playing) { this.freeze(); } }); this._io.observe(this.container); } // Add listener for Visibility API's change event. if (typeof document.hidden !== 'undefined') { document.addEventListener('visibilitychange', () => this.onVisibilityChange()); } // Setup lottie player if (this.src) { this.load(this.src); } } load(src) { const options = { container: this.container, loop: false, autoplay: false, renderer: this.renderer, rendererSettings: { scaleMode: 'noScale', clearCanvas: false, progressiveLoad: true, hideOnTransparent: true, }, }; try { const srcParsed = parseSrc(src); const srcAttrib = typeof srcParsed === 'string' ? 'path' : 'animationData'; // Clear previous animation, if any if (this._lottie) { this._lottie.destroy(); } // Initialize lottie player and load animation this._lottie = lottie.loadAnimation(Object.assign(Object.assign({}, options), { [srcAttrib]: srcParsed })); } catch (err) { this.currentState = PlayerState.Error; this.error.emit(); return; } if (this._lottie) { // Calculate and save the current progress of the animation this._lottie.addEventListener('enterFrame', () => { this.seeker = (this._lottie.currentFrame / this._lottie.totalFrames) * 100; this.frame.emit({ frame: this._lottie.currentFrame, seeker: this.seeker }); }); // Handle animation play complete this._lottie.addEventListener('complete', () => { if (this.currentState !== PlayerState.Playing) { this.complete.emit(); return; } if (!this.loop || (this.count && this._counter >= this.count)) { this.complete.emit(); return; } if (this.mode === PlayMode.Bounce) { if (this.count) { this._counter += 0.5; } setTimeout(() => { this.looped.emit(); if (this.currentState === PlayerState.Playing) { this._lottie.setDirection(this._lottie.playDirection * -1); this._lottie.play(); } }, this.intermission); } else { if (this.count) { this._counter += 1; } window.setTimeout(() => { this.looped.emit(); if (this.currentState === PlayerState.Playing) { this._lottie.stop(); this._lottie.play(); } }, this.intermission); } }); // Handle lottie-web ready event this._lottie.addEventListener('DOMLoaded', () => { this.ready.emit(); }); // Handle animation data load complete this._lottie.addEventListener('data_ready', () => { this.loaded.emit(); }); // Set error state when animation load fail event triggers this._lottie.addEventListener('data_failed', () => { this.currentState = PlayerState.Error; this.error.emit(); }); // Set handlers to auto play animation on hover if enabled this.container.addEventListener('mouseenter', () => { if (this.hover && this.currentState !== PlayerState.Playing) { this.play(); } }); this.container.addEventListener('mouseleave', () => { if (this.hover && this.currentState === PlayerState.Playing) { this.stop(); } }); // Set initial playback speed and direction this.setSpeed(this.speed); this.setDirection(this.direction); // Start playing if autoplay is enabled if (this.autoplay) { this.play(); } } } /** * Start playing animation. */ async play() { if (!this._lottie) { return; } this._lottie.play(); this.currentState = PlayerState.Playing; this.playing.emit(); } /** * Stop playing animation. */ async pause() { if (!this._lottie) { return; } this._lottie.pause(); this.currentState = PlayerState.Paused; this.paused.emit(); } /** * Stops animation play. */ async stop() { if (!this._lottie) { return; } this._counter = 0; this._lottie.stop(); this.currentState = PlayerState.Stopped; this.stopped.emit(); } /** * Seek to a given frame. */ async seek(value) { if (!this._lottie) { return; } // Extract frame number from either number or percentage value const matches = value.toString().match(/^([0-9]+)(%?)$/); if (!matches) { return; } // Calculate and set the frame number const frame = matches[2] === '%' ? this._lottie.totalFrames * Number(matches[1]) / 100 : matches[1]; // Set seeker to new frame number this.seeker = frame; // Send lottie player to the new frame if (this.currentState === PlayerState.Playing) { this._lottie.goToAndPlay(frame, true); } else { this._lottie.goToAndStop(frame, true); this._lottie.pause(); } } /** * Freeze animation play. * This internal state pauses animation and is used to differentiate between * user requested pauses and component instigated pauses. */ freeze() { if (!this._lottie) { return; } this._lottie.pause; this.currentState = PlayerState.Frozen; this.freezed.emit(); } async getLottie() { return this._lottie; } /** * Sets animation play speed * @param value Playback speed. */ async setSpeed(value = 1) { if (!this._lottie) { return; } this._lottie.setSpeed(value); } /** * Animation play direction. * @param value Direction values. */ async setDirection(value) { if (!this._lottie) { return; } this._lottie.setDirection(value); } /** * Sets the looping of the animation. * * @param value Whether to enable looping. Boolean true enables looping. */ async setLooping(value) { if (this._lottie) { this.loop = value; this._lottie.loop = value; } } /** * Toggle playing state. */ async togglePlay() { return this.currentState === PlayerState.Playing ? this.pause() : this.play(); } /** * Toggles animation looping. */ async toggleLooping() { this.setLooping(!this.loop); } renderButtonIcon(isPlaying) { if (isPlaying) { return (h("svg", { width: "24", height: "24" }, h("path", { d: "M14.016 5.016H18v13.969h-3.984V5.016zM6 18.984V5.015h3.984v13.969H6z" }))); } return (h("svg", { width: "24", height: "24" }, h("path", { d: "M8.016 5.016L18.985 12 8.016 18.984V5.015z" }))); } renderControls() { const isPlaying = this.currentState === PlayerState.Playing; const isPaused = this.currentState === PlayerState.Paused; const isStopped = this.currentState === PlayerState.Stopped; return (h("div", { class: 'toolbar' }, h("button", { class: { active: isPlaying || isPaused }, onClick: () => this.togglePlay() }, this.renderButtonIcon(isPlaying)), h("button", { class: { active: isStopped }, onClick: () => this.stop() }, h("svg", { width: "24", height: "24" }, h("path", { d: "M6 6h12v12H6V6z" }))), h("input", { class: "seeker", type: "range", min: "0", step: "1", max: "100", value: this.seeker, onInput: e => this.handleSeekChange(e), onMouseDown: () => { this._prevState = this.currentState; this.freeze(); }, onMouseUp: () => { this._prevState === PlayerState.Playing && this.play(); } }), h("button", { class: { 'active': this.loop }, onClick: () => this.toggleLooping() }, h("svg", { width: "24", height: "24" }, h("path", { d: "M17.016 17.016v-4.031h1.969v6h-12v3l-3.984-3.984 3.984-3.984v3h10.031zM6.984 6.984v4.031H5.015v-6h12v-3l3.984 3.984-3.984 3.984v-3H6.984z" }))), h("a", { href: "https://www.lottiefiles.com/", target: "_blank" }, h("svg", { width: "24", height: "24", viewBox: "0 0 320 320", "fill-rule": "nonzero" }, h("rect", { fill: "#adadad", x: ".5", y: ".5", width: "100%", height: "100%", rx: "26.73" }), h("path", { d: "M251.304 65.44a16.55 16.55 0 0 1 13.927 18.789c-1.333 9.04-9.73 15.292-18.762 13.954-15.992-2.37-39.95 22.534-66.77 73.74-34.24 65.37-66.113 96.517-99.667 94.032-9.102-.674-15.93-8.612-15.258-17.723s8.592-15.96 17.695-15.286c16.57 1.227 40.908-24.737 67.97-76.4 34.46-65.79 66.764-96.157 100.866-91.105z", fill: "#fff" }))))); } render() { return (h("div", { class: { main: true, controls: this.controls } }, h("div", { class: "animation", style: { background: this.background }, ref: ref => this.container = ref }, this.currentState === PlayerState.Error ? h("div", { class: "error" }, "\u26A0\uFE0F") : undefined), this.controls ? this.renderControls() : undefined)); } /** * Handle visibility change events. */ onVisibilityChange() { if (document.hidden === true && this.currentState === PlayerState.Playing) { this.freeze(); } else if (this.currentState === PlayerState.Frozen) { this.play(); } } /** * Handles click and drag actions on the progress track. */ handleSeekChange(e) { if (!this._lottie || isNaN(e.target.value)) { return; } const frame = ((e.target.value / 100) * this._lottie.totalFrames); this.seek(frame); } static get is() { return "lottie-player"; } static get encapsulation() { return "shadow"; } static get originalStyleUrls() { return { "$": ["lottie-player.css"] }; } static get styleUrls() { return { "$": ["lottie-player.css"] }; } static get properties() { return { "mode": { "type": "string", "mutable": false, "complexType": { "original": "PlayMode", "resolved": "PlayMode.Bounce | PlayMode.Normal", "references": { "PlayMode": { "location": "import", "path": "../../utils/player-mode" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Play mode." }, "attribute": "mode", "reflect": false, "defaultValue": "PlayMode.Normal" }, "autoplay": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Autoplay animation on load" }, "attribute": "autoplay", "reflect": false, "defaultValue": "false" }, "background": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "Background color." }, "attribute": "background", "reflect": true, "defaultValue": "'transparent'" }, "controls": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Show controls." }, "attribute": "controls", "reflect": false, "defaultValue": "false" }, "count": { "type": "number", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "Number of times to loop animation." }, "attribute": "count", "reflect": false }, "direction": { "type": "number", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Direction of animation" }, "attribute": "direction", "reflect": false, "defaultValue": "1" }, "hover": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Whether to play on mouse hover" }, "attribute": "hover", "reflect": false, "defaultValue": "false" }, "loop": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Whether to loop animation" }, "attribute": "loop", "reflect": true, "defaultValue": "false" }, "renderer": { "type": "string", "mutable": false, "complexType": { "original": "'svg'", "resolved": "\"svg\"", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Renderer to use." }, "attribute": "renderer", "reflect": false, "defaultValue": "'svg'" }, "speed": { "type": "number", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Animation speed." }, "attribute": "speed", "reflect": false, "defaultValue": "1" }, "src": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "Bodymovin JSON data or URL to json." }, "attribute": "src", "reflect": false }, "currentState": { "type": "string", "mutable": false, "complexType": { "original": "PlayerState", "resolved": "PlayerState.Error | PlayerState.Frozen | PlayerState.Loading | PlayerState.Paused | PlayerState.Playing | PlayerState.Stopped", "references": { "PlayerState": { "location": "import", "path": "../../utils/player-state" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Player state." }, "attribute": "current-state", "reflect": false, "defaultValue": "PlayerState.Loading" }, "seeker": { "type": "any", "mutable": false, "complexType": { "original": "any", "resolved": "any", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "" }, "attribute": "seeker", "reflect": false }, "intermission": { "type": "number", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "" }, "attribute": "intermission", "reflect": false, "defaultValue": "1" } }; } static get events() { return [{ "method": "error", "name": "error", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "" }, "complexType": { "original": "any", "resolved": "any", "references": {} } }, { "method": "frame", "name": "frame", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "" }, "complexType": { "original": "any", "resolved": "any", "references": {} } }, { "method": "complete", "name": "complete", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "" }, "complexType": { "original": "any", "resolved": "any", "references": {} } }, { "method": "looped", "name": "looped", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "" }, "complexType": { "original": "any", "resolved": "any", "references": {} } }, { "method": "ready", "name": "ready", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "" }, "complexType": { "original": "any", "resolved": "any", "references": {} } }, { "method": "loaded", "name": "loaded", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "" }, "complexType": { "original": "any", "resolved": "any", "references": {} } }, { "method": "playing", "name": "playing", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "" }, "complexType": { "original": "any", "resolved": "any", "references": {} } }, { "method": "paused", "name": "paused", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "" }, "complexType": { "original": "any", "resolved": "any", "references": {} } }, { "method": "stopped", "name": "stopped", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "" }, "complexType": { "original": "any", "resolved": "any", "references": {} } }, { "method": "freezed", "name": "freezed", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "" }, "complexType": { "original": "any", "resolved": "any", "references": {} } }]; } static get methods() { return { "play": { "complexType": { "signature": "() => Promise<void>", "parameters": [], "references": { "Promise": { "location": "global" } }, "return": "Promise<void>" }, "docs": { "text": "Start playing animation.", "tags": [] } }, "pause": { "complexType": { "signature": "() => Promise<void>", "parameters": [], "references": { "Promise": { "location": "global" } }, "return": "Promise<void>" }, "docs": { "text": "Stop playing animation.", "tags": [] } }, "stop": { "complexType": { "signature": "() => Promise<void>", "parameters": [], "references": { "Promise": { "location": "global" } }, "return": "Promise<void>" }, "docs": { "text": "Stops animation play.", "tags": [] } }, "seek": { "complexType": { "signature": "(value: string | number) => Promise<void>", "parameters": [{ "tags": [], "text": "" }], "references": { "Promise": { "location": "global" } }, "return": "Promise<void>" }, "docs": { "text": "Seek to a given frame.", "tags": [] } }, "getLottie": { "complexType": { "signature": "() => Promise<any>", "parameters": [], "references": { "Promise": { "location": "global" } }, "return": "Promise<any>" }, "docs": { "text": "", "tags": [] } }, "setSpeed": { "complexType": { "signature": "(value?: number) => Promise<void>", "parameters": [{ "tags": [{ "text": "value Playback speed.", "name": "param" }], "text": "Playback speed." }], "references": { "Promise": { "location": "global" } }, "return": "Promise<void>" }, "docs": { "text": "Sets animation play speed", "tags": [{ "name": "param", "text": "value Playback speed." }] } }, "setDirection": { "complexType": { "signature": "(value: number) => Promise<void>", "parameters": [{ "tags": [{ "text": "value Direction values.", "name": "param" }], "text": "Direction values." }], "references": { "Promise": { "location": "global" } }, "return": "Promise<void>" }, "docs": { "text": "Animation play direction.", "tags": [{ "name": "param", "text": "value Direction values." }] } }, "setLooping": { "complexType": { "signature": "(value: boolean) => Promise<void>", "parameters": [{ "tags": [{ "text": "value Whether to enable looping. Boolean true enables looping.", "name": "param" }], "text": "Whether to enable looping. Boolean true enables looping." }], "references": { "Promise": { "location": "global" } }, "return": "Promise<void>" }, "docs": { "text": "Sets the looping of the animation.", "tags": [{ "name": "param", "text": "value Whether to enable looping. Boolean true enables looping." }] } }, "togglePlay": { "complexType": { "signature": "() => Promise<void>", "parameters": [], "references": { "Promise": { "location": "global" } }, "return": "Promise<void>" }, "docs": { "text": "Toggle playing state.", "tags": [] } }, "toggleLooping": { "complexType": { "signature": "() => Promise<void>", "parameters": [], "references": { "Promise": { "location": "global" } }, "return": "Promise<void>" }, "docs": { "text": "Toggles animation looping.", "tags": [] } } }; } }