Размытие — отличный способ перенаправить фокус пользователя. Размытие одних визуальных элементов при сохранении фокуса на других естественным образом направляет фокус пользователя. Пользователи игнорируют размытость и вместо этого сосредотачиваются на том, что могут прочитать. Примером может служить список значков, отображающих подробную информацию об отдельных элементах при наведении курсора. В это время оставшиеся варианты выбора могут быть размыты, чтобы перенаправить пользователя к новой отображаемой информации.
TL;DR
Анимировать размытие — не лучший вариант, поскольку это очень медленно. Вместо этого предварительно вычислите серию вариантов с увеличивающимся размытием и плавно переходите между ними. Мой коллега И Гу написал библиотеку , которая всё сделает за вас! Взгляните на нашу демоверсию .
Однако этот приём может быть довольно резким, если его применять без переходного периода. Анимация размытия — переход от неразмытого к размытому — кажется разумным решением, но если вы когда-либо пробовали сделать это в интернете, то, вероятно, обнаружили, что анимация получается далеко не плавной, как показывает эта демонстрация , если у вас не мощный компьютер. Можно ли сделать лучше?
Проблема

На данный момент мы не можем добиться эффективной анимации размытия. Однако мы можем найти обходной путь, который выглядит достаточно хорошо , но технически не является анимированным размытием. Для начала давайте сначала разберемся, почему анимированное размытие медленное. Для размытия элементов в вебе существует два метода: свойство CSS filter
и фильтры SVG. Благодаря расширенной поддержке и простоте использования, обычно используются фильтры CSS. К сожалению, если вам требуется поддержка Internet Explorer, у вас нет другого выбора, кроме как использовать фильтры SVG, поскольку IE 10 и 11 поддерживают их, но не фильтры CSS. Хорошая новость заключается в том, что наш обходной путь для анимации размытия работает с обоими методами. Итак, давайте попробуем найти узкое место, обратившись к DevTools.
Если включить функцию «Paint Flashing» в DevTools, вы вообще не увидите никаких вспышек. Похоже, что перерисовки не происходит. И это технически верно, поскольку «перерисовка» означает, что центральный процессор перерисовывает текстуру продвигаемого элемента. Всякий раз, когда элемент одновременно продвигается и размывается, размытие применяется графическим процессором с помощью шейдера.
Как SVG-фильтры, так и CSS-фильтры используют свёрточные фильтры для размытия. Свёрточные фильтры довольно затратны, поскольку для каждого выходного пикселя приходится учитывать несколько входных пикселей. Чем больше изображение или чем больше радиус размытия, тем дороже эффект.
Вот в чем проблема: мы запускаем довольно затратную операцию графического процессора в каждом кадре, расходуя наш кадровый бюджет в 16 мс и, следовательно, получаем скорость значительно ниже 60 кадров в секунду.
Вниз по кроличьей норе
Итак, что же нам сделать, чтобы всё работало гладко? Можно прибегнуть к ловкости рук! Вместо анимации фактического значения размытия (радиуса размытия) мы заранее вычисляем пару размытых копий, где значение размытия увеличивается экспоненциально, а затем делаем плавный переход между ними с помощью opacity
.
Кроссфейд представляет собой серию перекрывающихся плавных усилений и ослаблений непрозрачности. Например, если у нас четыре этапа размытия, мы постепенно уменьшаем первый этап и одновременно усиливаем второй. Как только второй этап достигает 100% непрозрачности, а первый — 0%, мы постепенно уменьшаем второй этап и одновременно уменьшаем третий. После этого мы, наконец, уменьшаем третий этап и увеличиваем четвёртый, финальный, вариант. В этом случае каждый этап займёт ¼ от общей желаемой длительности. Визуально это очень похоже на настоящее анимированное размытие.
В наших экспериментах экспоненциальное увеличение радиуса размытия на каждом этапе дало наилучшие визуальные результаты. Пример: если у нас есть четыре этапа размытия, мы бы применили filter: blur(2^n)
к каждому этапу, то есть этап 0: 1px, этап 1: 2px, этап 2: 4px и этап 3: 8px. Если мы принудительно поместим каждую из этих размытых копий на отдельный слой (называемый «продвижением») с помощью will-change: transform
, изменение непрозрачности этих элементов должно быть супер-пупер быстрым. Теоретически это позволило бы нам загрузить дорогостоящую работу по размытию на передний план. Оказывается, логика ошибочна. Если вы запустите эту демо-версию , вы увидите, что частота кадров все еще ниже 60 кадров в секунду, а размытие на самом деле хуже, чем было раньше.

