Оптимизация рендеринга холста вне экрана

Canvas

Недавно я исследовал анимационный эффект частиц холста и обнаружил, что когда количество частиц достигает определенного уровня, эффект анимации замедляется и останавливается. Я искал решение и обнаружил, что рендеринг вне экрана является наиболее рекомендуемым решением. Затем в этой статье используется рендеринг вне экрана для достижения эффекта анимации, чтобы сравнить улучшение производительности.

концепция

Я проверил информацию и дал обзор концепции внеэкранного рендеринга, что эквивалентно открытию буфера при рендеринге на экране, и отображению анимации, которую нужно заранее загрузить в буфер перед выводом на экран .

закадровый рендеринг

Рендеринг вне экрана означает, что буфер не создается, и он рисуется непосредственно на экране один за другим, что требует повторного использования API холста. Когда количество частиц достигает определенного уровня, производительность сильно снижается.

выполнить

Сначала создайте класс частиц снежинки, создайте связанные свойства и определите класс с именемsnowArraycountэто количество снежинок.

class DrawSnow {
  constructor(count) {
    this.canvas = document.getElementById('canvas');
    this.content = this.canvas.getContext('2d')
    this.width = this.canvas.width = 1200;
    this.height = this.canvas.height = 1000;
    this.r = 2.5;

    this.timer = null;
    this.snowArray= [];
    this.count = count;

    this.useOffCanvas = false; // 是否使用离屏渲染
    this.init()
  }
}

init()Функция инициализирует частицы снежинки и в зависимости от количества частиц повторяет рендеринг для генерации случайных позиций и сохранения их в массиве. После завершения инициализации начните рисовать частицы. и выполнить функцию анимацииanimate().

  init() {
    let OffScreen = '';

    if (this.useOffCanvas) {
      OffScreen = new OffScreen();
    }

    for (let i = 0; i < this.count; i++) {
      let x = this.width * Math.random();
      let y = this.height * Math.random();

      this.snowArray.push({
        x: x,
        y: y
      });
    this.draw(x, y);
    }
    this.animate();
  }

animate()Функция реализует цикл анимации, после выполнения анимации она проходит черезwindow.requestAnimationFrameдля достижения повторяющегося эффекта. По хранящимся вsnowArray[]Информация о частицах на чертеже повторяется.

  animate() {
    this.content.clearRect(0, 0, this.width, this.height);

    for (let i in this.snowArray) {
      let snow = this.snowArray[i];

      snow.y += 2;
      if (snow.y >= this.height + 10) {
        snow.y = Math.random() * 50;
      }

      this.draw(snow.x, snow.y);
    }
    this.timer = requestAnimationFrame(() => {
      this.animate();
    });
  }

Эффект

После выполнения вышеуказанных шагов взгляните на эффект в браузере.

снег

非离屏小雪

средний снег

非离屏中雪

сильный снегопад

非离屏大雪

анализ производительности

На приведенной выше анимации в правом верхнем углу находится инструмент анализа производительности, который поставляется с Chrome. Щелкните панель производительности инструментов разработчика и нажмите клавишу быстрого доступа.cmd + shift + pзатем введитеshow rendering(Откройте панель для просмотра частоты кадров в реальном времени), вы можете увидеть изменение частоты кадров в реальном времени.

Использование панели производительности было представлено ранее, давая указания:Десять минут, чтобы начать работу с панелью анализа производительности Chrome.

Частицы, которые необходимо нарисовать для легкого снега, среднего снега и сильного снега, составляют соответственно 80, 200 и 7000. Когда количество частиц мало, эффект анимации относительно плавный и поддерживается на уровне около 60 кадров в секунду. 7000, анимация начинает зависать, частота кадров быстро падает. Поскольку инструмент записи экрана повлияет на фактическое количество кадров, приведенную выше анимацию можно использовать в качестве справки, а фактическое количество кадров см. на следующем рисунке:

非离屏大雪截图

закадровый рендеринг

выполнить

принцип

Чтобы создать буфер, вам нужно создать дополнительный холст, нарисовать буферизованную картинку на холсте и отобразить холст в холст, отображаемый на экране, с помощью drawImage().

код

Во-первых, реализуйте метод построения частиц внеэкранного рендеринга и отрисовывайте его после завершения построения.move()Пропустите нарисованный холст черезdrawImageМетод отображается на экране.

// 粒子类
class OffScreen {
  constructor() {
    this.canvas = document.createElement('canvas');
    this.r = 2.5;
    this.width = this.canvas.width = 5;
    this.height = this.canvas.height = 5;
    this.ctx = this.canvas.getContext('2d');
    this.x = this.width * Math.random();
    this.y = this.height * Math.random();

    this.create();
  }
  
  // 创建粒子
  create() {
    this.ctx.save();
    this.ctx.fillStyle = 'rgba(255,255,255)';
    this.ctx.beginPath();
    this.ctx.arc(this.x, this.y, 2.5, 0, 2 * Math.PI, false);
    this.ctx.closePath();
    this.ctx.fill();
    this.ctx.restore();
  }
  
  // 绘制粒子
  move(ctx, x, y) {
    ctx.drawImage(this.canvas, x, y);
  }
}

При инициализации частицы определите, находится ли он в режиме вне экрана. В режиме вне экрана постройте частицу не экрана и сначала нарисуйте его на холсте. При пересечении массива частицanimate()выполнить вOffScreenв классеmove()способ отображения частиц, аналогичный операции копирования и вставки.

