如何在Web组件中变平/反跳属性更改的回调

时间:2019-06-07 22:50:28

标签: javascript html web-component native-web-component

我有一个使用attributeChangedCallback

的Web组件
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表示怀疑,因为在每个animationFrame 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的时间戳,并将其标记为仅用于动画的可选属性。因此,每次更改属性时,我们都需要设置此额外属性以实际触发渲染。但这听起来有点hacky /补丁。

1 个答案:

答案 0 :(得分:1)

使用超时。这样一来,您的整个代码即可在执行昂贵的渲染之前执行。

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,则可能是三遍。

setTimeout可以在下一个事件周期发生。并且必须每秒超过60次。我使用01的超时将事件作为队列中的下一个事件放置。是的,在回调之前可能还会发生其他事情,但是它仍然应该在不到一秒的时间内。

requestAnimationFrame发生在刷新时间段,通常是每秒60次。