предисловие
В прошлогоднем рождественском продукте появился запрос на активность с анимацией снежного фона. В процессе создания этой анимации я немного углубил понимание анимации холста. Здесь я просто делюсь некоторыми идеями. Жду критики от всех вас.
Код загружен на гитхаб, желающие могут клонировать код для локального запуска. Надеюсь дать звезду, чтобы поддержать его.
Введите вопрос
Стили пользовательского интерфейса, указанные в требованиях, следующие:
Требование пользовательского интерфейса заключается в том, чтобы направление падения снежинок было немного наклонным, а скорость падения каждой снежинки была разной, но должна оставаться в определенном диапазоне.
Вам нужно знать об этом эффекте, чтобы начать добиваться этого эффекта (вам нужно понять некоторые основные API холста, прежде чем читать эту статью).
drawImage
drawImage
Вы можете передать параметры 9. Чаще всего используются 5 параметров на изображении выше, а остальные параметры используются для вырезания изображений.
Использовать напрямую
drawImage
Чтобы вырезать картинку, ее производительность будет не очень, рекомендуется использовать закадровую часть для той части, которую необходимо использовать.canvas
Сохраните его и используйте, когда вам это нужно.
requestAnimationFrame
requestAnimationFrame
относительноsetinterval
Работа с анимацией имеет несколько преимуществ:
- Браузер оптимизирован для более плавной анимации
- Когда окно не активировано, анимация останавливается, экономя вычислительные ресурсы.
- Больше энергосбережения, особенно для мобильных терминалов
Этому API не нужно передавать интервал анимации, этот метод скажет браузеру перерисовать анимацию наилучшим образом.
Из-за проблем с совместимостью можно использовать следующие методы дляrequestAnimationFrame
Чтобы переписать:
window.requestAnimationFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
Пожалуйста, обратитесь к документации для других API.
первая попытка
Когда у меня появляется общее представление, я с радостью начинаю писать код.Основная идея заключается в использованииrequestAnimationFrame
обновитьcanvas
артборд.
Поскольку снежинки имеют неправильную форму, снежинки — это изображения, предоставляемые пользовательским интерфейсом. Поскольку это изображения, нам нужно сначала предварительно загрузить изображения, иначе при преобразовании изображений может снизиться производительность.
Используемый метод предварительной загрузки выглядит следующим образом:
function preloadImg(srcArr){
if(srcArr instanceof Array){
for(let i = 0; i < srcArr.length; i++){
let oImg = new Image();
oImg.src = srcArr[i];
}
}
}
Я переписывал его взад и вперед в течение дня, и он был закончен.Когда я проверил его на своем мобильном телефоне, я обнаружил, что он очень застрял. 100 снежинокFPS
Было только за 40. А в некоторых моделях будет джиттер.
Если продукт увидит этот эффект, я боюсь, что нужно будет созывать соответствующий персонал для проведения соответствующего совещания. Такое отставание, должно быть, написало какой-то дорогой код, поэтому нужна вторая попытка.
Вам все равно нужно уходить с работы вовремя ночью. Однако, вернувшись домой с работы, вы не можете бездействовать, и начинаете искать актуальную информацию, чтобы на следующий день быстро заполнить ее.
Подготовка перед второй попыткой
После ночи поиска и обучения я, вероятно, знаю следующие оптимизацииcanvas
Методы выполнения:
1. Рисуйте сложные сцены с помощью многослойных полотен
Цель многослойности — уменьшить совершенно ненужные накладные расходы на производительность рендеринга.
То есть: разделить часть с высокой частотой изменений и большой амплитудой и часть с малой частотой изменений и малой амплитудой на две или более частей.
canvas
объект. То есть многократноcanvas
Например, они разместили по одномуCanvas
использовать разныеz-index
чтобы определить порядок укладки.
<canvas style="position: absolute; z-index: 0"></canvas>
<canvas style="position: absolute; z-index: 1"></canvas>
// js 代码
2. Анимируйте с помощью requestAnimationFrame
упомянутый выше.
3. Используйте clearRect для очистки холста
Производительность в целом:clearRect
> fillRect
> canvas.width = canvas.width;
4. Пререндеринг с рисованием за кадром
использовался в то времяdrawImage
Нарисуйте ту же область:
- Если источник данных (изображение, холст) и
canvas
Размер чертежной доски аналогичен, тогда производительность будет лучше; - Если источник данных является только частью большего изображения, производительность будет низкой, поскольку каждый рисунок также включает работу по обрезке.
Во втором случае мы можем сначала обрезать рисуемую область и сохранить ее за кадром.
canvas
в объекте. При рисовании каждого кадра рисуйте этот объект вcanvas
в артборде.
drawImage
Первый параметр метода может не только получатьImage
объект, который также может принимать другойCanvas
объект. Кроме того, используйтеCanvas
Накладные расходы на отрисовку объектов и их использованиеImage
Накладные расходы на объект почти одинаковы.
Когда объект, который нужно вызывать, каждый кадр нужно вызывать несколько разcanvasAPI
, мы также можем использовать отрисовку за кадром для предварительного рендеринга для повышения производительности.
который:
let cacheCanvas = document.createElement("canvas");
let cacheCtx = this.cacheCanvas.getContext("2d");
cacheCtx.save();
cacheCtx.lineWidth = 1;
for(let i = 1;i < 40; i++){
cacheCtx.beginPath();
cacheCtx.strokeStyle = this.color[i];
cacheCtx.arc(this.r , this.r , i , 0 , 2*Math.PI);
cacheCtx.stroke();
}
this.cacheCtx.restore();
// 在绘制每一帧的时候,绘制这个图形
context.drawImage(cacheCtx, x, y);
cacheCtx
Установите ширину и высоту на фактические ширину и высоту, насколько это возможно, иначе слишком много пустого пространства также приведет к снижению производительности.
На следующем графике показано улучшение производительности, вызванное методом предварительного рендеринга с использованием внеэкранного рисования:
5. Делайте как можно меньше звонковcanvasAPI
, нарисуйте как можно больше
Следующий код:
for (var i = 0; i < points.length - 1; i++) {
var p1 = points[i];
var p2 = points[i + 1];
context.beginPath();
context.moveTo(p1.x, p1.y);
context.lineTo(p2.x, p2.y);
context.stroke();
}
Можно изменить на:
context.beginPath();
for (var i = 0; i < points.length - 1; i++) {
var p1 = points[i];
var p2 = points[i + 1];
context.moveTo(p1.x, p1.y);
context.lineTo(p2.x, p2.y);
}
context.stroke();
Советы: при написании эффектов частиц можно использовать квадраты вместо кругов, так как частицы маленькие, квадраты и круги выглядят одинаково. Кто-то спросил, почему? Легко понять, что для рисования круга требуется три шага:
beginPath
, затем используйтеarc
нарисовать дугу, использовать сноваfill
. Художнику нужен только одинfillRect
. Разрыв в производительности проявится, когда количество объектов-частиц достигнет определенного числа.
6. Операции на уровне пикселей стараются избегать операций с плавающей запятой
провести
canvas
Когда анимация отрисовывается, если координаты представляют собой числа с плавающей запятой, может появитьсяCSS Sub-pixel
То есть значение с плавающей запятой будет автоматически округлено до целого числа, что может вызвать дрожание в процессе анимации, а также может вызвать искажение сглаживания на краю элемента.
Хотя javascript предоставляет некоторые методы округления, такие какMath.floor
,Math.ceil
,parseInt
,ноparseInt
Этот метод выполняет некоторую дополнительную работу (например, определяет, являются ли данные допустимым значением, сначала преобразует параметр в строку и т. д.), поэтому напрямую используйтеparseInt
Условно говоря, он потребляет больше производительности.
Округление можно выполнить напрямую следующими умными методами:
function getInt(num){
var rounded;
rounded = (0.5 + num) | 0;
return rounded;
}
Кроме того, эффективность цикла for самая высокая, и желающие могут поэкспериментировать самостоятельно.
вторая попытка
Во время вчерашнего обзора в эту анимацию были внесены следующие оптимизации:
- Пререндеринг с рисованием за кадром
- Сокращение использования некоторых API
- Округление с плавающей запятой
- кеш-переменная
- Используйте цикл for вместо forEach
- Переписал весь код, используя метод цепочки прототипов.
После того, как план написан, вы можете с радостью приступить к написанию кода.
200 снежинокFPS
В основном стабильный на 60, а дрожит также ушел;
При увеличении до 1000 шт.FPS
Все еще в основном стабильно на уровне 60;
При увеличении до 1500 штук появляются единичные кадры карт;
Когда она увеличилась до 2000 штук, она начала замерзать.
Это показывает, что эта анимация все еще не оптимизирована, и есть еще возможности для оптимизации.Пожалуйста, дайте мне несколько советов.
Рекомендуемое использование
stats.js
Плагин, этот плагин может отображать FPS во время работы анимации.
Основной код
let snowBox = function () {
let canvasEl = document.getElementById("snowFall");
let ctx = canvasEl.getContext('2d');
canvasEl.width = window.innerWidth;
canvasEl.height = window.innerHeight;
let lineList = []; // 雪的容器
let snow = function () {
let _this = this;
_this.cacheCanvas = document.createElement("canvas");
_this.cacheCtx = _this.cacheCanvas.getContext("2d");
_this.cacheCanvas.width = 10;
_this.cacheCanvas.height = 10;
_this.speed = [1, 1.5, 2][Math.floor(Math.random()*3)]; // 雪花下落的三种速度,便于取整
_this.posx = Math.round(Math.random() * canvasEl.width); // 雪花x坐标
_this.posy = Math.round(Math.random() * canvasEl.height); // 雪花y坐标
_this.img = `./img/snow_(${Math.ceil(Math.random() * 9)}).png`; // img
_this.w = _this.getInt(5 + Math.random() * 6);
_this.h = _this.getInt(5 + Math.random() * 6);
_this.cacheSnow();
};
snow.prototype = {
cacheSnow: function () {
let _this = this;
// _this.cacheCtx.save();
let img = new Image(); // 创建img元素
img.src = _this.img;
_this.cacheCtx.drawImage(img, 0, 0, _this.w, _this.h);
// _this.cacheCtx.restore();
},
fall: function () {
let _this = this;
if (_this.posy > canvasEl.height + 5) {
_this.posy = _this.getInt(0 - _this.h);
_this.posx = _this.getInt(canvasEl.width * Math.random());
}
if (_this.posx > canvasEl.width + 5) {
_this.posx = _this.getInt(0 - _this.w);
_this.posy = _this.getInt(canvasEl.height * Math.random());
}
// 如果雪花在可视区域
if (_this.posy <= canvasEl.height || _this.posx <= canvasEl.width) {
_this.posy = _this.posy + _this.speed;
_this.posx = _this.posx + _this.speed * .5;
}
_this.paint();
},
paint: function () {
ctx.drawImage(this.cacheCanvas, this.posx, this.posy)
},
getInt: function(num){
let rounded;
rounded = (0.5 + num) | 0;
return rounded;
}
};
let control;
control = {
start: function (num) {
for (let i = 0; i < num; i++) {
let s = new snow();
lineList.push(s);
}
(function loop() {
ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);
for (let i = 0; i < num; i++) {
lineList[i].fall();
}
requestAnimationFrame(loop)
})();
}
};
return control;
}();
window.onload = function(){
snowBox.start(2000)
};
Рекомендуется клонировать код с github для локального запуска.
позже
Хотя эта статья посвящена оптимизации производительности анимации холста. Некоторые начальники также видели, что другие решения по оптимизации производительности примерно такие же, как это, не более того:
- Сокращение использования API
- Использовать кеш (выделено)
- Объединение часто используемых API
- Избегайте использования энергоемких API
- Используйте webWorker для выполнения некоторых трудоемких вычислений.
- ...
Я надеюсь, что, прочитав эту статью, я смогу дать вам ссылку на оптимизацию производительности, спасибо за чтение.
Серия интерфейсных словарей
Серия «Front-end Dictionary» будет постоянно пополняться, и в каждом выпуске я буду рассказывать о точке знаний, которая появляется все чаще. Надеюсь, что в процессе прочтения в тексте будут неточности или ошибки, буду очень признательна, если что-то почерпну из этой серии, тоже буду очень рада.
Если вы считаете, что мои статьи написаны хорошо, то можете обратить внимание на мой паблик в WeChat, в котором заранее будут раскрыты спойлеры.
Вы также можете добавить мой WeChat wqhhsd, добро пожаловать в общение.
следующее уведомление
[Внешний словарь] Какие ссылки кэширования задействованы от ввода URL до отображения
портал
- [Интерфейсный словарь] Неожиданная удача после разговора с невесткой об агентстве
- [Front-end Dictionary] Решение проблемы проникновения прокрутки
- Наследование (1) — Вы действительно понимаете цепочку прототипов?
- [Внешний словарь] Наследование (2) - Восемь способов написать ответ · Вопросы для интервью
- [Фронт-конечный словарь] Сетевой фонд, необходимый для продвинутых (1)
- [Внешний словарь] Сетевая основа необходима для продвинутых (ниже)
- [Внешний словарь] Вы можете понять разницу между F5 и Ctrl+F5