Наша компания использует Google Search Console для проверки статуса индексации и оптимизации видимости наших веб-сайтов. Также в консоли можно проверить, какие внешние веб-сайты ссылаются на вашу страницу. Однажды я просматривал страницу «Top linking sites» и заметил сильное торможение скроллинга. Оно происходило, когда я выбирал отображение большого массива данных (500 строк) вместо стандартных 10 результатов.


Раздел «Top linking sites» в Google Search Console, 500 строк на страницу

Я интересуюсь производительностью фронтенда, поэтому не мог удержаться и решил разобраться, в чём дело. В конце концов, Google активно стремится к повышению веб-производительности, поэтому стоит ожидать, что собственные публичные приложения компании будут хорошим эталоном.

Этап 1 — Запись профиля производительности


В подобных случаях невероятно полезны профили производительности: часто бывает достаточно посмотреть отчёт, чтобы понять, почему какой-то элемент имеет низкую производительность. Я открыл DevTools / Performance и начал запись, немного прокрутил список вниз, а затем остановил запись. И вот что я увидел:


Профиль производительности скроллинга таблицы данных «Top linking sites», очень низкий FPS

Эти красные треугольники в блоках «Task» дают нам понять, что при скроллинге какие-то операции занимают больше времени, чем это приемлемо. Обычно для достижения идеального скроллинга в 60 FPS мы стремимся, чтобы эти блоки выполнялись менее чем за 16 мс. На показанном выше изображении блоки с красными треугольниками в среднем выполняются примерно 150 мс, что приводит приблизительно к 6–7 FPS. Да ладно, Google, ты ведь способен на большее!

Этап 2 — Разбираемся в причинах


Шкала времени наверху показывает, насколько занят CPU различными видами задач: оранжевый цвет — это JavaScript, фиолетовый — это структура и стили, а зелёный — отрисовка. Здесь всё фиолетовое, то есть проблема не в JavaScript, а в DOM/стилизации:


График показывает, что CPU занят обработкой структуры

Это подтверждается каскадным графиком под графиком CPU. В нём используются те же цветовые обозначения и в большинстве записей присутствует много оранжевого и немного меньше фиолетового и зелёного. В нашей записи видно, что время в основном тратится на обновление слоёв, это видно по тексту в фиолетовых блоках, гласящему Update layer tree:


На каскадном графике видено, что скроллинг тормозит из-за «Update layer tree»

Слои создаются для скроллящегося контента, переводного контента и так далее. Возможно, их очень много? Давайте выясним!

Этап 3 — Изучаем слои


В Chrome DevTools содержится впечатляющее количество полезных инструментов, но некоторые из них найти довольно сложно. Одним из таких скрытых сокровищ является панель Layers; чтобы найти её, нужно нажать кнопку меню в DevTools и выбрать More tools / Layers. В моём случае это выглядит так:


Панель «Layers» в Chrome DevTools; слой заполнен кучей контента

Слоёв не очень много, но есть парочка огромных. Похоже, в них куча контента, и это приводит нас к выводу о том, что используемый Google datagrid не применяет виртуализированный рендеринг. Частично это объясняет причины торможения, но 500 строк — это всё равно не очень много. Должно быть что-то ещё…

Этап 4 — Изучаем DOM


К сожалению, DOM не особо производителен, когда содержит много элементов. Если бы он был производительным, техники виртуализации, реализованные в различных популярных в вебе data grids на JS, не потребовались бы. На данном этапе мы можем предположить, что таблица рендерит много элементов. Создав Live expression в DevTools Console, вы можете пощёлкать по панели элементов и выяснить это. Переключимся на Console, нажмём на кнопку Create live expression (глаз) и введём $0.querySelectorAll('*').length.

Теперь нажимая на панель Elements, мы видим следующее, сначала для всей таблицы:


Live expression демонстрирует количество элементов-потомков для выбранного элемента

Как мы видим, для отображения всего 500 строк он создаёт больше 16 тысяч элементов DOM, что немного излишне. Нажав на тело документа, мы увидим следующее:


