Как заставить вашу веб-страницу «выглядеть» быстрее — скелетный экран, две или три вещи

PWA

Чтобы веб-страница отображалась быстрее, официальное утверждение называется рисованием первого экрана, сокращенно First Paint или FP, а простое утверждение называется временем белого экрана, которое начинается от ввода URL-адреса до фактического просмотра содержимого (у него нет быть интерактивным, что называется TTI, Time to Interactive) прошедшее время. Конечно, чем короче время, тем лучше.

Но здесь следует отметить, что помимо FP есть еще два индикатора, относящиеся к первому экрану, которые называются FCP (First Contentful Paint, отрисовка эффективного содержимого страницы) и FMP (First Meaningful Paint, отрисовка значимого содержания страницы). Хотя эти понятия могут сбить нас с толку, нам нужно понять только одну вещь:FP не требует, чтобы контент был реальным, действительным, содержательным и интерактивным.. Другими словами,ПовседневнаяПокажите пользователю что-нибудь.

FP/FCP/FMP/TTI

В этом загадка названия этой статьи: «Внешность». Да, это просто выглядит быстрее, на самом деле все так и есть. Поэтому в этой статье не обсуждается оптимизация производительности, а обсуждается оппортунистический трюк, но он действительно может улучшить опыт. Например, оптимизация производительности предназначена для развития внутренней силы и улучшения ваших собственных функций, и в следующей части этой статьи мы обсудим некоторые ходы, которые могут позволить вам в первую очередь блефовать против ваших оппонентов.

Об этом так называемом движении я и расскажу дальше, научное название — Skeleton Screen, также называемое Skeleton. Возможно, вы не слышали об этом имени, но не видеть его невозможно.

Как выглядит каркасный экран

Это должна быть наиболее распространенная форма, использующая различные формы серых прямоугольников для имитации изображений и текста. Некоторые приложения также используют круги, но ключевой момент заключается в том, что они похожи на реальную структуру контента и не могут сильно отличаться.

Если вы преследуете эффект, вы также можете добавить анимацию (например, рябь) на поверхность цветового блока, чтобы отобразить динамический эффект, который является данью Loading.

Это может быть удобно на сайтах с большим количеством изображений, поскольку изображения обычно загружаются медленно. Изображение-заполнитель в приведенной выше демонстрации использует изображение с низким разрешением, то есть общее соответствие цветов и изменения соответствуют фактическому содержимому.

Если такое низкопиксельное изображение не может быть сгенерировано, слегка ухудшенным решением является получение основного цвета изображения с помощью алгоритма и использование блока сплошного цвета, чтобы занять это место.

Возвращаясь на один уровень назад, вы также можете использовать одно и то же изображение станции для всей станции или непосредственно цветовой блок того же цвета. Хотя эффект определенно не так хорош, как у двух предыдущих, это хуже, чем ничего.

Скелетный экран полностью настраивается, и то, что вы хотите сделать, зависит от вашего воображения. Вы можете делать круглые, треугольные или трехмерные фигуры, но «заполнитель» определяет его характеристики: он не может быть слишком сложным и должен отображаться как можно быстрее.

В чем преимущества каркасного экрана

В целом преимуществами каркасного экрана являются:

  1. Предварительно визуализируйте контент в начале загрузки страницы, чтобы улучшить сенсорный опыт.

  2. В целом структура скелетного экрана и собственно контента схожа, поэтому последующее переключение не будет слишком резким. Это отличается от традиционной анимации загрузки и может рассматриваться как обновленная версия.

  3. Требуется только простая поддержка CSS (JS также может потребоваться для ленивой загрузки изображений), протокол HTTPS не требуется, а дополнительные затраты на обучение и обслуживание не требуются.

  4. Если страница разрабатывается компонентами, каждый компонент может определить свой собственный каркасный экран и время переключения в соответствии со своим собственным состоянием, сохраняя при этом независимость между компонентами.

Где можно использовать каркасный экран?

Существует примерно два режима рендеринга для современных веб-сайтов:

внешний рендеринг

Из-за последовательного внедрения и популярности Angular/React/Vue в последние годы начал доминировать внешний рендеринг. Приложение этого режима также называют одностраничным приложением (SPA, Single Page Application).

Режим внешнего рендеринга заключается в том, что сервер (в основном статический сервер) возвращает фиксированный HTML. Обычно этот HTML содержит пустой узел-контейнер без другого содержимого. После этого JS, содержащийся внутри, включает управление маршрутизацией, рендеринг страниц, переключение страниц, события привязки и другую логику, поэтому он называется рендерингом внешнего интерфейса.

Поскольку во внешнем интерфейсе нужно управлять многими вещами, JS обычно большой и сложный, и его выполнение занимает больше времени.Скелетные экраны — хорошая резервная копия, пока JS не отрендерит фактический контент.

бэкэнд-рендеринг

До того, как эта волна внешнего рендеринга стала популярной, режим, используемый ранними традиционными веб-сайтами, назывался внутренним рендерингом, то есть сервер напрямую возвращал HTML-страницу веб-сайта, которая уже содержала все (или большинство) DOM-элементы. домашняя страница. Большая часть включенного в него JS используется для привязки событий, определения поведения после взаимодействия с пользователем и так далее. Небольшое количество добавит/изменит дополнительный DOM, но это не повредит общей картине.

Кроме того, внешний режим рендеринга не оптимизирован для SEO, потому что возвращаемый им HTML-код представляет собой пустой контейнер. Если у поисковой системы нет возможности выполнять JS (называется Deep Render), то она не знает, о чем ваш сайт, и, естественно, не сможет ранжировать ваш сайт в результатах поиска. Это неприемлемо для большинства сайтов, поэтому интерфейсный фреймворк последовательно запускал режим рендеринга на стороне сервера (SSR, Server Side Rendering). Этот шаблон очень похож на традиционные веб-сайты, поскольку возвращаемый HTML также содержит всю модель DOM, а не внешний рендеринг. В дополнение к событиям привязки внешний JS будет делать еще одну вещь, называемую «гидратацией», которая здесь не будет повторяться.

Будь то традиционный режим или SSR, если это внутренний рендеринг, каркасный экран не требуется.Поскольку содержимое страницы существует непосредственно в формате HTML, экран-скелет не может появиться на экране.

Как использовать экран скелета

Обсудив фоновую волну, давайте посмотрим, как ее использовать. Во-первых, игнорируйте конкретные детали реализации и сначала смотрите на идеи.

Реализовать идеи

Его условно делят на несколько этапов:

  1. Вставьте HTML-код скелетного экрана в узел-контейнер, который должен быть пустым.

    Чтобы отобразить скелетный экран как можно скорее, требуется, чтобы он был быстрым и простым, поэтому скелетный экран в основном использует статические изображения. Кроме того, компиляция изображений в формат кодировки base64 может сократить сетевые запросы, делая отображение экрана скелета более быстрым и эффективным.

    <html>
        <head>
            <style>
                .skeleton-wrapper {
                    // styles
                }
            </style>
            <!-- 声明 meta 或者引入其他 CSS -->
        </head>
        <body>
            <div id="app">
                <div class="skeleton-wrapper">
                    <img src="data:image/svg+xml;base64,XXXXXX">
                </div>
            </div>
            <!-- 引用 JS -->
        </body>
    </html>
    
  2. Перед выполнением JS, чтобы начать рендеринг реального контента, очистите HTML-код скелетного экрана.

    Возьмите Vue в качестве примера, то есть вmountВы можете очистить содержимое раньше.

    let app = new Vue({...})
    let container = document.querySelector('#app')
    if (container) {
        container.innerHTML = ''
    }
    app.$mount(container)
    

Только эти два шага не предполагают сколь сложных механизмов и высококлассных API, поэтому их очень легко применить, поторопитесь и пользуйтесь!

Пример

Я написал пример, чтобы быстро показать эффект каркасного экрана,код здесь.

  • index.html

    Каркас экрана включен по умолчанию, а стили встроены (с<style>теги добавляются в шапку).

  • render.js

    Он отвечает за создание элементов DOM и добавление их в<body>, который отображает фактическое содержимое страницы для имитации распространенных интерфейсных режимов рендеринга.

  • index.css

    Таблица стилей фактического содержимого страницы, за исключением стиля каркасного экрана.

Три файла кода выполняют свои обязанности, и с приведенными выше идеями реализации его следует хорошо понимать. допустимыйздесьПроверьте эффект.

Поскольку логика этого примера слишком проста, а реальная структура рендеринга переднего плана намного сложнее, включены функции не только рендеринга, но и управления состоянием, управления маршрутизацией, виртуального DOM и т. д., поэтому размер файла и время выполнения больше.Когда мы смотрели на примеры, настройка сети на «Быстрый 3G» или «Медленный 3G» была немного более реалистичной.

Но что невероятно, так это то, что после нескольких попыток обновить этот адрес я почти не вижу экран-скелет (содержимое экрана-скелета представляет собой центрированное синее квадратное изображение плюс анимация выделения белой горизонтальной линии, неоднократно скользящей вбок). Есть ли проблема с нашими идеями реализации?

Тайна браузера: уменьшение оплавления

Чтобы исключить упущение и вмешательство невооруженного глаза, мы используем инструмент производительности Chrome Dev Tools для записи того, что произошло только что. Скриншот выглядит следующим образом: (Настройка сети на момент скриншота - «Быстрый 3G» )

normal timeline

Мы можем ясно видеть 3 момента времени:

  1. Загрузка HTML завершена. При разборе HTML браузер обнаружил 2 внешних ресурса, на которые нужно было сослаться.index.jsа такжеindex.css, поэтому отправьте сетевой запрос, чтобы получить его.

  2. После успешного приобретения выполните JS и пропишите правила CSS.

  3. После выполнения JS фактический контент отображается естественным образом, и применяются правила стиля (полосы случайных цветов).

А как насчет нашего каркасного экрана? Как и ожидалось, экран-скелет должен появиться между 1 и 2, то есть при получении JS и CSS должен отрисовываться экран-скелет. Это также время, когда мы внедрили HTML-код каркасного экрана вindex.html, а также поставить CSS изindex.cssБлагие намерения отделились от него, но браузер на это не покупается.

На самом деле это связано с порядком рендеринга браузера.

Я уверен, что все упаковали свои чемоданы. Когда мы организуем багаж, мы организуем багаж разумно в соответствии с размером каждого багажа.Большие и маленькие будут сопоставлены, и будет заполнен один слой, а затем будет размещен верхний слой. И вот вдруг кто-то пришел к вам и сказал, что вам не нужно приносить свой компьютер, вам нужно принести еще две одежды, и вы не можете принести столько бутылок минеральной воды. Помимо желания побить его, чтобы переставить чемодан, необходимо вынуть упакованный багаж и снова положить его обратно. В браузерах этот процесс называется перекомпоновкой, и эта плохая идея — вновь загруженный CSS. Очевидно, что накладные расходы на перестановку очень велики.

Практика ведет к совершенству, чем лучше организована коробка, тем больше решений вы можете придумать. Поскольку каждая загрузка файла CSS может вызвать перерисовку, могу ли я дождаться загрузки и рендеринга всего CSS вместе? Исходя из этого, браузер будет ждать, пока все CSS в HTML будут загружены, зарегистрированы, и применит стили вместе, стремясь завершить работу в одном расположении, а не повторно. Создается впечатление, что дизайнеры браузеров много путешествуют, потому что это очень правильная идея оптимизации, но с применением каркасного экрана есть проблема.

Чтобы отобразить экран скелета как можно скорее, мы изменили стиль экрана скелета сindex.cssотдельно. Но браузер не знает, он думает, что HTML скелетного экрана все еще зависит отindex.css, поэтому вам нужно дождаться его загрузки. И после того, как он загрузится,render.jsОн почти загрузился и начал выполняться, поэтому HTML-код скелетного экрана снова заменен, и естественно его не видно. Более того, при ожидании загрузки JS и CSS это по-прежнему белый экран, и эффект каркасного экрана значительно снижается.

