UNPKG

@easrng/sparkle

Version:

a zero-dependency web component implementing the MFM sparkle effect

112 lines (111 loc) 4.49 kB
/** SPDX-FileCopyrightText: syuilo and other misskey contributors, web component port by easrng SPDX-License-Identifier: AGPL-3.0-only */ const svgNS = "http://www.w3.org/2000/svg"; class Sparkle extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open", }); } connectedCallback() { const span = Object.assign(document.createElement("span"), { style: "display:inline-block", }); span.append(document.createElement("slot")); this.shadowRoot.append( Object.assign(document.createElement("style"), { textContent: ":host{position:relative;display:inline-block;}", }), span, ); const colors = ["#FF1493", "#00FFFF", "#FFE202", "#FFE202", "#FFE202"]; let stop = false; let ro; let width = 0; let height = 0; ro = new ResizeObserver((entries, observer) => { width = this.offsetWidth + 64; height = this.offsetHeight + 64; }); ro.observe(this); const add = () => { if (stop) return; const x = Math.random() * (width - 64); const y = Math.random() * (height - 64); const sizeFactor = Math.random(); const particle = { id: Math.random().toString(), x, y, size: 0.2 + (sizeFactor / 10) * 3, dur: 1000 + sizeFactor * 1000, color: colors[Math.floor(Math.random() * colors.length)], }; const svg = document.createElementNS(svgNS, "svg"); svg.setAttribute("width", width); svg.setAttribute("height", height); svg.setAttribute("viewBox", "0 0 " + width + " " + height); svg.setAttribute( "style", "position:absolute;top:-32px;left:-32px;pointer-events:none", ); const path = document.createElementNS(svgNS, "path"); path.setAttribute( "style", "transform-origin:center;transform-box:fill-box", ); path.setAttribute( "transform", "translate(" + particle.x + " " + particle.y + ")", ); path.setAttribute("fill", particle.color); path.setAttribute( "d", "M29.427,2.011C29.721,0.83 30.782,0 32,0C33.218,0 34.279,0.83 34.573,2.011L39.455,21.646C39.629,22.347 39.991,22.987 40.502,23.498C41.013,24.009 41.653,24.371 42.354,24.545L61.989,29.427C63.17,29.721 64,30.782 64,32C64,33.218 63.17,34.279 61.989,34.573L42.354,39.455C41.653,39.629 41.013,39.991 40.502,40.502C39.991,41.013 39.629,41.653 39.455,42.354L34.573,61.989C34.279,63.17 33.218,64 32,64C30.782,64 29.721,63.17 29.427,61.989L24.545,42.354C24.371,41.653 24.009,41.013 23.498,40.502C22.987,39.991 22.347,39.629 21.646,39.455L2.011,34.573C0.83,34.279 0,33.218 0,32C0,30.782 0.83,29.721 2.011,29.427L21.646,24.545C22.347,24.371 22.987,24.009 23.498,23.498C24.009,22.987 24.371,22.347 24.545,21.646L29.427,2.011Z", ); const animateTransform1 = document.createElementNS( svgNS, "animateTransform", ); animateTransform1.setAttribute("attributeName", "transform"); animateTransform1.setAttribute("attributeType", "XML"); animateTransform1.setAttribute("type", "rotate"); animateTransform1.setAttribute("from", "0 0 0"); animateTransform1.setAttribute("to", "360 0 0"); animateTransform1.setAttribute("dur", particle.dur + "ms"); animateTransform1.setAttribute("repeatCount", "1"); animateTransform1.setAttribute("additive", "sum"); animateTransform1.addEventListener("endEvent", () => svg.remove()); const animateTransform2 = document.createElementNS( svgNS, "animateTransform", ); animateTransform2.setAttribute("attributeName", "transform"); animateTransform2.setAttribute("attributeType", "XML"); animateTransform2.setAttribute("type", "scale"); animateTransform2.setAttribute("values", "0;" + particle.size + ";0"); animateTransform2.setAttribute("dur", particle.dur + "ms"); animateTransform2.setAttribute("repeatCount", "2"); animateTransform2.setAttribute("additive", "sum"); path.append(animateTransform1, animateTransform2); svg.append(path); this.shadowRoot.append(svg); window.setTimeout( () => { add(); }, 500 + Math.random() * 500, ); }; add(); this.disconnectedCallback = () => { if (ro) ro.disconnect(); stop = true; this.shadowRoot.textContent = ""; }; } } customElements.define("easrng-sparkle", Sparkle);