Куча элементов!

Вся страница содержит больше 38 тысяч (!) элементов, а так быстрое приложение не пишут! Очевидно, что ситуацию можно было бы улучшить, использовав data grid с виртуализированным рендерингом, но давайте посмотрим, можно ли сделать что-то меньшими усилиями.

Этап 5 — Улучшаем ситуацию


Учитывая данные в профиле производительности, я подозреваю, что структура всей страницы создаётся при скроллинге таблицы. А создание структуры такого количества элементов — это затратная операция. Если бы был какой-то способ ограничения её влияния…

Хорошие новости — он существует! Я попробовал применить один тайный рецепт, снова поскроллил, и теперь ситуация стала гораздо лучше. И это чётко видно из следующего профиля производительности:


Скроллинг сильно улучшился!

Каждый кадр теперь занимает примерно 16 мс, и мы скроллим почти на 60 FPS вместо 6–7. Потрясающе!

Что же я сделал? Просто добавил одну строку CSS в <table> на панели Elements, указав, что таблица не должна влиять на структуру или стили других элементов страницы:

table {
  contain: strict; 
}

Вот так:


Вот и всё, десятикратное увеличение скорости благодаря одной строке CSS. Вы можете попробовать «починить» свою Google Search Console.

Подробнее о CSS-свойстве contain можно узнать в MDN.

