JavaScript. Как сделать невероятно быстрый многопоточный Data Grid на 1 000 000 строк. Часть 1/2: нюансы работы с DOM

от автора

Demo | GitHub

Рисунок 1. Data Grid на 1 000 000 строк

Рисунок 1. Data Grid на 1 000 000 строк

Особенности Fast Data Grid:

  • Невероятно быстрый

  • Многопоточный

  • Всего 523 строчки кода

  • Нет зависимостей

  • Vanilla JavaScript

Попробуйте скролл и поиск по 1 000 000 строк — Fast Data Grid.

В статье перечислю нюансы работы с DOM. Про многопоточность в следующей статье.

Чем меньше DOM — тем лучше. Изменить содержимое DIV быстрей чем удалить DIV и создать новый

Браузер медленно отрисовывает большое DOM-дерево. 1 000 000 строк высотой 20 px браузер вообще не нарисует — максимальная высота DIV в Chrome 15 000 000 px. Чем меньше HTML-элементов тем лучше.

Fast Data Grid добавляет в DOM столько строк, сколько поместятся на экране.

const rowsCount = Math.ceil(viewPortHeight / rowHeight);

Листинг 1. Подсчет сколько строк помещается на экран

Когда нужно вывести новые данные DIV-ы строк переиспользуются. Новые данные записываются в те же самые DIV-ы. Изменить содержимое DIV быстрей чем удалить DIV и создать новый.

При скроле позиция DIV-ов строк рассчитывается на JavaScript.

Максимальная высота DIV 15 000 000 px

Для работы скролла, Fast Data Grid делает большой DIV. На этот DIV вешается событие scroll. Обработчик события scroll рассчитывает позицию DIV-ов строк.

Рисунок 2. Большой DIV для скрола

Рисунок 2. Большой DIV для скрола

Если суммарная высота строк больше 15 000 000 px, то DIV-ы строк должны крутиться быстрее чем большой DIV. Когда большой DIV докручивается до конца -> DIV-ы строк тоже должны докручиваться до конца.

При скроле DIV-ов строк нужно применять коэффициент.

const scrollYKoef = // if {allRowsHeight} > 15 million -> we have to applay koef on scroll // if {allRowsHeight} <= 15 million -> {scrollYKoef} = 1 (allRowsHeight - viewPortHeight) / (scrolHeight - viewPortHeight);   listen(scrollOverlayDiv, 'scroll', /** @param {Event & {target:HTMLDivElement}} evt */ evt => { const scrollTop = evt.target.scrollTop * scrollYKoef; rowsDiv.style.transform = `translateY(${scrollTop}px)`; });

Листинг 2. Применение коэффициента при скроле

CSS transform translate быстрее чем CSS top

При скроле позиция задается через transform translate. CSS transform translate быстрее чем CSS top.

<!-- transform быстрее --> <div style="transform: translateY(-16px);"></div>  <!-- чем top --> <div style="top: -16px;"></div>

Листинг 3. CSS transform translate быстрее чем CSS top

Вначале читайте DOM, потом изменяйте DOM. Плохо читать DOM после изменения

Браузер выводит фреймы на монитор так:
В начале отрабатывает JavaScript, потом рассчитываются стили, потом layout, потом отрисовка.

Рисунок 3. Стандартный порядок операций при выводе фрейма на монитор

Рисунок 3. Стандартный порядок операций при выводе фрейма на монитор

Если стандартный порядок не нарушать — браузер отрисует фрейм максимально быстро.

В начале цикла параметры DOM уже рассчитаны и соответствуют параметрам предыдущего фрейма. Например box.offsetHeight в начале цикла уже рассчитан. Но если изменить DOM и потом прочитать DOM -> браузеру придется нарушить стандартный порядок. Потребуется лишний раз рассчитать layout.

box.classList.add('super-big');  // Gets the height of the box in pixels and logs it out: console.log(box.offsetHeight);

Листинг 4. Изменение DOM перед чтением DOM. Плохо. Приводит к layout thrashing.

Лишний перерасчет Layout называется “layout thrashing”.

Визуальная демонстрация, как изменение DOM перед чтением замедляет браузер:
https://wilsonpage.github.io/fastdom/examples/animation.html

Отличная статья по теме:
Avoid large, complex layouts and layout thrashing  |  Articles  |  web.dev.

Самореклама

Делаю самый удобный редактор блок-схем DGRM.net.
Он же самый удобный сервис для бизнеса: Excel + схемы бизнес процессов.

Ставьте звездочки на GitHub.


ссылка на оригинал статьи https://habr.com/ru/articles/862272/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *