'undefined' !== typeof window ? require('web-animations-js') : ''; import {Proton} from './proton'; import {Vector} from '@avocado/math'; export class TextNode { constructor(text) { this.text = text; } createDom() { const div = window.document.createElement('div'); div.className = 'particle'; const span = window.document.createElement('span'); span.className = this.spanClassName(); span.textContent = this.text; span.style.fontSize = `${this.sizeInPx()}px`; this.span = span; div.appendChild(span); this.div = div; } sizeInPx() { return 12; } spanClassName() { return 'text'; } particleStateToCssRules(particle) { const color = particle.color; // Position. const transforms = []; const position = [particle.p.x, particle.p.y]; const scale = particle.scale; // Offset for font size. const {style, textContent} = particle.target.span; const fontSize = this.sizeInPx(); const length = textContent.length; const offset = (fontSize / 2) * (particle.scale / 2); position[0] -= offset * length; position[1] += offset; transforms.push(`translate(${position[0]}px, ${-position[1]}px)`); // Angle. const angle = 360 * (particle.rotation.x / (Math.PI * 2)); transforms.push(`rotate(${angle}deg)`); // Scale. transforms.push(`scale(${scale})`); const rules = { transform: transforms.join(' '), }; // Alpha? if (particle.useAlpha) { rules.opacity = particle.alpha || 1; } // Color? if (particle.useColor) { rules.color = `rgb(${color.r * 255}, ${color.g * 255}, ${color.b * 255})`; } return rules; } } export class TextNodeRenderer extends Proton.BaseRender { static get freeList() { if (!this._freeList) { this._freeList = []; } return this._freeList; } static pushToFreeList(target) { const freeList = this.freeList; freeList.push(target); } static pullFromFreeList() { const freeList = this.freeList; return freeList.pop(); } constructor(selector, stage) { super(); this._body = new TextNode(''); this.name = 'NodeRenderer'; this.queued = []; const promise = stage.findUiElement(selector); promise.then((element) => { if (this.parent) { let freeNode; while (freeNode = this.constructor.pullFromFreeList()) { this.parent.removeChild(freeNode.div); } } this.parent = element; // Start with a warm cache. if (this.parent && 0 === this.constructor.freeList.length) { for (let i = 0; i < 200; ++i) { const target = new TextNode(); target.createDom(); this.parent.appendChild(target.div); target.div.style.opacity = 0; this.constructor.pushToFreeList(target); } } }); } onParticleCreated(particle) { // Clone global body if none was passed. if (!particle.body) { particle.body = this._body.clone(); } // Pull from free list if we can. const target = this.constructor.pullFromFreeList(); if (target) { particle.target = target; particle.target.span.className = particle.body.spanClassName(); particle.target.span.textContent = particle.body.text; particle.target.span.style.fontSize = `${particle.body.sizeInPx()}px`; particle.target.div.style.opacity = 1; } else { particle.target = particle.body; particle.target.createDom(); if (this.parent) { this.parent.appendChild(particle.target.div); } } // Queue if we don't have a parent yet. if (!this.parent) { this.queued.push(particle.target); } else { for (let i = 0; i < this.queued.length; ++i) { this.parent.appendChild(this.queued[i].div); } this.queued = []; } // Simulate the particle in discrete steps to build keyframes for // animation. const life = particle.life; particle.life = life * 1.01; const stepsPerSec = 10; let stepCount = life * stepsPerSec; const keyframes = []; const damping = 1 - .006; const time = 1 / stepsPerSec; while (stepCount--) { const keyframe = particle.target.particleStateToCssRules( particle, ); particle.update(time, 0); Proton.integrator.integrate(particle, time, damping); keyframes.push(keyframe); } particle.life = Infinity; // Start animation. const animation = particle.target.div.animate(keyframes, { duration: life * 1000, }); animation.addEventListener('finish', () => { particle.dead = true; particle.target.div.style.opacity = 0; this.constructor.pushToFreeList(particle.target); particle.target = undefined; }); } onParticleUpdate(particle) {} onParticleDead(particle) {} }