2019-04-23 03:24:40 -05:00
|
|
|
'undefined' !== typeof window ? require('web-animations-js') : '';
|
|
|
|
|
2019-04-19 00:50:15 -05:00
|
|
|
import {Proton} from './proton';
|
|
|
|
|
|
|
|
import {Vector} from '@avocado/math';
|
|
|
|
|
|
|
|
export class TextNode {
|
|
|
|
|
|
|
|
constructor(text) {
|
2019-04-24 02:45:08 -05:00
|
|
|
this.text = text;
|
|
|
|
}
|
|
|
|
|
|
|
|
createDom() {
|
2019-04-19 00:50:15 -05:00
|
|
|
const div = window.document.createElement('div');
|
|
|
|
div.className = 'particle';
|
|
|
|
const span = window.document.createElement('span');
|
2019-04-24 02:45:08 -05:00
|
|
|
span.className = this.spanClassName();
|
|
|
|
span.textContent = this.text;
|
|
|
|
span.style.fontSize = `${this.sizeInPx()}px`;
|
2019-04-19 00:50:15 -05:00
|
|
|
this.span = span;
|
|
|
|
div.appendChild(span);
|
|
|
|
this.div = div;
|
|
|
|
}
|
|
|
|
|
2019-04-24 02:45:08 -05:00
|
|
|
sizeInPx() {
|
|
|
|
return 12;
|
|
|
|
}
|
|
|
|
|
|
|
|
spanClassName() {
|
|
|
|
return 'text';
|
2019-04-19 00:50:15 -05:00
|
|
|
}
|
|
|
|
|
2019-04-23 03:24:40 -05:00
|
|
|
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;
|
2019-04-24 02:45:08 -05:00
|
|
|
const fontSize = this.sizeInPx();
|
2019-04-23 03:24:40 -05:00
|
|
|
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?
|
2019-04-21 14:58:11 -05:00
|
|
|
if (particle.useAlpha) {
|
2019-04-23 03:24:40 -05:00
|
|
|
rules.opacity = particle.alpha || 1;
|
2019-04-21 14:58:11 -05:00
|
|
|
}
|
2019-04-23 03:24:40 -05:00
|
|
|
// Color?
|
2019-04-21 14:58:11 -05:00
|
|
|
if (particle.useColor) {
|
2019-04-23 03:24:40 -05:00
|
|
|
rules.color = `rgb(${color.r * 255}, ${color.g * 255}, ${color.b * 255})`;
|
2019-04-21 19:24:45 -05:00
|
|
|
}
|
2019-04-23 03:24:40 -05:00
|
|
|
return rules;
|
2019-04-19 00:50:15 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
export class TextNodeRenderer extends Proton.BaseRender {
|
|
|
|
|
2019-04-21 14:58:11 -05:00
|
|
|
static get freeList() {
|
|
|
|
if (!this._freeList) {
|
|
|
|
this._freeList = [];
|
|
|
|
}
|
|
|
|
return this._freeList;
|
|
|
|
}
|
|
|
|
|
2019-04-24 02:45:08 -05:00
|
|
|
static pushToFreeList(target) {
|
|
|
|
const freeList = this.freeList;
|
|
|
|
freeList.push(target);
|
|
|
|
}
|
|
|
|
|
|
|
|
static pullFromFreeList() {
|
|
|
|
const freeList = this.freeList;
|
|
|
|
return freeList.pop();
|
|
|
|
}
|
|
|
|
|
2019-04-19 00:50:15 -05:00
|
|
|
constructor(selector, stage) {
|
|
|
|
super();
|
2019-04-19 23:11:57 -05:00
|
|
|
this._body = new TextNode('');
|
2019-04-19 00:50:15 -05:00
|
|
|
this.name = 'NodeRenderer';
|
|
|
|
this.queued = [];
|
2019-04-20 21:35:07 -05:00
|
|
|
const promise = stage.findUiElement(selector);
|
|
|
|
promise.then((element) => {
|
2019-04-22 14:36:39 -05:00
|
|
|
if (this.parent) {
|
2019-04-24 02:45:08 -05:00
|
|
|
let freeNode;
|
|
|
|
while (freeNode = this.constructor.pullFromFreeList()) {
|
|
|
|
this.parent.removeChild(freeNode.div);
|
2019-04-22 14:36:39 -05:00
|
|
|
}
|
|
|
|
}
|
2019-04-20 21:35:07 -05:00
|
|
|
this.parent = element;
|
2019-04-24 02:45:08 -05:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
}
|
2019-04-19 00:50:15 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
onParticleCreated(particle) {
|
2019-04-24 02:45:08 -05:00
|
|
|
// Clone global body if none was passed.
|
2019-04-23 03:24:40 -05:00
|
|
|
if (!particle.body) {
|
|
|
|
particle.body = this._body.clone();
|
|
|
|
}
|
2019-04-24 02:45:08 -05:00
|
|
|
// 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;
|
2019-04-21 14:58:11 -05:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
particle.target = particle.body;
|
2019-04-24 02:45:08 -05:00
|
|
|
particle.target.createDom();
|
|
|
|
if (this.parent) {
|
|
|
|
this.parent.appendChild(particle.target.div);
|
|
|
|
}
|
2019-04-19 00:50:15 -05:00
|
|
|
}
|
2019-04-24 02:45:08 -05:00
|
|
|
// Queue if we don't have a parent yet.
|
2019-04-19 00:50:15 -05:00
|
|
|
if (!this.parent) {
|
2019-04-21 14:58:11 -05:00
|
|
|
this.queued.push(particle.target);
|
2019-04-19 00:50:15 -05:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
for (let i = 0; i < this.queued.length; ++i) {
|
2019-04-24 02:45:08 -05:00
|
|
|
this.parent.appendChild(this.queued[i].div);
|
2019-04-21 14:58:11 -05:00
|
|
|
}
|
|
|
|
this.queued = [];
|
2019-04-19 00:50:15 -05:00
|
|
|
}
|
2019-04-24 02:45:08 -05:00
|
|
|
// Simulate the particle in discrete steps to build keyframes for
|
|
|
|
// animation.
|
2019-04-23 03:24:40 -05:00
|
|
|
const life = particle.life;
|
2019-04-24 02:45:08 -05:00
|
|
|
particle.life = life * 1.01;
|
|
|
|
const stepsPerSec = 10;
|
2019-04-23 03:24:40 -05:00
|
|
|
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;
|
2019-04-24 02:45:08 -05:00
|
|
|
particle.target.div.style.opacity = 0;
|
|
|
|
this.constructor.pushToFreeList(particle.target);
|
2019-04-23 03:24:40 -05:00
|
|
|
particle.target = undefined;
|
|
|
|
});
|
2019-04-19 00:50:15 -05:00
|
|
|
}
|
|
|
|
|
2019-04-23 03:24:40 -05:00
|
|
|
onParticleUpdate(particle) {}
|
|
|
|
|
|
|
|
onParticleDead(particle) {}
|
2019-04-19 00:50:15 -05:00
|
|
|
|
|
|
|
}
|