предисловие
Всем привет, меня зовут Zero One. Недавно я собирался поговорить о коде в группе, поэтому перед проектом разобрался с бизнес-кодом. В процессе разбора я увиделкомпонент индикатора выполненияОн очень хорошо написан, что напоминает мне код прогресса, который я написал, когда впервые начал изучать интерфейс.По сравнению с этим он действительно слишком большой (большинство новичков не должны думать об этом, и моя первая стажировка компания принесла так мой наставник).
Так что я хочу поделиться этим со всеми вамиотличная идеяизкомпонент индикатора выполнения, он тоже существуеточень серьезныйпроблемы с производительностью, которые будут объяснены в конце этой статьипроблема заключается втак же какМетод оптимизации
Сценарии применения индикатора выполнения
Как правило, компоненты Progress Bar появляются в сценах, таких как Douyin играют в видео, как показано стрелкой в нижней части рисунка:
Индикатор выполнения растет с увеличением длины видео, видео приостанавливается, а анимация индикатора выполнения также приостанавливается.
Далее давайте посмотрим, как это пишет большинство людей, и почему они говорят, что идея и исполнение не годятся. Взяв здесь в качестве примера React, разработчикам Vue не нужно бояться непонимания, главное думать
Основные функции реализации:
- Поддержка воспроизведения, паузы, повтора
- После окончания воспроизведения количество воспроизведений равно +1, и воспроизведение начинается заново.
Не рекомендуется написание
составная часть
// index.jsx
import { useState } from 'react'
import './index.css'
let timer = null // 递增进度的定时器
let totalTime = 3000 // 假设视频播放为3s
function App() {
const [progress, setProgress] = useState(0) // 进度
const [isPlay, setIsPlay] = useState(false) // 是否播放
// setProgress的递增逻辑
const handlerProgress = pre => {
if(pre < 100) return pre + 1;
else {
alert('播放结束')
return 0 // 播放结束,重新开始播放
}
}
// 开始播放 && 暂停播放
const handleVideo = () => {
setIsPlay(!isPlay)
isPlay
? clearInterval(timer)
: timer = setInterval(() => setProgress(handlerProgress), totalTime / 100)
}
// 重播
const replay = () => {
setIsPlay(true)
if(timer) clearInterval(timer);
setProgress(0)
timer = setInterval(() => setProgress(handlerProgress), totalTime / 100)
}
return (
<div id="root">
<button onClick={handleVideo}>{ isPlay ? '暂停' : '播放' }</button>
<button onClick={replay}>重播</button>
<div className="container">
<div className="progress" style={{ width: `${progress}%` }}/>
</div>
</div>
)
}
раздел стиля
.container {
height: 10px;
border-radius: 5px;
border: 1px solid black;
}
.progress {
height: 100%;
width: 0;
background-color: red;
}
Давайте кратко продемонстрируем, как выглядит этот индикатор выполнения.
Почему вы говорите, что это не лучший способ написать это? Поскольку мы используем таймер для быстрого увеличения переменнойprogress
Таким образом, прогресс увеличивается, и каждое изменение переменной заставит представление пересчитывать рендеринг, что должно быть очень низкой производительностью (честно говоря, когда я испытывал эту демонстрацию, было небольшое зависание, видимое для просмотра). невооруженным глазом)
В добавок к этому? На самом деле, есть еще одна причина для причинения коробки, вы, возможно, пожелаете догадаться, давайте поместим его в последний разговор, я хочу знать маленького партнера, что ответ может скользнуть непосредственно к следующему
Рекомендуемое написание
То, что я рекомендую здесь, — это лучшее решение, которое я увидел, когда читал код, и я поделюсь им с вами в следующий раз.
составная часть
// index.jsx
import { useState } from 'react'
import './index.css'
let totalTime = 3000 // 假设视频播放为3s
function App() {
const [isPlay, setIsPlay] = useState(false) // 是否播放
const [count, setCount] = useState(0) // 播放次数
const [type, setType] = useState(0) // 使用哪个动画。0: @keyframes play; 1: @keyframes replay;
// 暂停 && 播放
const handleVideo = () => setIsPlay(!isPlay);
// 重播
const replay = () => {
setIsPlay(true)
setType(type ? 0 : 1)
}
// 动画结束时触发的事件
const end = () => {
setCount(count + 1) // 播放次数 +1
replay() // 重新开始播放
}
return (
<div id="root">
<button onClick={handleVideo}>{ isPlay ? '暂停' : '播放' }</button>
<button onClick={replay}>重播</button>
<span>{ `播放次数为:${count}` }</span>
<div className="container">
<div
className={`progress ${isPlay ? 'play' : 'pause'}`}
style={{
animationDuration: `${totalTime}ms`,
animationName: `${type ? 'replay' : 'play'}`
}}
onAnimationEnd={end} // 动画结束时的事件
/>
</div>
</div>
)
}
раздел стиля
@keyframes play {
to {
width: 100%;
}
}
@keyframes replay {
to {
width: 100%;
}
}
.container {
height: 10px;
border-radius: 5px;
border: 1px solid black;
}
.progress {
height: 100%;
width: 0;
background-color: red;
animation-timing-function: linear;
}
.progress.play { /* 使animation动画启动 */
animation-play-state: running;
}
.progress.pause { /* 使animation动画暂停 */
animation-play-state: paused;
}
Мы установили два@keyframes
Анимация заключается в том, чтобы сделать переключение при воспроизведении индикатора выполнения, то есть, когда вы нажимаете «повторить», вы можете напрямую переключаться на другую анимацию, а индикатор выполнения может увеличиваться с 0.
В то же время мы также устанавливаем стили двух имен классов, которые используются для управления воспроизведением и паузой анимации соответственно.
Когда воспроизведение завершено, через событие можно передать функцию числа воспроизведения +1.animationend
контролировать
Так же посмотрим на рендеры этой схемы (функция точно такая же, как и у предыдущей схемы)
Сравнивая предыдущий набор решений, вы можете видеть, что этот метод написания не требует постоянного изменения данных для управления изменением представления, что сокращает количество вычислений в фреймворке и значительно повышает производительность.
дефект
Хотя производительность второго решения очень хорошая, как и у первого решения, есть еще одна скрытая проблема производительности, которая также обнаруживается, когда я исследую проблему производительности кода моего бывшего коллеги.
Недостаток: оба решения будут вызывать частые перекомпоновки и перерисовки.
можно использоватьchrome devtools performance
проверить статус страницы
Небольшой индикатор выполнения вызывает так много перекомпоновок и перерисовок, так какой же эффект он имеет? Давайте кратко рассмотрим влияние перекомпоновки и перерисовки.
переставлять: Браузер должен пересчитать геометрию элемента, и это изменение также может повлиять на геометрию или положение других элементов.
перерисовать: Не все изменения DOM влияют на геометрические свойства элемента.Если изменение цвета фона элемента не влияет на его ширину и высоту, то в этом случае произойдет только перерисовка, а перекомпоновки не произойдет из-за компоновки элемента элемент не изменился
Поэтому, узнав о серьезных проблемах, вызванных перестановкой и перерисовкой, мы сразу же анализируем и оптимизируем ее.
Максимальная оптимизация
Сначала рассмотрим очень распространенную схему.
Рендеринг страницы обычно следует этим пяти процессам. Конечно, есть также способы пропустить определенные промежуточные шаги, например, избежатьLayout
а такжеPaint
Давайте рассмотрим, какие методы приведут перегруппирование и перекраску.
Перемещение триггера: добавление или удаление видимых элементов DOM, изменение положения элемента, изменение размера элемента (в том числе: поля, отступы, граница, высота и т. д.), изменение содержимого (например: изменение текста или изображение заменяется другим изображением другого размера), изменение размера окна браузера, черезdisplay: none
скрыть узел DOM и т. д.
Факторы, вызывающие перекраску: Перестановка должна вызвать перерисовку (важно), пройтиvisibility: hidden
Скрыть узел DOM, изменить цвет фона элемента, изменить цвет шрифта и т. д.
Итак, где в коде, который мы написали ранее, триггеры переупорядочиваются и перерисовываются? При простом осмотре нетрудно обнаружить, что в обеих схемах постоянно меняются элементыwidth
Одно изменение ширины элемента неизбежно вызовет перестановку и перерисовку, не говоря уже об ультрачастом изменении!
Решение: Включите ускорение GPU, избегайте процесса перестановки и перерисовки и продвигайте индикатор выполнения в один слой, то есть он не влияет на другие элементы.
Давайте оптимизируем только второе решение ~ Нам нужно только изменить его содержимое css (отметив его как изменение)
@keyframes play { /* 通过transform来启用GPU加速,跳过重排重绘阶段 */
0% {
transform: translateX(-50%) scaleX(0); /* 用 scaleX 来代替 width */
}
to {
transform: translateX(0) scaleX(1);
}
}
@keyframes replay {
0% {
transform: translateX(-50%) scaleX(0);
}
to {
transform: translateX(0) scaleX(1);
}
}
.container {
height: 10px;
border-radius: 5px;
border: 1px solid black;
}
.progress {
height: 100%;
width: 100%; /* 初始宽度为100%,因为我们要对其缩放 */
background-color: red;
will-change: transform; /* 通过will-change告知浏览器提前做好优化准备 */
animation-timing-function: linear;
}
.progress.play {
animation-play-state: running;
}
.progress.pause {
animation-play-state: paused;
}
Вот краткое объяснениеtranslateX
а такжеscaleX
настройки стоимости. установить индикатор выполненияwidth: 100%
, мы проходимscaleX(0.5)
Увеличив его наполовину, вы обнаружите, что индикатор выполнения составляет половину длины контейнера и центрирован. В это время нам нужно пройтиtranslateX(-25%)
переместите его влево в крайнее лево, почему это-25%
Шерстяная ткань? Поскольку индикатор выполнения занимает половину контейнера и расположен по центру, это указывает на то, что левый и правый пробелы точно(100% - 50%) / 2 = 25%
, так что нетрудно узнать, когда начальное состояниеscaleX(0)
час,translateX
ценность-(100% - 0%) / 2 = -50%
После этого мы снова используемperformance
Проверь это
Хорошо видно, что количество перестановок и перерисовок страниц значительно сократилось, а остальные в основном самые элементарные перестановки и перерисовки страниц.
Некоторые люди хотят сказать, что я титульный участник, и тогда я покажу вам, насколько оптимизирована производительность.
Давайте запустим его с тем, который только что был оптимизирован.performance
Глядя на правую часть изображения, FPS в основном стабилен на55 ~ 70
между
Давайте посмотрим на первое решение в начале статьи.performance
беговые очки
Глядя на правую часть изображения, FPS в основном стабилен на32 ~ 50
между
Хорошо видно, что колебания FPS до оптимизации очень серьезные, то есть он недостаточно стабилен, поэтому подвержен проблеме зависаний, при этом изменение FPS после оптимизации невелико, а общее изменение тренд относительно плоский, почти прямой.
На такой минималистичной странице наша оптимизированная производительность улучшилась примерно на40% ~ 54%
Тогда, если в обычном проекте, учитывая сложность страницы, наше оптимизированное решение не только избегает повторного расчета и рендеринга страницы, но и избегает перерисовки и перекомпоновки, то можно предположить, что в этом случае прирост производительности должен быть далек больше, чем40% ~ 54%
Да, эммммммм, так что не будет преувеличением сказать, что производительность улучшилась на 70% ххххх
пасхальные яйца
Включение ускорения графического процессора переместит элементы в отдельный слой, что мы можем сделать с помощьюchrome devtools layers
смотреть
Вот слои страницы до и после оптимизации.
«До оптимизации»
Очевидно, что на всей странице есть толькоdocument
слой, то есть индикатор выполнения не многоуровневый
"Оптимизированный"
Также очевидно, что индикатор выполнения был выделен в отдельный слой.
конец
Прошлые рекомендации:
- Рука об руку с вами в течение 10 минут с простым редактором Markdown(159+ 👍🏻)
- Прямая трансляция Lianmai прошлой ночью, я многому научился! ! !(517+ 👍🏻)
- Статья поможет вам понять, как устранить феномен страницы Caton, вызванный утечкой памяти.(1029+ 👍🏻)
Я ноль один, если моя статья была вам полезна, пожалуйста, нажмитеотличный👍🏻 поддержите меня
Я создал публичный аккаунт: Front-end Impression, который каждый день выкладывает качественные статьи. Кроме того, вы также можете присоединиться к моей фронтенд-группе, поболтать и похвастаться технологиями, vx:Lpyexplore333