Как сгладить / отменить изменение атрибута обратного вызова в веб-компонентах - PullRequest
0 голосов
/ 08 июня 2019

У меня есть веб-компонент, который использует 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 кадров в секунду.

Для лучшего обзора мой компонент выглядит примерно так:

<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)
  );
}

То, что предложил Дэнни, интересно, у меня может быть третий атрибут, который может принимать значение, может быть отметкой времени из requestAnimationFrame и помечать ее как необязательный атрибут, используемый только для анимации. Таким образом, каждый раз, когда атрибуты меняются, нам нужно установить этот дополнительный атрибут для фактического запуска рендера. Но это звучит немного хаки / патч.

1 Ответ

1 голос
/ 08 июня 2019

Используйте тайм-аут. Это позволяет выполнить весь код до выполнения дорогостоящего рендеринга.

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 раз в секунду. Я использую тайм-аут 0 или 1, чтобы поместить событие как следующую вещь в очередь. Да, другие вещи могут произойти до обратного вызова, но это все равно должно быть в субсекундном периоде.

requestAnimationFrame происходит во время обновления, которое часто составляет 60 раз в секунду.

...