Безжалостное избавление от Layout Shift на netlify.com
Приветствую. Представляю вашему вниманию перевод статьи «Ruthlessly Eliminating Layout Shift On Netlify.com», опубликованной 25 ноября 2020 года автором Zach Leatherman.
На сайте Netlify у нас есть небольшой баннер, который появляется вверху для привлечения трафика к новым и интересным вещам, происходящим в мире Netlify.
Этот баннер состоит из двух частей:
Расширенная фича HTML, о которой знают только разработчики старой школы: гиперссылка.
Кнопка закрытия (которая сохраняет предпочтение для будущих загрузок страницы)
В жизненном цикле этого компонента есть несколько ключевых этапов производительности, и вот как это работало раньше:
Первоначальный рендеринг страницы. По умолчанию баннер ⚠️⚠️⚠️ скрыт. До загрузки JavaScript или без JavaScript баннер не отображается.
После загрузки JavaScript мы проверяем
localStorage
, чтобы узнать, закрывал ли пользователь баннер ранее. Значение - URL-адрес ссылки, чтобы при изменении баннера он снова отображался, даже если пользователь его закрыл. Если нужно - рендерим баннер.Наконец, мы вешаем события на кнопку закрытия. Для гиперссылки это не нужно, потому что ее поведение предоставляется исключительно в HTML (вааау).
Шаги 2 и 3 объединены и выполняются вместе в одном компоненте. В некоторых предедущих версиях сайта время между шагами 1 и 2 могло достигать ~600 мс.
При редизайне нашего сайта (заметьте, быстрее) мы вставили JavaScript шагов 2 и 3 в конец <body>
, и задержка все еще была:
Решение
Что нам нужно сделать? Сделать поведение наоборот. Обычно баннер по умолчанию скрыт. Мы должны сделать баннер видимым по умолчанию и с помощью JavaScript скрывать его, если нужно.
Это изменяет наш выше упомянутый Шаг 1, первоначальный рендеринг страницы. Теперь без JavaScript или до загрузки JavaScript баннер будет виден.
Мы также разделили код JavaScript-компонента на две отдельные части: в одной проверяем, есть ли у пользователя флаг для скрытия, и отдельный веб-компонент для привязки событий.
Update: Я собрал этот код и выложил на GitHub для повторного использования.
HTML и CSS
Мы используем opacity
для скрытия кнопки закрытия, чтобы изза неё не перерисовывался компонент, когда он отобразится с помощью JavaScript.
.banner--hide announcement-banner,
announcement-banner[hidden] {
display: none;
}
[data-banner-close] {
opacity: 0;
pointer-events: none;
}
.banner--show-close [data-banner-close] {
opacity: 1;
pointer-events: auto;
}
<announcement-banner>
<a href="https://www.netlify.com/sustainability/">Read about our Sustainability</a>
<button type="button" data-banner-close>Close</button>
</announcement-banner>
JavaScript
banner-helper.js
, вставленный в <head>
страницы:
// Адрес CTA ссылки, мы инжектим его из JSON файла
let ctaUrl = "https://www.netlify.com/sustainability/";
let savedCtaUrl = localStorage.getItem("banner--cta-url");
if(savedCtaUrl === ctaUrl) {
document.documentElement.classList.add("banner--hide");
}
banner.js
, отоложен на потом (на сколько позже - зависит от вас):
class Banner extends HTMLElement {
connectedCallback() {
// Независимо от того, когда это выполняется, кнопка закрытия будет скрыта,
// пока этот класс не будет добавлен - он предотвращает призрачные клики
// по кнопке до добавления обработчика событий.
this.classList.add("banner--show-close");
let button = this.getButton();
if(button) {
button.addEventListener("click", () => {
this.savePreference();
this.close();
});
}
}
getButton() {
return this.querySelector("[data-banner-close]");
}
savePreference() {
let cta = this.querySelector("a[href]");
if(cta) {
let ctaUrl = cta.getAttribute("href");
localStorage.setItem("banner--cta-url", ctaUrl);
}
}
close() {
this.setAttribute("hidden", true);
}
}
window.customElements.define("announcement-banner", Banner);
Внимательные читатели заметят, что это веб-компонент, но давайте оставим это между нами.
Результат
Обратите внимание, что первый рендер содержит баннер! Это одинаковый рендеринг вне зависимости от того, задействован ли JavaScript или нет.
На этой диаграмме видно, что мы снизили показатели Layout Shift до нуля.
И поскольку мы вставили скрипт для повторных просмотров в <head>
, когда пользователь скрывает баннер и переходит на новую страницу, баннер также будет скрыт перед первым рендерингом.
Довольно неплохо для таких маленьких изменений!
Следующей целью будет улучшение отображения веб-шрифтов.