class DrawSnow {
  constructor(count,useOffCanvas) {
    ......

    this.useOffCanvas = useOffCanvas; // 是否使用离屏渲染
    this.init();
  }
    
  init() {
    let offScreen = '';

    if (this.useOffCanvas) {
      offScreen = new OffScreen();
    }

    for (let i = 0; i < this.count; i++) {
      let x = this.width * Math.random();
      let y = this.height * Math.random();

      if (this.useOffCanvas) {

        this.snowArray.push({
          instance: offScreen,
          x: x,
          y: y
        });
      } else {
        this.snowArray.push({
          x: x,
          y: y
        });
        this.draw(x, y);
      }
    }
    this.animate();
  }
  
  animate() {
    this.content.clearRect(0, 0, this.width, this.height);

    for (let i in this.snowArray) {
      let snow = this.snowArray[i];

      snow.y += 2;
      if (snow.y >= this.height + 10) {
        snow.y = Math.random() * 50;
      }

      if (this.useOffCanvas) {
        snow.instance.move(this.content, snow.x, snow.y);
      } else {
        this.draw(snow.x, snow.y);
      }
    }
    this.timer = requestAnimationFrame(() => {
      this.animate();
    });
  }
}

Эффект

снег

离屏小雪

средний снег

离屏中雪

сильный снегопад

离屏大雪

анализ производительности

В отличие от не удаленного рендеринга, обнаружено, что разрыв не очевиден, когда количество частиц не так много, и когда количество частиц достигает 7000, существует значительный разрыв.

В приведенной выше анимации при рендеринге экрана частота кадров анимации сильного снегопада достигает в среднем 23 кадра в секунду, и инструмент записи влияет на производительность.Фактическая производительность выглядит следующим образом:

离屏大雪截图

По сравнению с незакадровым режимом частота кадров удваивается.

Как выбрать использование внеэкранного рендеринга

В приведенном выше примере использование внеэкранного рендеринга увеличивает частоту кадров анимации, но внеэкранный рендеринг не всегда применим.

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

Эффект случая

Сначала посмотрите на результаты, достигнутые в двух модах и частоте кадров, но скорость кадров без экрана ниже, с предыдущими результатами точно обратно.

复杂非离屏大雪截图

复杂离屏大雪截图

причина

Новые частицы генерируются случайным образом по размеру, положению и прозрачности.Если внеэкранные частицы построены прежним способом, свойства каждой частицы будут одинаковыми, и случайные эффекты не могут быть достигнуты. В этом примере необходимо передать конструктору разные параметры через цикл, что эквивалентно многократному вызову API-интерфейса холста конструктора. По сравнению с неэкранным режимом рендеринга он также увеличивает потребление производительности при создании буфера и рисовании из буфера на экран, поэтому частота кадров ниже, чем у неэкранного режима.

В предыдущем примере размер, цвет и прозрачность частиц одинаковы, и нет необходимости повторять построение, поэтому конструктор вызывается только один раз, и апи холста для рисования вызывается только один раз.

Связанный код

Глядя на приведенный ниже код в сочетании с описанным выше методом конструирования во внеэкранном режиме, можно обнаружить, что в этом примере новые частицы строятся в цикле, а API вызывается непрерывно, что не снижает потребление производительности.

  init() {
    let offScreen = '';

    for (let i = 0; i < this.count; i++) {
      let x = this.width*Math.random();
      let y = this.height*Math.random();
      let alpha = (Math.floor(Math.random() * 10) + 1) / 10 / 2;
      let color = "rgba(255,255,255," + alpha + ")";
      let r = Math.random() * 2 + 1;

      if (this.useOffCanvas) {
      
        // 循环构造新的粒子
        offScreen = new OffScreen();

        this.snowArray.push({
          instance: offScreen,
          x: x,
          y: y,
          color: color,
          r:r
        });
      } else {
        this.snowArray.push({
          x: x,
          y: y,
          color: color,
          r:r
        });
        this.draw(x,y,color,r);
      }
    }
    this.animate();
  }

FPS

FPS — это определение в поле изображения, которое относится к количеству кадров в секунду, передаваемых экраном.

Теоретически, чем выше FPS, более гладкая анимация, текущая скорость обновления экрана большинства устройств составляет 60 раз в секунду, поэтому, как правило, говорящие FPS - 60 кадров / с, когда лучшие анимационные эффекты.

Когда частота кадров отличается, каков визуальный эффект анимации?

  • Анимации с частотой кадров от 50 до 60 FPS будут достаточно плавными и комфортными;
  • Для анимаций с частотой кадров от 30 до 50 кадров в секунду уровень комфорта варьируется от человека к человеку из-за их разных уровней чувствительности;
  • Анимации с частотой кадров ниже 30 кадров в секунду заставляют людей чувствовать явные заикания и дискомфорт; Анимации с большими колебаниями частоты кадров также могут заставить людей чувствовать себя застрявшими.

Поэтому для плавной анимации частота кадров должна достигать 30 кадров в секунду или выше.

Для конкретного анализа, пожалуйста, обратитесь к:[Внешняя производительность] Расчет частоты кадров веб-анимации (FPS)

Суммировать

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

Например, во втором примере выше обращение к апи не уменьшается, но внеэкранный процесс увеличивает потребление производительности, что не подходит для данной ситуации. Внеэкранный рендеринг также требует выбора разумного сценария использования.