Беглый взгляд в DevTools показывает, что графический процессор по-прежнему чрезвычайно занят и растягивает каждый кадр примерно до 90 мс. Но почему? Мы больше не меняем значение размытия, только непрозрачность, так что же происходит? Проблема снова кроется в природе эффекта размытия: как объяснялось ранее, если элемент и продвигается, и размывается, эффект применяется графическим процессором. Таким образом, даже если мы больше не анимируем значение размытия, сама текстура по-прежнему не размыта и должна повторно размываться в каждом кадре графическим процессором. Причина, по которой частота кадров стала еще хуже, заключается в том, что по сравнению с наивной реализацией у графического процессора на самом деле больше работы, чем раньше, поскольку большую часть времени видны две текстуры, которые необходимо размыть независимо.
То, что мы получили, не очень красиво, но зато анимация невероятно быстрая. Мы возвращаемся к принципу не продвигать элемент, который должен быть размыт, а продвигать родительскую оболочку. Если элемент одновременно размыт и продвигается, эффект применяется графическим процессором. Именно это и замедляло нашу демоверсию. Если элемент размыт, но не продвигается, размытие растеризуется в ближайшую родительскую текстуру. В нашем случае это продвигаемый родительский элемент-оболочка. Размытое изображение теперь является текстурой родительского элемента и может быть повторно использовано во всех будущих кадрах. Это работает только потому, что мы знаем, что размытые элементы не анимированы, и их кэширование на самом деле полезно. Вот демоверсия , реализующая этот метод. Интересно, что думает Moto G4 об этом подходе? Спойлер: он считает его отличным:

Теперь у нас есть огромный запас мощности графического процессора и плавные 60 кадров в секунду. Мы это сделали!
Производство
В нашей демоверсии мы дублировали структуру DOM несколько раз, чтобы получить копии контента для размытия с разной интенсивностью. Вам, возможно, интересно, как это будет работать в рабочей среде, ведь это может привести к непреднамеренным побочным эффектам, связанным с CSS-стилями автора или даже с его JavaScript. Вы правы. Встречайте Shadow DOM!
Хотя большинство людей думают о Shadow DOM как о способе присоединения «внутренних» элементов к своим пользовательским элементам , это также примитив изоляции и производительности! JavaScript и CSS не могут преодолеть границы Shadow DOM, что позволяет нам дублировать контент, не вмешиваясь в стили разработчика или логику приложения. У нас уже есть элемент <div>
для каждой копии, на которую нужно растеризовать, и теперь мы используем эти <div>
в качестве теневых хостов. Мы создаём ShadowRoot
с помощью attachShadow({mode: 'closed'})
и прикрепляем копию контента к ShadowRoot
вместо самого <div>
. Нам также необходимо скопировать все таблицы стилей в ShadowRoot
, чтобы гарантировать, что наши копии будут оформлены так же, как и оригинал.
Некоторые браузеры не поддерживают Shadow DOM v1, и в таких случаях нам приходится просто дублировать контент и надеяться на лучшее, что ничего не сломается. Мы могли бы использовать полифилл Shadow DOM с ShadyCSS , но мы не реализовали это в нашей библиотеке.
Вот и всё. Разобравшись с процессом рендеринга Chrome, мы выяснили, как эффективно анимировать размытие в разных браузерах!
Заключение
К такому эффекту нельзя относиться легкомысленно. Поскольку мы копируем элементы DOM и помещаем их в отдельный слой, мы можем расширить возможности устройств начального уровня. Копирование всех таблиц стилей в каждый ShadowRoot
также потенциально снижает производительность, поэтому вам следует решить, хотите ли вы настроить логику и стили так, чтобы они не влияли на них при копировании в LightDOM
, или использовать нашу технику ShadowDOM
. Но иногда наша техника может быть оправданной инвестицией. Взгляните на код в нашем репозитории на GitHub , а также на демо-версию . Если у вас есть вопросы, напишите мне в Твиттере !