Итак, что нам нужно сделать, это сообщить браузеру, что вы можете сначала нарисовать скелет экрана, и это то же самое, что и следующее:index.cssне имеет значения. Так как же это сказать?

Скажите браузеру, чтобы сначала отображался экран скелета

Когда мы говорим о CSS, мы используем<link rel="stylesheet" href="xxxx>такой синтаксис. Но на самом деле браузер также предоставляет некоторые другие механизмы для обеспечения работоспособности (последующей) страницы, мы называем это предварительной загрузкой, китайцы называют это предварительной загрузкой. В частности, используя<link rel="preload" href="xxxx">, заранее объявите ресурсы, которые будут использоваться позже. Он будет загружаться раньше времени и помещаться в кеш, когда браузер будет бездействовать. Последующее использование может сохранить сетевой запрос.

Эта, казалось бы, несвязанная технология будет играть здесь большую роль, потому чтоПредварительно загруженные ресурсы не повлияют на текущую страницу.

Таким образом, мы можем сказать браузеру: сначала не контролируйтеindex.css, нарисуйте экран скелета напрямую. Позжеindex.cssПосле загрузки обратно примените стиль. В частности, код выглядит следующим образом:

<link rel="preload" href="index.css" as="style" onload="this.rel='stylesheet'">

Суть метода заключается в измененииrelПозволяет браузеру переопределять<link>Роль тега меняется с предварительной загрузки на текущий стиль страницы. (Есть также статьи, в которых используются модификацииmediaметод, но поддержка браузеров низкая, поэтому здесь он не будет расширен. Я перечислил статью в конце) В этом случае браузер отобразит каркас экрана до того, как будет получен CSS (поскольку CSS в это время все ещеpreload, то есть используется позже и не мешает текущей странице). И когда CSS загружает и изменяет свой собственныйrelПосле этого браузер повторно применяет стили, и цель достигнута.

Вопросы для рассмотрения

На самом деле это неrel="stylesheet"изменить наrel="preload"Вот и все. Нам еще многое предстоит рассмотреть, прежде чем применять его в производственной среде.

Вопросы совместимости

Первый в<link>Внутренне мы использовалиonload, то есть с помощью JS. Чтобы справиться с ситуацией, когда в браузере пользователя не включена функция сценариев, нам нужно добавить запасной вариант. (Но это может не иметь значения для одностраничных приложений, потому что при отсутствии скрипта фактическое содержимое страницы не будет отображаться)

<noscript><link rel="stylesheet" href="index.css"></noscript>

Второй,rel="preload"Не без проблем с совместимостью. Для браузеров, которые не поддерживают предварительную загрузку, мы можем добавить некоторыеполифил код(Чтобы получить согласованные результаты во всех браузерах.

<script>
/*! loadCSS rel=preload polyfill. [c]2017 Filament Group, Inc. MIT License */
(function(){ ... }());
</script>

Сжатый код полифилла можно найти в шаблоне SPA Лавасстрока 29.

порядок загрузки

В отличие от традиционных страниц, наш фактический DOMrender.jsСгенерировано. Поэтому, если JS выполняется до CSS, будет скачок. (Поскольку фактический контент отображается первым, но стиль отсутствует, а затем загружается стиль, страница явно меняется)Так что здесь нам нужно строго контролировать предварительный рендеринг CSS.

<link rel="preload" href="index.css" as="style" onload="this.rel='stylesheet';window.STYLE_READY=true;window.mountApp && window.mountApp()">

JS предоставляетmountAppМетод используется для рендеринга страницы (фактически имитирует Vue).mount)

// render.js

function mountApp() {
    // 方法内部就是把实际内容添加到 <body> 上面
}

// 本来直接调用方法完成渲染
// mountApp()

// 改成挂到 window 由 CSS 来调用
window.mountApp = mountApp()
// 如果 JS 晚于 CSS 加载完成,那直接执行渲染。
if (window.STYLE_READY) {
    mountApp()
}

Если CSS загружается быстрее, то, установивwindow.STYLE_READYРазрешить выполнение JS сразу после загрузки, а если JS быстрее, то он не будет выполняться первым, а оставить возможность для CSSonload.

пустая загрузка