Комментарии (26)


  1. zede
    02.12.2021 16:29
    +1

    Эх, а ведь все еще приходится для кроссбраузерного ускорения абузить враппер + `position: absolute;`. Тот же Safari, насколько я помню, все еще не может в `contain`.


    1. ArchiBoom
      03.12.2021 16:28

      Сколько себя знаю, а в сафари на маке и таблицы тормозят, и документы(( да и MS с Adobe тоже


      1. Vasek18
        05.12.2021 19:37
        +1

        Я в пет проектах просто отключал поддержку сафари)


  1. rdv-ua13
    02.12.2021 16:35
    +1

    Спасибо, было интересно.


  1. matheu042
    02.12.2021 16:35
    -2

    Да - это жостка)


    1. ForeverLive
      03.12.2021 12:14
      +18

      Хабр 2021. Жостка ппц превед йопта.


      1. Flux
        04.12.2021 00:36
        -3

        The future is now, old man.


  1. Alexufo
    03.12.2021 01:53
    -8

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


    1. vanxant
      03.12.2021 05:14
      +21

      Наверное, потому что это таблица?..


      1. Alexufo
        03.12.2021 21:09

        А что такое таблица? Структура с ячейками? Ну гриды тоже с ячейками, но это же не таблица. Таблица не самоцель. Учитывая, что она плохо работает в адаптиве.


        1. kisskin
          04.12.2021 01:42
          +1

          Зато дивы отлично копируются и вставляются в эксель. (нет)


          1. Alexufo
            04.12.2021 02:22

            когда хром научится по ctrl выделять в таблицах ячейки так же как в ff я обязательно вспомню про ваш лайфхак


        1. vanxant
          04.12.2021 02:54
          +1

          Нормально всё с таблицами в адаптиве, если уметь их готовить.

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


          1. Alexufo
            04.12.2021 03:11

            с таблицами нет вариантов адаптива, кроме отображения через overflow сокрытие и горизонтальный скролл.

            В конечном счёте всегда можно перешаманить ячейки хоть на гриды, хоть на абсолютное позиционирование

            На сколько я знаю нет, нельзя просто так ожидать от td и tr поведения как от div. Гриды к ним не применить, а уж абсолютное тут вообще не в тему, высота родителя теряется и там еще больше проблем чем от таблиц. Костылить можно чем угодно но таблица на дивах это единственное, что позволяет управлять контентом максимально.
            Как минимум, для этого есть flex-direction. Просто поменять в нужный момент направление. А как максимум гриды. Но и на флексах можно из трех ячеек сделать две, пустив среднюю ниже под две. Таким образом сократив ширину общей строки. Для большого количества данных подходят таблицы, удобнее, потому что их все равно придется их отображать через горизонтальный скролл, а для таблиц до 5-8 значений вполне можно на дивах, если адаптив там получается симпатичный, ну и если он нужен конечно. Не все таблицы нужно адаптивить.


            1. vanxant
              04.12.2021 05:04
              +1

              нельзя просто так ожидать от td и tr поведения как от div. Гриды к ним не применить

              FYI

              https://jsfiddle.net/azkc07tm/2/


              1. Alexufo
                04.12.2021 05:28

                да, спасибо, был не прав, все таки это обычные dom элементы, почему то я решил что нельзя перебивать их некоторые свойства, как у кнопки input type=«file»


  1. Platon_123
    03.12.2021 07:45
    +8

    Вот это действительно магия - одна строчка на css и веб-страница перестала тормозить. Я бы, например, если бы и нашёл из-за чего тормозит, вряд ли бы до такого "финта ушами" догадался. Супер!


  1. sinneren
    03.12.2021 10:27
    +2

    Так и в чём суть-то? Ну добавил, теперь пустое всё. Или правильно его добавлять на всё то, что грузится фетчем и после добавления стиль удалять?


  1. SergeiMinaev
    03.12.2021 12:12
    +8

    Вся страница содержит больше 38 тысяч (!) элементов, а так быстрое приложение не пишут!

    Это ахтунг. Одно из основных правил, если немного заботит производительность - чем меньше элементов, тем лучше. Как автор подметил выше, DOM сам по себе становится медленнее, когда элементов много, даже если не учитывать пересчёт раскладки и рендеринг. Если нужно отрисовывать кучу однотипных блоков/таблиц, есть много библиотек для виртуального рендеринга (обычно называются virtual scroll или virtual grid), которые ограничивают кол-во одновременно отображаемых элементов.

    Что же я сделал? Просто добавил одну строку CSS в <table> на панели Elements, указав, что таблица не должна влиять на структуру или стили других элементов страницы

    Было бы интересно узнать, почему именно это повысило производительность. А то статья как-то внезапно заканчивается.


    1. jtraub
      03.12.2021 13:45
      +1

      https://developer.mozilla.org/en-US/docs/Web/CSS/contain

      The contain CSS property allows an author to indicate that an element and its contents are, as much as possible, independent of the rest of the document tree. This allows the browser to recalculate layout, style, paint, size, or any combination of them for a limited area of the DOM and not the entire page, leading to obvious performance benefits.


      1. sinneren
        03.12.2021 14:34

        Ну ок, независимо от общего дерева. Почему тогда элементы пропадают. Как это работает-то ответа так и нет? Для табличек ок, на дивку добавил - всё сломалось.


        1. nsinreal
          03.12.2021 15:25
          +1

          Потому что contain: strict - это алиас к contain: size layout paint, а contain: size указывает, что элемент рендерится независимо от размеров дочерних элементов.

          В то же время, на table автоматически задан display: table, который очень серьезно перекручивает рендеринг. (Как именно перекручивает — я точно не знаю).

          Соответственно, нужно задавать либо contain: strict + display: table; либо contain: strict + height: 800px; либо contain: layout + paint.


          1. sinneren
            03.12.2021 16:04

            size указывает, что элемент рендерится независимо от размеров дочерних элементов

            ну так это противоречие. если задать дивке, то она и ее содержимое не отрендерится.


            1. nsinreal
              03.12.2021 16:42

              Нету тут противоречия. Если задаете contain: size, то нужно задавать высоту divа, потому что иначе его высота рассчитывается независимо от дочерних элементов — т.е. 0px. Контент будет рендериться, как только у него будет указана высота.


            1. nsinreal
              03.12.2021 16:46

              Типичный кейс для этой штуки:

              1. contain: strict;

              2. height: 400px;

              3. overflow: auto;


    1. nsinreal
      03.12.2021 15:18

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