import { isInDOM } from "./utils"; import LottiePlayer, { AnimationConfigWithPath, AnimationConfigWithData, AnimationItem } from "lottie-web/build/player/lottie.d"; let convert = (value: number) => { return Math.round(Math.min(Math.max(value, 0), 1) * 255); }; class LottieLoader { public lottie: /* any */ typeof LottiePlayer = null; private animations: { [group: string]: { animation: /* any */AnimationItem, container: HTMLDivElement, paused: boolean, autoplay: boolean, canvas: boolean }[] } = {}; private debug = false; public loaded: Promise; private lastTimeLoad = 0; private waitingTimeouts = 0; private static COLORREPLACEMENTS = [ [ [0xf77e41, 0xca907a], [0xffb139, 0xedc5a5], [0xffd140, 0xf7e3c3], [0xffdf79, 0xfbefd6], ], [ [0xf77e41, 0xaa7c60], [0xffb139, 0xc8a987], [0xffd140, 0xddc89f], [0xffdf79, 0xe6d6b2], ], [ [0xf77e41, 0x8c6148], [0xffb139, 0xad8562], [0xffd140, 0xc49e76], [0xffdf79, 0xd4b188], ], [ [0xf77e41, 0x6e3c2c], [0xffb139, 0x925a34], [0xffd140, 0xa16e46], [0xffdf79, 0xac7a52], ] ]; public loadLottie() { if(this.loaded) return this.loaded; return this.loaded = new Promise((resolve, reject) => { (window as any).lottieLoaded = () => { console.log('lottie loaded'); this.lottie = (window as any).lottie; resolve(); }; let sc = document.createElement('script'); sc.src = 'npm.lottie-web.chunk.js'; sc.async = true; sc.onload = (window as any).lottieLoaded; document.body.appendChild(sc); }); } public checkAnimations(blurred?: boolean, group?: string, destroy = false) { let groups = group ? [group] : Object.keys(this.animations); if(group && !this.animations[group]) { console.warn('no animation group:', group); this.animations[group] = []; //return; } for(let group of groups) { let animations = this.animations[group]; let length = animations.length; for(let i = length - 1; i >= 0; --i) { let {animation, container, paused, autoplay, canvas} = animations[i]; if(destroy && !isInDOM(container)) { this.debug && console.log('destroy animation'); animation.destroy(); animations.splice(i, 1); continue; } /* if(canvas) { let c = container.firstElementChild as HTMLCanvasElement; if(!c) { console.warn('no canvas element for check!', container, animations[i]); continue; } if(!c.height && !c.width && isElementInViewport(container)) { //console.log('lottie need resize'); animation.resize(); } } */ if(!autoplay) continue; /* if(blurred || !isElementInViewport(container)) { if(!paused) { this.debug && console.log('pause animation', isElementInViewport(container), container); animation.pause(); animations[i].paused = true; } } else if(paused) { this.debug && console.log('play animation', container); animation.play(); animations[i].paused = false; } */ } } } private applyReplacements(object: any, toneIndex: number) { const replacements = LottieLoader.COLORREPLACEMENTS[toneIndex - 2]; const iterateIt = (it: any) => { for(let smth of it) { switch(smth.ty) { case 'st': case 'fl': let k = smth.c.k; let color = convert(k[2]) | (convert(k[1]) << 8) | (convert(k[0]) << 16); let foundReplacement = replacements.find(p => p[0] == color); if(foundReplacement) { k[0] = ((foundReplacement[1] >> 16) & 255) / 255; k[1] = ((foundReplacement[1] >> 8) & 255) / 255; k[2] = (foundReplacement[1] & 255) / 255; } console.log('foundReplacement!', foundReplacement, color.toString(16), k); break; } if(smth.hasOwnProperty('it')) { iterateIt(smth.it); } } }; for(let layer of object.layers) { if(!layer.shapes) continue; for(let shape of layer.shapes) { iterateIt(shape.it); } } } public async loadAnimation(params: /* any */AnimationConfigWithPath & AnimationConfigWithData, group = '', toneIndex = -1) { //params.autoplay = false; //if(group != 'auth') { //params.renderer = 'canvas'; params.renderer = 'svg'; //} if(toneIndex >= 1 && toneIndex <= 5) { this.applyReplacements(params.animationData, toneIndex); } let rendererSettings = { //context: context, // the canvas context //preserveAspectRatio: 'xMinYMin slice', // Supports the same options as the svg element's preserveAspectRatio property clearCanvas: true, progressiveLoad: true, // Boolean, only svg renderer, loads dom elements when needed. Might speed up initialization for large number of elements. hideOnTransparent: true, //Boolean, only svg renderer, hides elements when opacity reaches 0 (defaults to true), }; if(params.rendererSettings) { params.rendererSettings = Object.assign(params.rendererSettings, rendererSettings); } else { params.rendererSettings = rendererSettings; } if(!this.lottie) { if(!this.loaded) this.loadLottie(); await this.loaded; this.lottie.setQuality('low'); //this.lottie.setQuality(10); } let time = Date.now(); let diff = time - this.lastTimeLoad; let delay = 150; if(diff < delay) { delay *= ++this.waitingTimeouts; console.log('lottieloader delay:', delay); //await new Promise((resolve) => setTimeout(resolve, delay)); this.waitingTimeouts--; } let animation = this.lottie.loadAnimation(params); this.lastTimeLoad = Date.now(); if(!this.animations[group]) this.animations[group] = []; this.animations[group].push({ animation, container: params.container as HTMLDivElement, paused: !params.autoplay, autoplay: params.autoplay, canvas: false//params.renderer == 'canvas' }); if(params.autoplay) { this.checkAnimations(); } return animation; } public getAnimation(el: HTMLElement, group = '') { let groups = group ? [group] : Object.keys(this.animations); //console.log('getAnimation', groups, this.animations); for(let group of groups) { let animations = this.animations[group]; let animation = animations.find(a => a.container === el); if(animation) return animation.animation; } return null; } } const lottieLoader = new LottieLoader(); (window as any).LottieLoader = lottieLoader; export default lottieLoader;