Оптимизация вывода длинного списка в браузере

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

Открытие списка

При раскрытии этого пункта страница «подвисает» приблизительно на 40 секунд (перестаёт реагировать на всё, не меняется даже вид курсора при его движении).
При изучении вкладки Timeline в Chrome DevTools выяснилось, что всё это время занимает только отрисовка контента.
Выглядит это как длинная фиолетовая полоска:
d6c7a771bf5b61d5be1191e44d556f2f3fb10ff5a0

В данном случае возможно оптимизировать вывод двумя способами.

Выводить только те элементы, которые видны в браузере

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

Добавлять элементы пачками

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

Повторное открытие списка

Изначально для повторного открытия списка использовалось банальное jQuery.show/hide, которое приводило к таким же подвисаниям приблизительно на 40 секунд.
Решил с этим побороться банальным

как видно в листинге выше.
Но удаление элементов занимает уже чуть больше 80 секунд (кстати, столько же занимает и удаление через $(el)[0].innerHTML = ‘ ‘;).
Попытка проставить всем элементам display: none;(jQuery(‘.item’).hide()) внутри свёрнутого пункта меню не увенчалась успехом (не дождался окончания, уж очень долго).
А помогло следующее: перезаписывать через innerHTML нужно не тот элемент, который содержит все эти 30000 элементов, а его родителя.
Тогда процесс занимает 0.1 секунды.
Вот код, на который заменил $(el).empty(); из предыдущего листинга:

3 Responses so far.

  1. Wl:
    А зачем такое подменю?
    Ведь данные отображаются для ВИЗУАЛЬНОГО просмотра
    Просматривать 30 ТЫСЯЧ записей бессмысленно

    Думаю, такое подменю надо либо делить на дополнительные подменю
    Либо просматривать с поиском — фильтром

    • bullgare:
      В целом правильные замечания.
      Это меню — типы ошибок в системе сбора логов.
      И деление на подменю было уже сделано.
      А эти 30000 ссылок — это что-то вроде «другие ошибки», т.е. те, которые под фильтры не подходят.
      Фильтр для поиска там тоже уже был, и начинался после ввода одного символа, что приводило к примерно таким же «тормозам».
      Задача была не переделать то, что уже существовало, а сделать этот функционал рабочим, т.е. «юзабельным».
  2. bullgare:
    Ну и мы же здесь не для обсуждения логики приложения, а для обсуждения одного из способов клиентской оптимизации. :)

LEAVE A COMMENT