@teamhive/lottie-player
Version:
Lottie animation web component.
979 lines (978 loc) • 32.2 kB
JavaScript
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": []
}
}
}; }
}