Проблемы больших (по количеству ячеек) таблиц в HTML
Многие проблемы формирования и отображения больших по количеству ячеек HTML-таблиц вполне известны более-менее опытным фронтендерам. Но в разработке не все являются гуру HTML и есть множество других сфер, где этот HTML используется только время от времени, например для отображения каких-либо данных из базы данных в виде HTML-документа с последующей его обработкой.
Чтобы далее не было путаницы, уточню, что в статье под большой таблицей подразумевается наличие огромного числа столбцов и строк, а не большой размер.
Вообще я хотел поднять эту тему в блоге еще с момента учебы в университете, когда услышал от одного из преподавателей недовольство касаемо медлительности отображения обширных табличных данных в какой-то там используемой по работе системе, работающей на Java. Затем как-то в StackOverflow мне попался вопрос, в котором автор вопроса столкнулся с проблемой ограничений HTML таблиц. Ну и после, как-то еще где-то этот вопрос поднимался.
В общем ситуация хоть и редчайшая, но вполне актуальна и стоит того, чтобы уделить ей пару слов.
Низкая производительность рендеринга больших таблиц
Начну пожалуй с того, что выделю несколько наиболее важных в нашем случае этапов рендеринга таблиц (и других HTML-элементов, см. подробнее Critical Rendering Path):
- Построение дерева рендера (Render Tree) — этап, при котором строится финальное дерево с учетом DOM и CSSOM
- Компоновка (Layout) — этап, при котором расчитываются размеры и положение всех элементов на странице
- Отрисовка (Paint) — простыми словами, это этап заливки пикселями.
Этапы выполняются последовательно, т.к. каждый последующий этап зависит от результатов предыдущего. Соответственно, от того, какие именно изменения происходят на элементах, будет зависеть, с какого этапа будет обновляться рендер. Например, если вы по наведению просто изменяете фоновый цвет элемента, то нет необходимости в перекомпоновке и достаточно только обновить этап отрисовки.
Изменение размеров или положения элементов уже задевают этап компоновки с последующей перерисовкой, но при этом никак не задевает структуру дерева. Логично, что это работает куда медленнее простой перерисовки элемента.
А вот изменения в структуру DOM могут привести к обновлению дерева рендера, что в свою очередь приведет к перерасчету компоновки и обновлению отрисовки, что может негативно повлиять на производительность в общем.
В большой таблице этап компоновки может быть очень медленным в случае, если размеры ячеек рассчитываются автоматически, в зависимости от контента. В таком случае если у вас есть таблица 1000х1000, то для рассчета координат расположения последней ячейки таблицы на странице, в любом случае необходимо будет произвести рассчеты для всех ячеек, расположенных до нее (итого как минимум 1000 * 1000 = 1000000 ячеек, не включая остальные элементы таблицы), т.е. отложить вычисления не получится никак.
Проблема сильно усугубляется в том случае, если в таблице происходят динамические изменения с помощью CSS или JS, которые могут влиять на компоновку её элементов на странице или на структуру дерева. Такие изменения каждый раз будут триггерить полный перерасчет и обновление рендеринга для всех элементов таблицы, что в рамках гигантской таблицы очень ощутимо.
Отмечу, что в некоторых случаях, ускорить отображение огромных таблиц может указание фиксированных размеров колонкам таблицы через тег col или явно задав ограничения через CSS. Трюк старый и вполне еще актуальный.
Ограниченные значения атрибутов colspan и rowspan
Как известно, атрибуты colspan
и rowspan
обозначают, какое количество столбцов или строк должно быть объединено. Прикол в том, что согласно стандарту, максимально допустимое значение для colspan
— 1000, а для rowspan
— 65534.
Проверить это можно следуюшим способом:
const td = document.createElement('td');
td.colSpan = 1000000;
td.rowSpan = 1000000;
// вместо 1000000 выведется 1000 и 65534
console.log(td.colSpan, td.rowSpan);
Если честно, в подавляющем большинстве случаев вы с такими ограничениями врядли столкнетесь. Хотя, мало-ли какие задачи бывают, например это может быть при выгрузке каких-нибудь аналитических данных из какого-то необычного источника (тот же Excel) в формате HTML таблицы, где необходимы большие значения для объединения ячеек. Этому не стоит удивляться, ибо мир коммерческой разработки всегда может удивить постановкой задач.
Большое потребление памяти
DOM-модель является не только медленным но и достаточно прожорливым по памяти инструментом. В случае болших таблиц, это значит, что необходимо обращать внимание на большое выделение памяти не только на данные, но и на саму таблицу. Каждая ячейка (HTMLTableCellElement), строка (HTMLTableRowElement) и пр., являются отдельными объектами DOM-модели, которые естественно нуждаются в вашей драгоценной оперативной памяти.
В дальнейшем эта проблема будет только усугубляться, ибо с каждым добавлением каких-то плюшек в стандарты DOM-модели, её объекты будут становиться все более жирными по памяти. В обычных случаях это может быть незаметно, но при наличии огромного числа DOM-элементов в гигантской таблице, это уже становится неприятно.
Как работать с гигантскими таблицами
Если есть возможность, старайтесь избегать формирования гигантских HTML таблиц. Например, можно разбивать таблицу на отдельные маленькие таблицы, которые браузер может рендерить независимо друг от друга.
Если нужна единая таблица, то обработку таблицы можно переложить на JavaScript. Фокус в том, что нам нет необходимости строить всю таблицу целиком, т.е. как минимум достаточно генерировать отображение только тех данных, которые выводятся в текущий момент пользователю. Задача в реальности не самая простая, т.к. в таком случае, необходимо обеспечить подгрузку и выгрузку данных из таблицы по мере прокрутки таблицы и пр. и поэтому проще просто воспользоваться одним из множества готовых решений.
Ну а если вам все-таки прям очень нужно выводить гигантскую HTML-таблицу, то хотя-бы в таком случае избегайте использования динамических CSS или JS эффектов, которые могут повлиять на этап копоновки элементов таблицы, а также все возможные изменения в таблице делайте пакетно.
Заключение
В веб-разработке стоит избегать генерации огромных таблиц, поскольку на практике браузеры работают с ними достаточно тяжело. Надо понимать, что веб-браузер не является специализированным процессором табличных данных, ибо он не для этого предназначен.
Тем не менее, это не значит что в браузере нельзя реализовать эффективную работу с большими табличными данными, просто в данном случае трудно будет обойтись без помощи JavaScript и понимания деталей и особенностей этапов рендеринга в браузерах.