我有一个正在使用 attributeChangedCallback
attributeChangedCallback(name, oldValue, newValue) {
// TODO: use something like rxjs debounce time?
this.expensiveRenderer()
}
我正在为每个动画帧上的两个属性设置新值: 另外,这可以增加到设置 4 个属性。
component.setAttribute("att1", r);
component.setAttribute("att2", p);
这将触发 attributeChangedCallback
两次,并且昂贵的渲染器也会触发两次。
是否有一种有效的方法可以将两个属性设置在一起,或者使更改的效果作为单个事件类似于去抖时间等?
我对使用 setTimeout
/clearTimeout
持怀疑态度,因为这是在每个动画帧 60 fps 上调用的。
为了提供更好的概述,我的组件看起来有点像:
<mm-spirograph
fixed-circle-radius="100"
moving-circle-radius="10"
moving-circle-locus-length="30"
repeat-count="100"
></mm-spirograph>
它使用 webGL 渲染螺旋图,并计划用于生成艺术。 我喜欢这种简单性,并且有点不愿意使用 JSON 属性。
此外,呼吸描记器的动画与组件分开,其想法是使用呼吸描记器作为静态渲染或更改属性可以轻松地实现类似动画的效果。这里它仅对两个属性进行动画处理,但对于不同的情况它可能会有所不同。
此外,还计划添加类似的组件,如果需要,可以通过设置属性来实现动画效果。
function animateSpirograph(spirograph, r, p, rIncrement, pIncrement) {
let v = r;
if (v + rIncrement > 100) rIncrement = -Math.abs(rIncrement);
if (v + rIncrement <= 0) rIncrement = Math.abs(rIncrement);
v = v + rIncrement;
r = v;
let w = p;
if (w + pIncrement > 200) pIncrement = -Math.abs(pIncrement);
if (w + pIncrement <= 0) pIncrement = Math.abs(pIncrement);
w = w + pIncrement;
p = w;
spirograph.setAttribute("moving-circle-radius", r);
spirograph.setAttribute("moving-circle-locus-length", p);
window.requestAnimationFrame(() =>
animateSpirograph(spirograph, r, p, rIncrement, pIncrement)
);
}
Danny 的建议很有趣,我可以有第三个属性,它可以接收 requestAnimationFrame 中的时间戳,并将其标记为仅用于动画的可选属性。因此,每次更改属性时,我们都需要设置这个额外的属性来实际触发渲染。但这听起来有点老套/补丁。
最佳答案
使用超时。这允许您的整个代码在进行昂贵的渲染之前执行。
class MyEl extends HTMLElement {
constructor() {
super();
this.renderTimeout = null;
this.renderCalled = 0;
this.renderDone = 0;
}
static get observedAttributes() {
return ['time','direction'];
}
expensiveRenderer() {
this.renderCalled++;
if (this.renderTimeout) {
clearTimeout(this.renderTimeout);
}
this.renderTimeout = setTimeout(
() => {
this.renderDone++;
this.innerHTML = `<p>The time is: ${this._time}</p><p>And the direction is ${this._direction}</p><p>Render called: ${this.renderCalled}</p><p>Rendered: ${this.renderDone}</p>`;
}, 1
);
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
this[`_${name}`] = newValue;
this.expensiveRenderer();
}
}
}
customElements.define('my-el', MyEl);
const component = document.querySelector('my-el');
setTimeout(() => {
component.setAttribute("time", "1:00");
component.setAttribute("time", "2:00");
component.setAttribute("time", "3:00");
component.setAttribute("time", "4:00");
component.setAttribute("direction", "East");
component.setAttribute("direction", "West");
component.setAttribute("direction", "South");
}, 2000);
<my-el time="12:00" direction="North"></my-el>
在此我们多次设置属性并多次调用该函数。但您会看到我们实际上只执行假昂贵的渲染例程两次。 (如果 DOM 解析器需要额外的时间来解析您的初始 HTML,则可能是 3 次。
setTimeout
可以在下一个事件周期时立即发生。并且速度必须快于每秒 60 次。我使用 0
或 1
超时将事件作为队列中的下一个事件。是的,在回调之前可能会发生其他事情,但它仍然应该在亚秒级的时间范围内。
requestAnimationFrame
发生在刷新时间范围内,通常为每秒 60 次。
关于javascript - 如何在 Web 组件中展平/消除属性更改回调,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56501986/