optimize: fastest possible precalc web animation?

This commit is contained in:
cha0s 2019-04-24 02:45:08 -05:00
parent 950c598c5a
commit 177b719ef4

View File

@ -7,23 +7,27 @@ import {Vector} from '@avocado/math';
export class TextNode {
constructor(text) {
this.parent = null;
const wrapper = window.document.createElement('div');
this.wrapper = wrapper;
wrapper.style.position = 'absolute';
this.text = text;
}
createDom() {
const div = window.document.createElement('div');
wrapper.appendChild(div);
div.className = 'particle';
const span = window.document.createElement('span');
span.className = 'text';
span.textContent = text;
span.className = this.spanClassName();
span.textContent = this.text;
span.style.fontSize = `${this.sizeInPx()}px`;
this.span = span;
div.appendChild(span);
this.div = div;
}
copyFrom(other) {
this.span.textContent = other.span.textContent;
sizeInPx() {
return 12;
}
spanClassName() {
return 'text';
}
particleStateToCssRules(particle) {
@ -34,7 +38,7 @@ export class TextNode {
const scale = particle.scale;
// Offset for font size.
const {style, textContent} = particle.target.span;
const fontSize = parseFloat(style.fontSize);
const fontSize = this.sizeInPx();
const length = textContent.length;
const offset = (fontSize / 2) * (particle.scale / 2);
position[0] -= offset * length;
@ -63,10 +67,6 @@ export class TextNode {
export class TextNodeRenderer extends Proton.BaseRender {
static clearFreeList() {
this._freeList = [];
}
static get freeList() {
if (!this._freeList) {
this._freeList = [];
@ -74,6 +74,16 @@ export class TextNodeRenderer extends Proton.BaseRender {
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('');
@ -82,48 +92,61 @@ export class TextNodeRenderer extends Proton.BaseRender {
const promise = stage.findUiElement(selector);
promise.then((element) => {
if (this.parent) {
const freeList = this.constructor.freeList;
while (freeList.length > 0) {
const node = freeList.pop();
this.parent.removeChild(node);
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) {
const freeList = this.constructor.freeList;
// Clone global body if none was passed.
if (!particle.body) {
particle.body = this._body.clone();
}
if (freeList.length > 0) {
particle.target = freeList.pop();
particle.target.copyFrom(particle.body);
// 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);
}
}
particle.target.wrapper.style.opacity = 0;
// 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].wrapper);
this.queued[i].parent = this.parent;
this.queued[i].wrapper.style.opacity = 1;
this.parent.appendChild(this.queued[i].div);
}
this.queued = [];
if (!particle.target.parent) {
this.parent.appendChild(particle.target.wrapper);
particle.target.parent = this.parent;
}
particle.target.wrapper.style.opacity = 1;
}
// Simulate the particle in discrete steps to build keyframes for
// animation.
const life = particle.life;
particle.life = life * 1.05;
const stepsPerSec = 30;
particle.life = life * 1.01;
const stepsPerSec = 10;
let stepCount = life * stepsPerSec;
const keyframes = [];
const damping = 1 - .006;
@ -136,12 +159,6 @@ export class TextNodeRenderer extends Proton.BaseRender {
Proton.integrator.integrate(particle, time, damping);
keyframes.push(keyframe);
}
// Immediately apply the first keyframe.
const firstKeyframe = keyframes[0];
for (const attribute in firstKeyframe) {
const value = firstKeyframe[attribute];
particle.target.div.style[attribute] = value;
}
particle.life = Infinity;
// Start animation.
const animation = particle.target.div.animate(keyframes, {
@ -149,9 +166,8 @@ export class TextNodeRenderer extends Proton.BaseRender {
});
animation.addEventListener('finish', () => {
particle.dead = true;
particle.target.wrapper.style.opacity = 0;
const freeList = this.constructor.freeList;
freeList.push(particle.target);
particle.target.div.style.opacity = 0;
this.constructor.pushToFreeList(particle.target);
particle.target = undefined;
});
}