loadCSSразработчиков предполагают, что некоторые браузеры будутrelПерезагружать при сменеonload, в результате чего логика повторяется дважды. Для устранения этого эффекта мыonloadдобавить предложениеthis.onload=null.

Окончательный справочник по CSS

<link rel="preload" href="index.css" as="style" onload="this.onload=null;this.rel='stylesheet';window.STYLE_READY=true;window.mountApp && window.mountApp()">

<!-- 为了方便阅读,折行重复一遍 -->
<!-- this.onload=null -->
<!-- this.rel='stylesheet' -->
<!-- window.STYLE_READY=true -->
<!-- window.mountApp && window.mountApp() -->

Модифицированный эффект

Модифицированный код находится вздесь, адрес доступа находится по адресуздесь. (Для краткости я пропустил код, отвечающий за совместимость, т.е.<noscript>и предзагрузить полифилл)

Скриншот производительности выглядит следующим образом: (по-прежнему используются сетевые настройки «Fast 3G»)

preload timeline

на этот раз вrender.jsа такжеindex.cssКогда страница еще загружается, содержимое экрана скелета уже отображается, и его можно наблюдать даже невооруженным глазом. В случае со скриншотами отображение скелетного экрана длится около 300 мс, что занимает примерно половину всего сетевого запроса.

Что касается того, почему скелетный экран не отображается сразу после загрузки HTML, а для отображения требуется около 300 мс.На рисунке показано время, затрачиваемое браузером ParseHTML.Возможно, вычислительные ресурсы ограничены, когда Dev Инструменты открыты, но их можно оптимизировать, места мало. (Возможно, упрощение структуры каркасного экрана может сыграть какую-то роль)

Поддержка экрана с несколькими скелетами

Вообще говоря, все страницы сайта вряд ли будут иметь одинаковый тип отображения. Например, домашняя страница и внутренняя страница будут сильно различаться по стилю отображения, например, страница списка и страница поиска относительно близки (могут иметь отображение списка), но отличаются от страницы сведений ( могут быть продукты, услуги, личная информация, сообщения в блогах и т.д. и т.п.) будут очень разными. Но одностраничное приложениеindex.htmlЕсть только один, все изменения исходят из внешней среды рендеринга, вносящей изменения внутри узла контейнера. Таким образом, непосредственно введите скелетный экран вindex.htmlЭто приведет к тому, что все страницы будут использовать один и тот же скелетный экран, будет трудно достичь цели «похожей на реальную структуру контента», и каркасный экран выродится в загрузку.

Для поддержки нескольких каркасных экранов нам необходимоindex.htmlЛогика суждения выполняется внутри (независимо от основного JS), а именно:

  1. Напишите HTML и стили для всех типов каркасных экранов.index.html

  2. существуетindex.htmlДобавлен встроенный скрипт ниже<script>, в соответствии с текущим маршрутом, чтобы определить, какой экран скелета должен отображаться

Это приведет кindex.htmlОбъем стал немного больше, но общее ощущение, что выгоды перевешивают затраты, которые, я думаю, того стоят.

постскриптум

Этот пункт оптимизации был впервые введен моим бывшим коллегойсяоп одноклассникОбнаружено и выполнено при разработке шаблона SPA Lavas, проблема задокументированаздесь. На его основе я сделал пример, который разделяет среды Lavas и Vue и делает его более понятным, чтобы скриншоты были максимально понятны и читабельны. Большое спасибо за его работу!

Кроме того, я использую чистый рукописный HTML и CSS для написания скелетного экрана, не только логика отображения, но и процесс разработки, не зависящий от других обычных страниц одностраничного приложения. Конечно, это может доставить небольшие неудобства разработчикам, поэтому в это время необходимо запустить острый инструмент одноклассников xiaop:vue-skeleton-webpack-plugin. Его роль состоит в том, чтобы экранировать сам скелет как компонент Vue, в сочетании с отдельными правилами маршрутизации до единого опыта разработки в проекте Vue, в последний раз используемый в упаковке WebPack различают сборку и при введении, используя классные одноклассники Vue + Webpack.

Справочная статья