let animationNameID = Date.now(); const added = new Map(); export const AnimationInstaller = { installAnimation(text: string, styleSheet: CSSStyleSheet) { let id = added.get(text); if (id) { return { id }; } const parsed = this.tryParse(text); if (!parsed) { return; } const { keyFrames, css } = parsed; if (css) { // text based animation id = this.installCssAnimation(css, styleSheet); added.set(text, id); return { id }; } try { if (keyFrames) { id = this.installKeyframeAnimation(keyFrames, styleSheet); added.set(text, id); return { id }; } console.error(`Could not install animation ${text}`); } catch (error) { console.error(error); } }, installCssAnimation(text: string, styleSheet: CSSStyleSheet) { // replace name... const id = `internal-style-generated-${animationNameID++}`; const style = text.replace(/(\@keyframes\s{1,100})([^\s\{]{1,200})(\s{1,1000}\{)/gm, "$1" + id + "$3"); // console.log(style); styleSheet.insertRule(style); return id; }, installKeyframeAnimation(frames, styleSheet: CSSStyleSheet) { const total = frames.length; let i = 0; for (const frame of frames) { frame.attributes = { ... frame }; delete frame.attributes.offset; frame.offset ??= i === 0 ? "0%" : (i === total - 1 ? "100%" : `${Math.floor(i * 100 / (total - 1))}%` ); i++; } const id = `internal-style-generated-${animationNameID++}`; const styleText = `@keyframes ${id} { ${frames.map((f) => `${f.offset} { ${Object.entries(f.attributes).map(([k,v]) => `${k}: ${v};`).join("\n")} }`).join("\n")} }`; styleSheet.insertRule(styleText); return id; }, tryParse(n: string) { try { n = n.trim(); if (n.startsWith("@keyframes ")) { return { css: n }; } if (!/[\{]/.test(n)) { return void 0; } return { keyFrames: JSON.parse(n) }; } catch (e) { console.warn(e); } } }