refactor: Web Animations for TextNodeRenderer
This commit is contained in:
parent
b750ffd0ff
commit
7e42d5392c
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@avocado/graphics",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.4",
|
||||
"main": "index.js",
|
||||
"author": "cha0s",
|
||||
"license": "MIT",
|
||||
|
@ -14,6 +14,7 @@
|
|||
"immutable": "4.0.0-rc.12",
|
||||
"pixi.js": "4.8.6",
|
||||
"three": "0.103.0",
|
||||
"three.proton.js": "0.1.5"
|
||||
"three.proton.js": "0.1.5",
|
||||
"web-animations-js": "2.3.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
'undefined' !== typeof window ? require('web-animations-js') : '';
|
||||
|
||||
import {Proton} from './proton';
|
||||
|
||||
import {Vector} from '@avocado/math';
|
||||
|
@ -5,11 +7,6 @@ import {Vector} from '@avocado/math';
|
|||
export class TextNode {
|
||||
|
||||
constructor(text) {
|
||||
this.lastAlpha = undefined;
|
||||
this.lastColor = undefined;
|
||||
this.lastPosition = undefined;
|
||||
this.lastAngle = undefined;
|
||||
this.lastScale = undefined;
|
||||
this.parent = null;
|
||||
const wrapper = window.document.createElement('div');
|
||||
this.wrapper = wrapper;
|
||||
|
@ -29,79 +26,37 @@ export class TextNode {
|
|||
this.span.textContent = other.span.textContent;
|
||||
}
|
||||
|
||||
update(particle, stage) {
|
||||
const {div, span} = this;
|
||||
if (particle.useAlpha) {
|
||||
const alpha = Math.floor(256 * particle.alpha) / 256;
|
||||
if (this.lastAlpha !== alpha) {
|
||||
div.style.opacity = alpha;
|
||||
this.lastAlpha = alpha;
|
||||
}
|
||||
}
|
||||
if (particle.useColor) {
|
||||
const color = particle.color;
|
||||
color.r = Math.floor(255 * color.r) / 255;
|
||||
color.g = Math.floor(255 * color.g) / 255;
|
||||
color.b = Math.floor(255 * color.b) / 255;
|
||||
if (
|
||||
!this.lastColor
|
||||
|| this.lastColor.r !== color.r
|
||||
|| this.lastColor.g !== color.g
|
||||
|| this.lastColor.b !== color.b
|
||||
) {
|
||||
div.style.color = `rgb(
|
||||
${color.r * 255},
|
||||
${color.g * 255},
|
||||
${color.b * 255}
|
||||
)`;
|
||||
this.lastColor = {
|
||||
r: color.r,
|
||||
g: color.g,
|
||||
b: color.b,
|
||||
};
|
||||
}
|
||||
}
|
||||
let transformsChanged;
|
||||
const camera = stage.camera;
|
||||
particleStateToCssRules(particle) {
|
||||
const color = particle.color;
|
||||
// Position.
|
||||
const transforms = [];
|
||||
const position = [particle.p.x, particle.p.y];
|
||||
if (camera) {
|
||||
const realOffset = camera.realOffset;
|
||||
position[0] -= realOffset[0];
|
||||
position[1] += realOffset[1];
|
||||
const scale = particle.scale;
|
||||
// Offset for font size.
|
||||
const {style, textContent} = particle.target.span;
|
||||
const fontSize = parseFloat(style.fontSize);
|
||||
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;
|
||||
}
|
||||
position[0] = Math.round(position[0] * 4) / 4;
|
||||
position[1] = Math.round(position[1] * 4) / 4;
|
||||
if (
|
||||
!this.lastPosition
|
||||
|| this.lastPosition[0] !== position[0]
|
||||
|| this.lastPosition[1] !== position[1]
|
||||
) {
|
||||
transformsChanged = true;
|
||||
this.lastPosition = position;
|
||||
}
|
||||
const angle = Math.floor(1440 * (particle.rotation.x / (Math.PI * 2))) / 1440;
|
||||
if (this.lastAngle !== angle) {
|
||||
const normalAngle = (360 + angle * 360) % 360;
|
||||
transformsChanged = true;
|
||||
this.lastAngle = angle;
|
||||
}
|
||||
let scale = particle.scale;
|
||||
scale += particle.p.z > 0 ? particle.p.z / 70 : 0;
|
||||
scale = Math.floor(100 * scale) / 100;
|
||||
if (this.lastScale !== scale) {
|
||||
transformsChanged = true;
|
||||
const spanTranslate = 50 * (1 / particle.scale);
|
||||
span.style.transform = `translate(-${spanTranslate}%, -${spanTranslate}%)`;
|
||||
this.lastScale = scale;
|
||||
}
|
||||
if (transformsChanged) {
|
||||
const transforms = [];
|
||||
const normalAngle = (360 + this.lastAngle * 360) % 360;
|
||||
transforms.push(`translate(${this.lastPosition[0]}px, ${-this.lastPosition[1]}px)`);
|
||||
transforms.push(`rotate(${normalAngle}deg)`);
|
||||
transforms.push(`scale(${this.lastScale})`);
|
||||
div.style.transform = transforms.join(' ');
|
||||
// Color?
|
||||
if (particle.useColor) {
|
||||
rules.color = `rgb(${color.r * 255}, ${color.g * 255}, ${color.b * 255})`;
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -124,7 +79,6 @@ export class TextNodeRenderer extends Proton.BaseRender {
|
|||
this._body = new TextNode('');
|
||||
this.name = 'NodeRenderer';
|
||||
this.queued = [];
|
||||
this.stage = stage;
|
||||
const promise = stage.findUiElement(selector);
|
||||
promise.then((element) => {
|
||||
if (this.parent) {
|
||||
|
@ -140,20 +94,17 @@ export class TextNodeRenderer extends Proton.BaseRender {
|
|||
|
||||
onParticleCreated(particle) {
|
||||
const freeList = this.constructor.freeList;
|
||||
if (!particle.body) {
|
||||
particle.body = this._body.clone();
|
||||
}
|
||||
if (freeList.length > 0) {
|
||||
particle.target = freeList.pop();
|
||||
// ...
|
||||
if (particle.body) {
|
||||
particle.target.copyFrom(particle.body);
|
||||
}
|
||||
particle.target.copyFrom(particle.body);
|
||||
}
|
||||
else {
|
||||
if (!particle.body) {
|
||||
particle.body = this._body.clone();
|
||||
}
|
||||
particle.target = particle.body;
|
||||
particle.target.wrapper.style.opacity = 0;
|
||||
}
|
||||
particle.target.wrapper.style.opacity = 0;
|
||||
if (!this.parent) {
|
||||
this.queued.push(particle.target);
|
||||
}
|
||||
|
@ -170,20 +121,43 @@ export class TextNodeRenderer extends Proton.BaseRender {
|
|||
}
|
||||
particle.target.wrapper.style.opacity = 1;
|
||||
}
|
||||
}
|
||||
|
||||
onParticleUpdate(particle) {
|
||||
if (!particle.target) {
|
||||
return;
|
||||
const life = particle.life;
|
||||
particle.life = life * 1.05;
|
||||
const stepsPerSec = 30;
|
||||
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.target.update(particle, this.stage);
|
||||
// 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, {
|
||||
duration: life * 1000,
|
||||
});
|
||||
animation.addEventListener('finish', () => {
|
||||
particle.dead = true;
|
||||
particle.target.wrapper.style.opacity = 0;
|
||||
const freeList = this.constructor.freeList;
|
||||
freeList.push(particle.target);
|
||||
particle.target = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
onParticleDead(particle) {
|
||||
particle.target.wrapper.style.opacity = 0;
|
||||
const freeList = this.constructor.freeList;
|
||||
freeList.push(particle.target);
|
||||
particle.target = undefined;
|
||||
}
|
||||
onParticleUpdate(particle) {}
|
||||
|
||||
onParticleDead(particle) {}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user