This is the strangest bug that I have ever met. blowed my mind.
function render_element(styles, el) {
for (const [kk, vv] of Object.entries(styles)) {
el.style[kk] = vv;
}
}
function capitalize(s){
return s[0].toUpperCase() + s.slice(1);
}
function swap(node1, node2) {
const afterNode2 = node2.nextElementSibling;
const parent = node2.parentNode;
node1.replaceWith(node2);
parent.insertBefore(node1, afterNode2);
}
function sortOnKeys(dict) {
var sorted = [];
for(var key in dict) {
sorted[sorted.length] = key;
}
sorted.sort();
var tempDict = {};
for(var i = 0; i < sorted.length; i++) {
tempDict[sorted[i]] = dict[sorted[i]];
}
return tempDict;
}
const delay = ms => new Promise(res => setTimeout(res, ms));
// round to 2 decimal places
const rit = val => Math.round(val * 100) / 100;
const unique = function(prefix){
if(!unique.cache)unique.cache = {};
if(unique.cache[prefix] == null)unique.cache[prefix] = 0;
unique.cache[prefix]++;
return prefix + unique.cache[prefix].toString();
}
// [1, 3], 3 -> [1, 2, 3]
const linespace = (tar, den) => Array.from({
length: den
}, (x, i) => tar[0] + i * (tar[1] - tar[0]) / (den - 1));
const isInt = value => !isNaN(value) && (function(x) {
return (x | 0) === x;
})(parseFloat(value));
// degree to radians
function d2r(deg) {
return deg * Math.PI / 180;
}
// radians to degree
function r2d(rad) {
return rad * 180 / Math.PI;
}
// theta = atan m
// convert y = kx + b to y = r x
// archive cartesian to polar and force domain to [0, 2pi] by
// regarding the line start from center and goes to one of the four quadrants
function get_rad(x, y) {
var rad = Math.atan(y / x);
if (x < 0 && y > 0) {
rad = Math.PI + rad;
} else if (x < 0 && y < 0) {
rad = Math.PI + rad;
} else if (x > 0 && y < 0) {
rad = 2 * Math.PI + rad;
}
return rad;
}
// pass in one point and a radians to ratate
// return a rotated dot
function rotate_by_origin(pt, rt) {
var phi = get_rad(pt.x, pt.y) + rt;
var r = (pt.x ** 2 + pt.y ** 2) ** 0.5;
if (phi > 2 * Math.PI) {
phi -= 2 * Math.PI;
}
if (0 < phi < Math.PI * 0.5) {
return {
x: Math.cos(phi) * r,
y: Math.sin(phi) * r
};
} else if (Math.PI * 0.5 < phi < Math.PI) {
phi -= 0.5 * Math.PI;
return {
x: -Math.cos(phi) * r,
y: Math.sin(phi) * r
};
} else if (Math.PI < phi < Math.PI * (2 / 3)) {
phi -= Math.PI;
return {
x: -Math.cos(phi) * r,
y: -Math.sin(phi) * r
};
} else {
phi -= (2 / 3) * Math.PI;
return {
x: Math.cos(phi) * r,
y: -Math.sin(phi) * r
};
}
}
// pass in two dots and one radians
// return a dot
// rotate pt by ori with rt radians
function rotate_one(ori, pt, rt) {
var bk = {
x: pt.x - ori.x,
y: pt.y - ori.y
};
var rbk = rotate_by_origin(bk, rt);
return {
x: rbk.x + ori.x,
y: rbk.y + ori.y
};
}
// pass in side, center, mouse
function nPolyDots(n, cen, pt) {
var inde = 2 * Math.PI / n;
var cont = [];
cont.push(pt);
for (let i = 1; i < n; i++) {
cont.push(rotate_one(cen, pt, i * inde));
}
return cont;
}
class XY{
constructor(s, x, y){
this.s = s;
this.x = x;
this.y = y;
this.concat = this.concat.bind(this);
this.calcX = this.calcX.bind(this);
this.calcY = this.calcY.bind(this);
this.parse = this.parse.bind(this);
}
calcX(func){this.x = func(this.x);return this}
calcY(func){this.y = func(this.y);return this}
concat(){return new XY(this.s, this.x, this.y)}
parse(){return this.s + rit(this.x).toString() + ' ' + rit(this.y).toString()}
}
// let xy = new XY('L', 10, 12);
// console.log(xy.addX(10).addY(-10).mulX(2));
class ML{
constructor(d){
if(d){
// sep always parsed to space
let sep = d.includes(',') ? ',' : ' ';
this.d = d;
this.zflag = d.includes('Z');
this.xys = d.split('L').map(st=>isInt(st[0]) ? new XY("L", ...st.split(sep).map(dd=>rit(parseFloat(dd)))) : new XY('M', ...st.split(sep).map((dd, ii)=>ii === 0 ? rit(parseFloat(dd.substring(1))) : rit(parseFloat(dd)))));
this.calcX = this.calcX.bind(this);
this.calcY = this.calcY.bind(this);
this.concat = this.concat.bind(this);
this.parse = this.parse.bind(this);
}
}
calcX(func){this.xys.forEach(xy=>xy.calcX(func));return this}
calcY(func){this.xys.forEach(xy=>xy.calcY(func));return this}
concat(){
let ml = new ML();
ml.xys = this.xys.map(xy=>xy.concat());
ml.zflag = this.zflag;
return ml;
}
get length(){return this.xys.length;}
parse(){
return this.xys.map(xy=>xy.parse()).join(' ') + (this.zflag ? 'Z' : '');
}
}
class Vector{
constructor(
parent_el,
id,
width,
height,
){
this.parent_el = parent_el;
this.id = id;
this.width = width;
this.height = height;
this.widgets = [];
this.parent_el.innerHTML += `<svg viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg"></svg>`
this.el = this.parent_el.lastChild;
this.viewBox = this.viewBox.bind(this);
this.addPath = this.addPath.bind(this);
}
viewBox(dx, dy){
this.width += dx;
this.height += dy;
this.el.setAttribute('viewBox', `0 0 ${this.width} ${this.height}`)
return this;
}
addPath(widget){
this.widgets.push(widget);
widget.init(this);
return widget;
}
}
class Path{
constructor(
id,
str,
width,
height,
){
this.id = id;
this.str = str;
this.width = width;
this.height = height;
this.init = this.init.bind(this);
this.fit = this.fit.bind(this);
this.frontMost = this.frontMost.bind(this);
this.front = this.front.bind(this);
this.backMost = this.backMost.bind(this);
this.back = this.back.bind(this);
this.animsvg = [];
this.animcss = [];
}
get el(){return this.host.el.getElementById(this.id)}
init(vector){
this.host = vector; // Vector instance (SVG)
this.host.el.innerHTML += this.str;
}
fit(){
this.host.viewBox(-this.host.width + this.width, -this.host.height + this.height);
}
async frontMost(ms){
if(ms){await delay(ms)}
let path_el = this.el;
this.host.el.removeChild(path_el);
this.host.el.appendChild(path_el);
return this;
}
async front(ms){
if(ms){await delay(ms)}
let kids = [];
for(let ii = 0; ii < this.host.el.children.length; ii++){
kids.push(this.host.el.children[ii]);
}
let path_el = this.el;
let ind = kids.indexOf(path_el);
this.host.el.removeChild(path_el);
this.host.el.insertBefore(path_el, kids[ind + 2]);
return this;
}
async back(ms){
if(ms){await delay(ms)}
let kids = [];
for(let ii = 0; ii < this.host.el.children.length; ii++){
kids.push(this.host.el.children[ii]);
}
let path_el = this.el;
let ind = kids.indexOf(path_el);
this.host.el.removeChild(path_el);
this.host.el.insertBefore(path_el, kids[ind - 1]);
return this;
}
async backMost(ms){
if(ms){await delay(ms)}
let path_el = this.el;
this.host.el.removeChild(path_el);
this.host.el.insertBefore(path_el, this.host.el.children[0]);
return this;
}
static star(
id,
radius,
deg, // rotate degree
padding,
fill,
stroke,
strokeWidth,
){
// star is composed by two 5 side polygons
// wheares sin54 r = sin18 R
let r_out = radius;
let r_in = Math.sin(d2r(18)) / Math.sin(d2r(54)) * r_out;
let center = {x: r_out, y: r_out};
let s_out_pre_r = {x: r_out, y: 2 * r_out - padding};
let s_out = rotate_one(center, s_out_pre_r, d2r(180 + deg));
let s_in_pre_r = {x: r_out, y: r_out + r_in};
let s_in = rotate_one(center, s_in_pre_r, d2r(216 + deg));
let p_out = nPolyDots(5, center, s_out);
let p_in = nPolyDots(5, center, s_in);
let c_out = 0;
let c_in = 0;
let pts = [];
for(let ii = 0; ii < 10; ii++){
if(ii % 2 === 0){
pts.push({x: rit(p_out[c_out].x), y: rit(p_out[c_out].y)});
c_out++;
}else{
pts.push({x: rit(p_in[c_in].x), y: rit(p_in[c_in].y)});
c_in++;
}
}
let path = Array.from(pts, (ii, cc)=>(cc === 0 ? "M" : "L") + ii.x.toString() + ',' + ii.y.toString()).join(' ') + 'Z';
let ra = radius * 2;
return new Path(
id,
`<path id=${id} d="${path}" fill="${fill}" stroke="${stroke}" stroke-linejoin="round" stroke-linecap="round" stroke-width="${strokeWidth}"></path>`,
ra,
ra,
);
}
addAnimStatic(anim){
this.animcss.push(anim);
anim.init(this);
return anim;
}
}
class Keyframe{
constructor(id){
this.id = id;
this.cache = [];
this.tag = document.createElement('style');
// bind
this.add = this.add.bind(this);
this.remove = this.remove.bind(this);
this.replace = this.replace.bind(this);
}
add(di){
this.cache.push(di);
document.head.appendChild(this.tag);
return ()=>this.cache.indexOf(di);
}
pop(ind){
if(ind == null)return null;
this.cache.splice(ind, 1);
this.push();
return this.cache.length;
}
replace(ind, di){
this.cache[ind] = di;
}
push(){
let di = {};
this.cache.forEach(keyframe=>{
for(const [time, css] of Object.entries(keyframe)){
if(!di[time]){
di[time] = Object.assign({}, css);
}else{
for(const [prop, val] of Object.entries(css)){
if(Object.keys(di[time]).includes(prop)){
if(prop === 'transform'){
di[time][prop] += ' ' + css[prop];
}else{
di[time][prop] = css[prop];
}
}else{
di[time][prop] = css[prop];
}
}
}
}
});
// parse a string
let st = '@keyframes ' + this.id + ' {';
for(const [time, css] of Object.entries(di)){
st += time.toString() + '%{';
for(const [prop, val] of Object.entries(css)){
st += prop + ': ' + val + ';';
}
st += '}';
}
st += '}';
this.tag.innerHTML = st;
}
remove(){
document.head.removeChild(this.tag);
}
}
// use a panel to integrite all
class AnimStatic{
constructor(
name, // func name
func, // static animation function
di, // param to func
tflag, // bool transform flag
keyframe,
styles,
){
this.name = name;
this.func = func;
this.di = di;
this.tflag = tflag;
this.keyframe = keyframe;
this.styles = styles;
this.init = this.init.bind(this);
this.remove = this.remove.bind(this);
this.update = this.update.bind(this);
}
init(path){
this.host = path;
if(!this.host.keyframe)this.host.keyframe = new Keyframe(path.id + 'Anim');
this.ind = this.host.keyframe.add(this.keyframe);
this.host.keyframe.push();
render_element(this.styles, path.el);
if(path.el.style.animation !== ''){
path.el.style.animation += `, ${this.host.keyframe.id} ${this.di.dur}ms ${this.di.begin}ms ${this.di.timing} ${this.di.count}`;
}else{
path.el.style.animation = `${this.host.keyframe.id} ${this.di.dur}ms ${this.di.begin}ms ${this.di.timing} ${this.di.count}`;
}
}
remove(){
if(!this.host.keyframe.pop(this.ind())){
let disp = window.getComputedStyle(this.host.el, null).display;
this.host.el.style.animation = 'none';
setTimeout(()=>{
this.host.el.style.animation = disp;
}, 0);
}else{
let anim = this.host.el.style.animation;
let disp = window.getComputedStyle(this.host.el, null).display;
this.host.el.style.animation = 'none';
this.host.el.style.display = 'none';
setTimeout(()=>{
this.host.el.style.animation = anim;
this.host.el.style.display = disp;
}, 0);
}
}
update(di){
console.log(['update', this.host]);
Object.assign(this.di, di);
// parse a new keyframe
Object.assign(this, this.func(this.di));
this.host.keyframe.replace(this.ind(), this.keyframe);
this.host.keyframe.push();
let anim = this.host.el.style.animation;
this.host.el.style.animation = 'none';
setTimeout(()=>{
this.host.el.style.animation = anim;
}, 0);
console.log(['aftupdate', this.host]);
}
static wrapper(prop, values, percents){
let di = {};
percents.forEach((pc, ii)=>{
di[pc] = {[prop]: values[ii]};
});
return di;
}
/*
interface{
begin: Number,
dur: Number,
values: any,
percents: Array,
timing: String, default to 'ease-in-out',
count: String, default to 'indefinite',
styles: Object, default to {}
}
*/
static opacity(di){
return new AnimStatic(
'opacity',
AnimStatic.opacity,
di,
false,
AnimStatic.wrapper('opacity', di.values, di.percents),
di.styles,
)
}
// transform frame
static translate(di){
return new AnimStatic(
'translate',
AnimStatic.translate,
di,
true,
AnimStatic.wrapper('transform', di.values.map(val=>`translate(${val[0]}, ${val[1]})`), di.percents),
di.styles,
)
}
static scale(di){
return new AnimStatic(
'scale',
AnimStatic.scale,
di,
true,
AnimStatic.wrapper('transform', di.values.map(val=>`scale(${val[0]}, ${val[1]})`), di.percents),
di.styles,
)
}
static rotate(di){
return new AnimStatic(
'rotate',
AnimStatic.rotate,
di,
true,
AnimStatic.wrapper('transform', di.values.map(val=>`rotate(${val}deg)`), di.percents),
di.styles,
)
}
static skew(di){
return new AnimStatic(
'skew',
AnimStatic.skew,
di,
true,
AnimStatic.wrapper('transform', di.values.map(val=>`skew(${val}deg)`), di.percents),
di.styles,
)
}
}
let svg = new Vector(
document.body,
'light',
200,
200,
);
svg.viewBox(400, 400);
let star = svg.addPath(
Path.star(
'star',
150,
0,
10,
'gray',
'lightgreen',
15,
)
);
let a1 = star.addAnimStatic(
AnimStatic.translate({
begin: 100,
dur: 1000,
values: [['0', '0'], ['200px', '200px'], ['200px', '0']],
percents: [0, 50, 100],
timing: 'ease-in-out',
count: 'infinite',
styles: {},
})
);
setTimeout(()=>{
a1.update({
values: [['0', '0'], ['200px', '200px'], ['0', '200px']],
});
setTimeout(()=>{
console.log(['sec', a1.host]);
a1.update({
values: [['0', '0'], ['200px', '0'], ['200px', '200px']],
});
console.log(['aftsec', a1.host]);
}, 900);
}, 900);
svg{
width: 200px;
height: 200px;
}
body{
position: fixed;
margin: 0;
padding: 0;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}I am trying to apply css keyframe animation to elements by javascript.
addAnimStaticwill create a<style>tag into<header>and the name of the animation will be added to the widget.updatewill overwrite the keyframe.
here is the part that went wrong without any logic. please take a look at the end of the script and pay close attention to the console, I am trying to update the animation twice, the first one works, but the second.
before the beginning of second animation, I logged a1.host which the result is shown. however, in the next line, when it got to update function, the host property magically disappeared.
which there is absolutely no conduction in between. When the scope jumped from setTimeout to AnimateStatic.update, the property host just vanished.
I will be so glad if you can give me a hand.
after one day of trying, I still have no idea why the property just vanished.
Minimum working example
In the original design I was meant to use click to trigger the animation update, it does not work as well, the same error undefined variable this.host reported.
from js update keyframe property VANISHED
No comments:
Post a Comment