существуетПроцесс рендеринга в браузере и оптимизация производительностиВ этой статье (рекомендуется прочитать эту статью перед прочтением этой статьи) мы разбираемся и понимаем критический путь рендеринга браузера и как оптимизировать скорость загрузки страницы. В этой статье мы в основном сосредоточимся на том, как улучшить производительность рендеринга браузера (браузер выполняет расчеты макета, отрисовку пикселей и т. д.) и эффективность.
Многие веб-страницы используют прикольную анимацию для взаимодействия с пользователями. Эта анимация значительно улучшает взаимодействие с пользователем, но если частота кадров анимации слишком мала из соображений производительности, это ухудшит работу пользователя. Хуже (если прикольная анимация всегда заикается или кажется медленным, это заставит пользователя чувствовать себя плохо).
Плавная анимация должна поддерживаться на уровне 60 кадров в секунду, которые преобразуются в миллисекунды, и браузер должен выполнить задачу рендеринга примерно за 10 миллисекунд (есть 1000 миллисекунд в секунду, 1000/60 — это около 16 миллисекунд на кадр, но браузер по-прежнему нуждается в другой работе.Это занимает время, поэтому оценивается в 10 мс), если вы можете понять процесс рендеринга браузера, найти узкое место в производительности и оптимизировать его, вы можете сделать свой проект интерактивным, а эффект анимации шелковистым. гладкий.
Автор этой статьи:SylvanasSun(sylvanas.sun@gmail.com).Для перепечатки просим разместить этот абзац в начале статьи (сохранить гиперссылку).
Эта статья была впервые опубликована сSylvanasSun Blog, оригинальная ссылка:sylva, то есть sun.GitHub.IO/2017/10/08/…
пиксельный конвейер
Так называемый пиксельный конвейер на самом деле представляет собой процесс, с помощью которого браузер рисует дерево рендеринга в пикселях. Каждая область конвейера может заикаться, то есть, если область в конвейере изменится, браузер автоматически переформатирует, а затем перерисует затронутую область.
-
JavaScript:Эта область на самом деле относится к методу достижения эффекта анимации., обычно используется
JavaScript
анимировать, напр.JQuery
изanimate
функция, сортировка набора данных или динамическое добавление некоторыхDOM
узел и т. д. Конечно, для достижения анимационных эффектов можно использовать и другие методы, такие какCSS
изAnimation
,Transition
а такжеTransform
. -
Стиль:Эта область является этапом расчета стиля, и браузер будет использовать селектор (т. е.
CSS
селектор, например.td
), чтобы выяснить, какие узлы применяются к какимCSS
rules, затем вычислите окончательный стиль для каждого узла и примените его к узлу. -
Макет:Эта область является этапом расчета макета, во время которого браузер рассчитает размер занимаемого пространства и положение на экране в соответствии со стилевыми правилами ноды..
-
Краска:Эта область является фазой рисования, браузер сначала создаст список вызовов отрисовки, а затем заполнит пиксели.. Фаза рисования включает в себя текст, цвета, изображения, границы и тени, практически каждую видимую часть. Рисунок обычно выполняется на нескольких слоях (используется
Photoshop
Слой слова должен быть знаком детской обуви программного обеспечения для редактирования изображений, и значение слоя здесь на самом деле аналогично). -
Композитный:Эта область является фазой компоновки, когда браузер выводит на экран несколько слоев в правильном порядке.
Предположим, мы изменяем геометрическое свойство (например, ширину, высоту и т. д., которые влияют на макет), затем затрагивается этап макета, браузер должен проверять элементы во всех других областях, а затем автоматически переформатировать страницу, любая затронутая часть должна быть Redrawing, а окончательный нарисованный элемент также необходимо перекомпоновать (проще говоря, весь пиксельный конвейер должен быть перевыполнен).
Если мы изменяем только свойства, не влияющие на макет страницы, такие как фоновое изображение, цвет текста и т. д., то браузер пропустит этап макета, но все равно потребуется перерисовка.
Или, если мы изменим только свойство, которое не влияет на компоновку или рисование, тогда браузер пропустит этапы компоновки и рисования Очевидно, что это изменение имеет наименьшую нагрузку на производительность.
Если вы хотите знать каждыйCSS
Как свойства повлияют на какой этап, перейти кCSS Triggers, сайт подробно описывает каждыйCSS
На какой этап влияет свойство.
Анимация с помощью функции RequestAnimationFrame
мы часто используемJavaScript
для достижения анимационного эффекта, однако неподходящего времени или длительного действияJavaScript
Возможно, именно поэтому ваша производительность падает.
избегать использованияsetTimeout()
илиsetInterval()
функция для достижения эффекта анимации, основная проблема с этим подходомОбратный вызов будет выполняться в какой-то момент кадра, который может быть прямо в конце (кадры будут потеряны, что приведет к заиканию).
Некоторые сторонние библиотеки все еще используютсяsetTimeout()&setInterval()
функция для достижения анимационных эффектов, что приведет к ненужному снижению производительности, например, в старой версииJQuery
, если вы используетеJQuery3
, то не беспокойтесь об этом,JQuery3
Модуль анимации был полностью переписан с использованиемrequestAnimationFrame()
функция для достижения эффекта анимации. Но если вы используете предыдущую версиюJQuery
, то вам нужноjquery-requestAnimationFrameбудущееsetTimeout()
заменитьrequestAnimationFrame()
функция.
Прочитав это, вы должны бытьrequestAnimationFrame()
Полюбопытствовать. Чтобы получить плавную анимацию, мы хотим, чтобы визуальное изменение происходило в начале каждого кадра, обеспечивая при этомJavaScript
Способ запуска в начале кадра заключается в использованииrequestAnimationFrame()
функция, которая по существу аналогичнаsetTimeout()
Нет никакой разницы, все они рекурсивно вызывают одну и ту же функцию обратного вызова, чтобы постоянно обновлять экран для достижения эффекта анимации.requestAnimationFrame()
используется следующим образом:
function updateScreen(time) {
// 这是你的动画效果函数
}
// 将你的动画效果函数放入requestAnimationFrame()作为回调函数
requestAnimationFrame(updateScreen);
Не все браузеры поддерживаютrequestAnimationFrame()
функции, такие какIE9
(опять злоIE
), но в основном современные браузеры поддерживают эту функцию. Если вам нужна совместимость со старыми браузерами, вы можете использовать следующую функцию.
// 本段代码截取自Paul Irish : https://gist.github.com/paulirish/1579671
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
|| window[vendors[x]+'CancelRequestAnimationFrame'];
}
// 如果浏览器不支持,则使用setTimeout()
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
Web Workers
мы знаемJavaScript
является однопоточным, но браузеры не являются однопоточными.JavaScript
запускать в основном потоке браузера, который выполняется вместе с операциями рендеринга в расчетах стилей, макетах и многих других случаях,еслиJavaScript
Если время выполнения слишком велико, это заблокирует эту последующую работу, что приведет к потере кадров.
использоватьChrome
инструменты разработчикаTimeline
функция, которая поможет нам увидеть каждыйJavaScript
Время выполнения скриптов (включая индексы) помогает нам находить и устранять узкие места в производительности.
Поиск влиятельной производительностиJavaScript
После скрипта можем пройтиWeb Workers
оптимизировать.Web Workers
даHTML5
предлагаемый стандарт, которыйможет позволитьJavaScript
Скрипт выполняется в фоновом потоке (аналогично созданию дочернего потока), и фоновый поток не влияет на страницу в основном потоке.. но,использоватьWeb Workers
Созданный поток не может работатьDOM
дерево(это тожеWeb Workers
нет подрывной деятельностиJavaScript
является причиной одиночного потока,JavaScript
Причина, по которой он всегда был однопоточным, в основном состоит в том, чтобы избежать нескольких операций скрипта.DOM
Проблема синхронизации дерева, это значительно усложнит), поэтому подходит только для некоторых чистых вычислений (сортировка данных, обход и т. д.).
если вашJavaScript
Он должен выполняться в основном потоке, тогда можно выбрать только другой метод. Разделите большую задачу на несколько небольших задач (каждая занимает не более нескольких миллисекунд) иrequestAnimationFrame()
запустить в функции:
var taskList = breakBigTaskIntoMicroTasks(monsterTaskList);
requestAnimationFrame(processTaskList);
function processTaskList(taskStartTime) {
var taskFinishTime;
do {
// 从列表中弹出任务
var nextTask = taskList.pop();
// 执行任务
processTask(nextTask);
// 如果有足够的时间进行下一个任务则继续执行
taskFinishTime = window.performance.now();
} while (taskFinishTime - taskStartTime < 3);
if (taskList.length > 0)
requestAnimationFrame(processTaskList);
}
СоздаватьWeb Workers
Объекты простые, просто позвонитеWorker()
конструктор, а затем передать указанный скриптURI
. Современные основные браузеры поддерживаютWeb Workers
,КромеInternet Explorer
(Это снова злой IE), поэтому нам также нужно проверить совместимость браузера в следующем примере кода.
var myWorker;
if (typeof(Worker) !== "undefined") {
// 支持Web Workers
myWorker = new Worker("worker.js");
} else {
// 不支持Web Workers
}
Web Workers
через основной потокpostMessage()
функция отправки информации, используйтеonmessage()
Обработчики событий для ответа на сообщения (данные не распределяются между основным потоком и подпотоками, они просто взаимодействуют, копируя данные).
main.js:
// 在主线程js中发送数据到myWorker绑定的js脚本线程
myWorker.postMessage("Hello,World");
console.log('Message posted to worker');
worker.js:
// onmessage处理函数允许我们在任何时刻,
// 一旦接收到消息就可以执行一些代码,代码中消息本身作为事件的data属性进行使用。
onmessage = function(data) {
console.log("Message received from main script.");
console.log("Posting message back to main script.");
postMessage("Hello~");
}
main.js:
// 主线程使用onmessage接收消息
myWorker.onmessage = function(data) {
console.log("Received message: " + data);
}
Если вам нужно немедленно завершить работающий воркер из основного потока, вызовите метод воркераterminate()
функция:
myWorker.terminate();
myWorker будет немедленно уничтожен, и у него не будет возможности продолжить выполнение остальной работы. И его также можно вызвать в рабочем потокеclose()
функция закрытия:
close();
для большегоWeb Workers
Для использования см.Using Web Workers - Web APIs | MDN.
Уменьшить сложность расчета стилей
каждая модификацияDOM
а такжеCSS
заставит браузер пересчитывать стили, а во многих случаях пересчитывает макет страницы или ее части.
Первая часть вычисления стиля заключается в создании набора совпадающих селекторов (для вычисления того, какие узлы применяют какие стили), а вторая часть включает в себя получение всех правил стиля из совпадающих селекторов и вычисление окончательного стиля узлов.
Скорость расчета стиля можно повысить, уменьшив сложность селекторов.
Ниже представлен комплексCSS
Селектор:
.box:nth-last-child(-n+1) .title {
/* styles */
}
Если вы хотите, чтобы браузер узла применил стиль, вам нужно найти.title
Узел класса, тогда его родитель точно отрицательный n дочерних элементов + 1 полоса.box
узел класса. Браузеру может потребоваться значительное время для вычисления этого результата, но мы можем изменить ожидаемое поведение селектора на класс:
.final-box-title {
/* styles */
}
мы просто положилиCSS
Модульность именования (уменьшает сложность селектора), а затем позволяет браузеру просто сопоставить селектор с узлом, поэтому эффективность браузера для вычисления стиля будет значительно повышена.
BEM
является модульнымCSS
Соглашения об именах, организованные с использованием этого методаCSS
Структура не только очень понятна, но и помогает при поиске в стиле браузера.
BEM
На самом деле этоBlock,Element,Modifier
, который представляет собой компонентный подход к разработке, его идея состоит в том, чтобы разделить пользовательский интерфейс на независимые части. Таким образом, даже используя сложныеUI
Его также можно легко и быстро разработать, а модульный подход может повысить возможность повторного использования кода.
Block
является функционально независимым компонентом страницы (который можно использовать повторно),Block
называется как письмоClass
то же имя. как показано ниже.button
представляет<button>
изBlock
.
.button {
background-color: red;
}
<button class="button">I'm a button</button>
Element
тот, который нельзя использовать в одиночкуBlock
составная часть. Это можно считатьElement
даBlock
дочерний узел .
<!-- `search-form`是一个block -->
<form class="search-form">
<!-- 'search-form__input'是'search-form' block中的一个element -->
<input class="search-form__input">
<!-- 'search-form__button'是'search-form' block中的一个element -->
<button class="search-form__button">Search</button>
</form>
Modifier
используется для определенияBlock
илиElement
внешний вид, состояние или поведение объекта. Предположим, у нас есть новое требование дляbutton
цвет фона с использованием зеленого, тогда мы можем использоватьModifier
правильно.button
Сделайте расширение:
.button {
background-color: red;
}
.button--secondary {
background-color: green;
}
первый контактBEM
детской обуви это название может показаться странным, ноBEM
Важна идея модульности и ремонтопригодности, Что касается именования, вы можете изменить его так, как вы можете принять. Из-за нехватки места в этой статье мы не будем продолжать обсуждениеBEM
Теперь заинтересованные детские туфли могут пойти посмотретьОфициальная документация БЭМ.
Избегайте принудительной синхронизации макета и дрожания макета
Каждый раз, когда браузер производит расчет макета, это почти всегда влияет на весьDOM
, если элементов большое количество, вычисление положения и размера всех элементов займет много времени.
поэтому мыВы должны стараться избегать динамического изменения свойств геометрии во время выполнения.(ширина, высота и т. д.), потому что эти изменения заставят браузер пересчитать макет. Если неизбежно, тоПриоритетное использованиеFlexbox
, что минимизирует накладные расходы, необходимые для макета.
Чтобы заставить синхронную компоновку использоватьJavaScript
Заставить браузер выполнять макет раньше времени. Сначала нужно понять,существуетJavaScript
Во время выполнения известны все старые значения макета из предыдущего кадра.
Например, следующий код выводит высоту элемента в начале каждого кадра:
requestAnimationFrame(logBoxHeight);
function logBoxHeight() {
console.log(box.offsetHeight);
}
Но если вы измените стиль высоты перед ее запросом, возникнет проблема, браузер должен сначала применить стиль, затем выполнить расчет макета, а затем вернуть правильную высоту. Это необязательно и создает очень большие накладные расходы.
function logBoxHeight() {
box.classList.add('super-big');
console.log(box.offsetHeight);
}
Правильный подход — воспользоваться возможностью браузера использовать значения макета предыдущего фрейма перед выполнением каких-либо операций записи:
function logBoxHeight() {
console.log(box.offsetHeight);
box.classList.add('super-big');
}
Дрожание макета возникает, когда принудительная синхронная компоновка выполняется одна за другой.. Следующий код перебирает набор абзацев и устанавливает ширину каждого абзаца в соответствии с шириной элемента с именем «box».
function resizeAllParagraphsToMatchBlockWidth() {
for (var i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = box.offsetWidth + 'px';
}
}
Проблема с этим кодом в том, что каждая итерация считываетbox.offsetWidth
, затем немедленно используйте это значение для обновления ширины абзаца. На следующей итерации цикла браузер должен учитывать тот факт, что стиль обновляется (box.offsetWidth
был запрошен в предыдущей итерации), поэтому он должен применить изменения стиля, а затем выполнить макет. Это приводит к принудительному синхронному макету на каждой итерации, правильный способ сделать это — сначала прочитать значение, а затем записать значение.
// Read.
var width = box.offsetWidth;
function resizeAllParagraphsToMatchBlockWidth() {
for (var i = 0; i < paragraphs.length; i++) {
// Now write.
paragraphs[i].style.width = width + 'px';
}
}
Чтобы легко решить эту проблему, вы можете использоватьFastDOMВыполняйте пакетное чтение и запись, это предотвращает принудительную синхронизацию макета и переборку макета.
Анимация с использованием свойств, которые не запускают компоновку и рисование
В разделе пиксельного конвейера мы обнаружили, что есть модификация свойства, которая пропускает этапы компоновки и рисования, что, очевидно, значительно снижает нагрузку на производительность. В настоящее время этому условию соответствуют только два свойства:transform
а такжеopacity
.
Следует отметить, что использованиеtransform
а такжеopacity
, элемент, на котором изменяются эти свойства, должен находиться в своем собственном слое,Поэтому нам нужно создать новый слой для анимированного элемента (преимущество этого в том, что перерисовка на этом слое может быть обработана, не затрагивая элементы на других слоях. Если вы использовалиPhotoshop
, вы должны понимать удобство работы с несколькими слоями).
Лучший способ создать новый слой — использоватьwill-change
Атрибуты,Этот атрибут сообщает браузеру, какие изменения будут внесены в элемент, чтобы браузер мог заранее подготовиться к соответствующей оптимизации до фактического изменения атрибута элемента.
.moving-element {
will-change: transform;
}
// 对于不支持 will-change 但受益于层创建的浏览器,需要使用(滥用)3D 变形来强制创建一个新层
.moving-element {
transform: translateZ(0);
}
но не думайwill-change
Может улучшить производительность, просто злоупотребляйте им, используйтеwill-change
Выполнение предварительной оптимизации и создание слоев требует дополнительной памяти и затрат на управление, а неправильное их использование только окупится.