Автоматическая виртуализация рендеринга произвольной вёрстки

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

    Это - текстовая расшифровка выступления на HolyJS'20 Moscow. Вы можете либо посмотреть видео запись, либо открыть в интерфейсе проведения презентаций, либо читать как статью… Короче, где угодно лучше, чем на Хабре, ибо новый редактор плохо импортирует статьи..

    Тяжёлое прошлое: Огромные списки задач

    Сперва расскажу немного о своём опыте. Мой релевантный опыт начался с разработки огромных списков задач, состоящих из нескольких десятков тысяч задач. Они образовывали иерархию и имели довольно причудливый дизайн, что осложняло применение virtal scroll. А таких списков задач на экране могло быть десятки разом. И всё это в реальном времени обновлялось, дрегендропилось и анимировалось.

    Рекламная пауза: Богатый редактор

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

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

    Альтернативная линия времени: $mol

    Ну и, конечно, нельзя не упомянуть про разработанный мною фреймворк, обеспечивающий автоматическую виртуализацию. Который задоминировал всех конкурентов.. В альтернативной линии времени. Там у нас все хорошо и поэтому я пришёл к вам.

    mol.hyoo.ru

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

    Типичный цикл разработки

    Типичный цикл разработки выглядит так:

    • Написали код.

    • Проверили в тепличных условиях.

    • Пришли пользователи и всё заспамили.

    • Оптимизировали.

    • Пользователи всё не унимаются.

    Мы уже взрослые ребята мы не пишем тяп-ляп и в продакшен. Теперь мы пишем код, проверяем его на тестовых данных, и если все хорошо, выкладываем в прод. Но тут приходят пользователи генерируют кучу контента и всё начинает тормозить. Тогда мы закатываем рукава и начинаем оптимизировать. Но приходят пользователи и генерят ещё больше контента и тормоза возвращаются вновь.

    Наивный рендеринг: Скорость загрузки и Отзывчивость

    Этот порочный круг приводит к таким вот последствиям:

    Сейчас вы видите timeline загрузки и рендера двух с половиной тысяч комментариев. Скрипту требуется 50 секунд на формирование DOM, после чего ещё 5 секунд нужно браузеру чтобы рассчитать стили, лейаут и дерево слоёв.

    И это не картинка из будущего. Это самое натуральное настоящее. Замечу, что это хоть и мобильная версия, но открытая на ноуте. На мобилке все куда печальнее.

    Наивный рендеринг: Потребление памяти

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

    На моем телефоне (потряхивает своим тапком) её меньше 3 гигов. И я думаю не надо объяснять откроется ли она у меня вообще. Тут мы плавно переходим к следующему риску..

    Наивный рендеринг: Риск неработоспособности

    Если мы не влезаем по памяти, то приложение может закрыться. Причём может закрыть не одно, а утянуть с собой ещё несколько. А даже если не закроется, то чем больше приложение, тем дольше оно грузится. А чем дольше грузится, тем выше риск, что соединение оборвётся и всё придётся начинать заново. Ну либо довольствоваться первыми 4 комментариями из двух с половиной тысяч - реальная ситуация на моей мобилке.

    • Не влезли по памяти - приложение закрывается.

    • Обрыв соединения - страница обрывается.

    • Браузер может заглючить на больших объёмах.

    Наивный рендеринг: Резюме

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

    • Медленная загрузка.

    • Плохая отзывчивость.

    • Высокое потребление памяти.

    • Риск неработоспособности.

    Первый подопытный: Статья на Хабре

    Недавно я опубликовал статью, где рассказывал как можно ускорить Хабр раз в десять, отказавшись от SSR.

    Вырезаем SSR и ускоряем Хабр в 10 раз

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

    https://nin-jin.github.io/habrcomment/#article=423889

    Второй подопытный: Ченьжлог на GitLab

    На сей раз мы разберём новый кейс - страница коммита на GitLab.

    Я просто взял второй попавшийся коммит в первом попавшемся репозитории. Это всего порядка 5 тысяч строк в 100 файлах. Казалось бы - совсем не много. Однако, грузится всё довольно долго. Сначала 10 секунд ждём ответа сервера, потом ещё пол минуты любуемся мёртвыми кусками кода без подсветки синтаксиса. Короче, приходится ждать почти минуту, прежде чем приложением становится возможно пользоваться.

    Перенос рендеринга HTML на сервер

    Что ж, давайте откроем таймлайн и увидим там следующую картину.

    Пол минуты HTML выдаётся браузеру. Обратите внимание что она вся фиолетовая. Это значит что каждый раз, когда браузер получает очередной чанк HTML и подкрепляет его в документ, то пересчитывает стили, лейаут и дерево слоёв. А это весьма не быстрый процесс. И чем больше DOM, тем медленнее он становится.

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

    Страдания Ильи Климова по GitLab-у

    Ну да ладно со скоростью загрузки. Один раз подождать ведь не проблема? Как бы не так! Браузер делает пересчёт стилей, лейаута и слоёв чуть ли не на каждый чих. И если вы попробуете поработать со страницей, то заметите, что на наведение указателя она реагирует весьма не рьяно, а вводить текст с лютыми задержками крайне не комфортно.

    Год назад Илья Климов как раз рассказывал в своём выступлении страшные истории про работу как раз над этой страницей. Например, прикручивание спинера заняло 3 месяца работы не самого дешёвого разработчика. А сворачивание небольшого файла вешало вкладку на 10 секунд. Но это, правда, уже оптимизировали - теперь на это требуется всего лишь пара секунд!

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

    На мой вопрос "что ж вы на $mol всё это не перепишите, чтобы у вас всё летало?" Илья ответил, что не знает как его продать в компании. Что ж, надеюсь дальнейший разбор техник виртуализации поможет ему в этом нелёгком деле.

    Оптимизации вёрстки

    Первое что приходит на ум - а давайте упростим вёрстку и DOM уменьшится. В качестве примера - кейс от Альфа-банка, который я разбирал в своей статье об истории $mol.

    $mol: 4 года спустя

    Там был компонент вывода денежных сумм, который рендерился в 8 DOM элементов вместо 3.

    <div class="amount">
        <h3 class="heading ...">
            <span>
                <span class="amount__major">1 233</span>
                <div class="amount__minor-container">
                    <span class="amount__separator">,</span>
                    <span class="amount__minor">43</span>
                </div>
                <span class="amount__currency"> ₽</span>
            </span>
        </h3>
    </div>
    
    <h3 class="amount">
        <span class="amount__major">1 233</span>
        <span class="amount__minor">,43 ₽</span>
    </h3>
    

    Не смотря на высокую частоту использования он был не оптимизирован. После моей статьи над ним поработали и теперь он рендерится в 5 элементов.

    Если так поработать и над остальными компонентами, думаю можно уменьшить размер DOM раза в 2. Однако…

    Достоинства оптимизации вёрстки

    Важно понимать, что асимптотика таким образом не меняется. Допустим страница грузилась 30 секунд. Вы оптимизировали, и она сала грузиться 10 секунд. Но пользователи сгенерируют в 3 раза больше контента и страница снова грузится 30 секунд. А оптимизировать вёрстку уже не куда.

    • Кратное ускорение ✅

    • Асимптотика не меняется ❌

    Короче, годится эта техника лишь как временная мера, пока вы прикручиваете что-то по серьёзней.

    Прикладная оптимизация

    Можно пошаманить над вашим прикладным кодом - кодом приложения, кодом ваших компонент. Тут есть несколько вариантов с разными ограничениями..

    • Пагинация

    • Экспандеры

    • Бесконечный скролл

    • Виртуальный скролл

    Прикладная оптимизация: Пагинация

    Первое, что приходит на ум - это пагинация.

    Не буду рассказывать что это такое - все и так с ней прекрасно знакомы. Поэтому сразу к достоинствам…

    Достоинства пагинации

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

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

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

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

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

    • Много кликать ❌

    • Ожидание загрузки каждой страницы ❌

    • Теряется контекст ❌

    • Элементы скачут между страницами ❌

    • Вероятность пропустить элемент ❌

    • Применимо лишь для плоских списков ❌

    • Большой элемент возвращает тормоза ❌

    • Слепые вас ненавидят ❌

    • Работает быстрее, чем всё скопом рендерить ✅

    В общем, думаю вы уже поняли какое тёплое у меня отношение к пагинации. Я даже отдельную статью ей посвятил..

    Популярные анти паттерны: пагинация

    Прикладная оптимизация: Экспандеры

    Можно выводить и на одной странице, но свернув каждый элемент под спойлером, а показывать его содержимое уже по клику.

    https://nin-jin.github.io/my_gitlab/#collapse=true

    Достоинства экспандеров

    Тут опять же приходится много кликать и ждать загрузки очередной ветки. Соответственно, мы тренирую мелкую моторику пользователя и позволяем ему подумать. Работает оно быстро, ведь нам нужно показать лишь список элементов, но не их содержимое. Но если пользователь откроет много экспандеров, то снова вернутся тормоза. Пользователи скрин ридеров будут вас жарко вспоминать за то, что им приходится ещё и искать эти экспандеры и нажимать на них, что крайне неудобно когда пользуется клавиатура. и, наконец, самое главное преимущество: экспандер можно применять и для иерархических структур, а не только плоских списков.

    • Очень много кликать ❌

    • Ожидание загрузки каждой ветки ❌

    • Если не закрывать, то снова тормоза ❌

    • Слепые вас проклинают ❌

    • Открывается быстро ✅

    • Применимо не только для плоских списков ✅

    Прикладная оптимизация: Бесконечный скролл

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

    И сейчас вы видите скриншот Яндекс.Диска. У меня есть там директория состоящая из пяти тысяч файлов. И если открытие её происходит относительно быстро, то, чтобы домотать до самого низа, требуется 3 с лишним минуты реального времени. Всё потому, что по мере скролла вниз, DOM становится всё больше и больше, от чего добавление очередного куска данных становится всё медленнее. В итоге, добавление последнего куска, например, занимает уже несколько секунд.

    Достоинства бесконечного скролла

    • Применимо лишь для плоских списков ❌

    • Ожидание загрузки каждой ветки ❌

    • Увеличение тормозов по мере прокрутки ❌

    • Быстрое появление ✅

    Прикладная оптимизация: Виртуальный скролл

    Дальнейшее развитие бесконечного скролла - это виртуальный скролл, который не только дорендеривается снизу, но и удаляет контент сверху, по мере прокрутки вниз. И наоборот, при прокрутке вверх.

    https://bvaughn.github.io/react-virtualized/#/components/WindowScroller

    Достоинства виртуального скролла

    Тут размер DOM остаётся примерно постоянным, то есть интерфейс остаётся отзывчивым. Но годится это лишь для плоских списков. Для иерархических применять его зачастую больно - смотря какой визуал нарисовал дизайнер. Тут еще есть ещё такое ограничение, что нам должны быть известны примерные размеры элементов, чтобы мы могли рассчитать, какие элементы попадают в видимую область, а какие точно не попадают.

    • Применимо лишь для плоских списков ❌

    • Размеры элементов должны быть предсказуемы ❌

    • Работает быстро ✅

    Прикладная оптимизация: Резюме

    • Ухудшение пользовательского опыта ❌

    • Не решают проблему полностью ❌

    • Ограниченная применимость ❌

    • Полный контроль где какую применять ✅

    • Нужно не забыть ❌

    • Нужно продавить ❌

    • Нужно реализовать ❌

    • Нужно оттестировать ❌

    • Нужно поддерживать ❌

    Оптимизация инструментов

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

    • Тайм слайсинг

    • Прогрессивный рендеринг

    • Ленивый рендеринг

    • Виртуальный рендеринг

    Оптимизация инструментов: Тайм слайсинг

    Можно попробовать так называемый time slicing. На одной из прошлых Holy JS я рассказывал про технику квантования вычислений, где долгое вычисление разбивается на кванты по 16 миллисекунд, и между этими квантами браузер может обрабатывать какие-то события делать плавную анимацию и так далее. То есть вы не блокируете поток на долгое время, получая хорошую отзывчивость..

    Квантовая механика вычислений в JS

    Достоинства тайм слайсинга

    Звучит вроде бы не плохо. Но тут есть такая проблема, как замедление общего времени работы всего вычисления. Например, если просто взять и выполнить вычисление без квантования занимает полсекунды, то если его постоянно прерывать каждые 16 мс, позволяя браузеру делать свои дела, то до завершения может пройти и пара секунд. Для пользователя это может выглядеть как замедление работы. Ну и другой аспект заключается в том что javascript не поддерживает файберы, то есть такие потоки исполнения, которые можно останавливать и возобновлять в любое время. Их приходится эмулировать тем или иным способом, а это всегда костыли, замедление работы и некоторые ограничения на то, как пишется код. В общем, с этим подходом всё не очень хорошо, поэтому в $mol мы и отказались от квантования.

    • Хорошая отзывчивость ✅

    • Замедленность работы ❌

    • Эмуляция файберов в JS ❌

    Оптимизация инструментов: Прогрессивный рендеринг

    Частным случаем тайм слайсинга является прогрессивный рендеринг, где DOM генерируется и подклеивается по кусочкам. Это позволяет очень быстро да ещё и анимированно показать первый экран и в фоне дорендерить страницу до конца. Такой подход реализован, например, во фреймворке Catberry..

    catberry.github.io

    Достоинства прогрессивного рендеринга

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

    • Хорошая отзывчивость в процессе появления ✅

    • Эмуляция файберов в JS ❌

    • На больших объёмах всё встаёт колом ❌

    Оптимизация инструментов: Ленивый рендеринг

    Вообще, изначально в $mol у нас был так называемый ленивый рендер. Суть его в том, что мы сначала рендерим первый экран и по мере прокрутки добавляем столько контента снизу, чтобы видимая область была гарантированно накрыта. А при прокрутке вверх, наоборот, удаляем снизу тот контент, что гарантированно на экран не попадает, чтобы минимизировать размер DOM для лучшей отзывчивости.

    https://nin-jin.github.io/my_gitlab/#lazy=true

    Достоинства ленивого рендеринга

    Чтобы понимать сколько рендерить элементов, необходимо знать заранее минимальные размеры элементов, которые мы ещё не отрендерили. Но это решаемая проблема. А вот другая - не очень. Хоть появляется первая страница и быстро, но по мере прокрутки вниз увеличивается размер DOM, что неизбежно приводит к снижению отзывчивости. Так что если пользователь домотал до самого низа, то нам всё равно придётся отрендерить весь DOM целиком. То есть проблема отзывчивости решена не полностью.

    • Размеры элементов должны быть предсказуемы ❌

    • Увеличение тормозов по мере прокрутки ❌

    • Быстрое появление ✅

    Оптимизация инструментов: Виртуальный рендеринг

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

    https://nin-jin.github.io/my_gitlab/

    Достоинства виртуального рендеринга

    • Размеры элементов должны быть предсказуемы ❌

    • Работает, наконец, быстро ✅

    Оптимизация инструментов: Резюме

    На уровне инструментов поддержка сейчас есть лишь в полутора фреймворках: time slicing в React, прогрессивный рендер в catberry и виртуальный рендер в $mol. Зато, такую оптимизацию инструмента достаточно реализовать один раз? и далее наслаждаться ею во всех своих приложениях не тратя дополнительное время на оптимизацию.

    • Поддерживает полтора фреймворка ❌

    • Работает само по себе ✅

    Оптимизации: Резюме

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

    Оптимизация

    Стоит того?

    Вёрстка

    Прикладной код

    Инструментарий

    Виртуализация браузера

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

    content-visibility: auto;
    contain-intrinsic-size: 1000px;

    И всё начинает летать. Устанавливая эти свойства, мы говорим браузеру, что он может пересчитывать layout только для видимой части, а для не видимой он будет брать ту оценку, что мы предоставили. Тут есть, конечно же, ограничение из-за которого иконка добавления нового комментария обрезается, но это решаемая проблема. А вот нерешаемая - это то, что нам всё-равно нужно потратить кучу времени на формирование огромного DOM. То есть таким образом мы можем обеспечить хорошую отзывчивость, но не быстрое открытие. Поэтому мы реализуем виртуализацию именно на стороне яваскрипта.

    Логика рендеринга

    Для каждого компонента нам нужно получить следующие данные.

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

    Далее мы пройдёмся по этим шагам подробнее.

    Оценка размеров

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

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

    Так что самое оптимальное - это минимальная оценка. То есть сколько элемент точно занимает места. Он может быть больше, но точно не меньше. Это позволяет для заданной видимой области рассчитать сколько нам нужно отрендерить элементов, чтобы точно накопить суммарной высоты больше, чем размер видимой области. Отрендерим чуть больше - ничего страшного. Важно лишь, что видимой области не будет дырок и пользователь не увидит, что мы немного сэкономили на рендеринге.

    • Последняя ❌

    • Усреднённая ❌

    • Минимальная ✅

    Типы компонент: Атомарный

    Теперь о том как рассчитать размеры. Во первых мы можем просто напрямую задать эти размеры.

    Например, допустим все иконки у нас умеют размер 24х24, или любая строка текста имеет минимальную высоту в 24 пикселя.

    Типы компонент: Стек наложения

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

    Соответственно мы просто берём максимальное значение минимального значения его элементов по соответствующим осям.

    Типы компонент: Вертикальный список

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

    Типы компонент: Горизонтальный список

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

    Типы компонент: Горизонтальный список с переносами

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

    Далее мы берём все компоненты все вложенные компоненты и рассчитываем их суммарную ширину. Поделив одно значение на другое, мы получаем количество строк, которые точно будут в нашем списке с переносами.

    В данном примере у нас будет два переноса, а следовательно после рендеринга он расползётся не менее чем на три строки.

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

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

    Типы компонент: Грид и Буклет

    Такие компоненты, как гриды и буклеты, - это просто композиция упомянутых ранее.

    Грид - это вертикальный список из горизонтальных списков. А буклет - это горизонтальный список из вертикальных списков.

    Типы компонент: Резюме

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

    • Атомарный

    • Стек наложения

    • Вертикальный список

    • Горизонтальный список

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

    Отслеживание положения: onScroll

    Теперь перейдем к вопросу о том, когда производить обновление DOM. Самое очевидное - это реагировать на событие scroll..

    document.addEventListener( 'scroll', event => {
        // few times per frame
    }, { capture: true } )

    Достоинства отслеживания onScroll

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

    • Слишком часто ❌

    • Изменения DOM ❌

    • Изменения стилей ❌

    • Изменения состояния элементов ❌

    • Изменения состояния браузера ❌

    Отслеживание положения: IntersectionObserver

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

    const observer = new IntersectionObserver(
    
        event => {
            // calls on change of visibility percentage
            // doesn't call when visibility percentage doesn't changed
        },
    
        { root: document.body  }
    
    )
    
    observer.observe( watched_element )

    Достоинства отслеживания IntersectionObserver

    К сожалению, это не работает, когда у нас элемент выходит за границы видимой области и сверху, и снизу. При скролле, процент наложения таким образом может не меняться, а значит и событие вызваться не будет. А нам отслеживать такое перемещение все равно необходимо, чтобы понимать, что теперь нужно рендерить, например, элементы не с 5 по 10, а с 10 по 15.

    • Облом, если степень наложения не меняется ❌

    Отслеживание положения: requestAnimationFrame

    Самый простой и надёжный способ отслеживать габариты элементов - это опрос по requestAnimationFrame. Обработчик вызывается 60 раз в секунду и первое, что делает, - подписывается себя на следующий фрейм.

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

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

    function tick() {
    
        requestAnimationFrame( tick )
    
        for( const element of watched_elements ) {
            element.getBoundingClientRect()
        }
    
        render()   
    }

    Достоинства отслеживания requestAnimationFrame

    Недостатком такого подхода является постоянная фоновая загрузка процессора. То есть мы тратим где-то одну-две миллисекунды на каждый фрейм. Это примерно 5% загрузка процессора на моём ноуте, что не очень много для интерфейса с которым в данный момент идёт работа. К счастью requestAnimationFrame не вызывается для фоновых вкладок, так что открытие произвольного числа вкладок не приведёт к неконтролируемому росту потребления ресурсов. Кажется это - разумная плата за простое и надёжное решение, накладывающее минимум ограничений.

    • Постоянная фоновая нагрузка ❌

    • Просто и надёжно ✅

    Обновление: Резюме

    • onScroll ❌

    • IntersectionObserver ❌

    • requestAnimationFrame ✅

    Скачки при прокрутке

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

    https://nin-jin.github.io/my_gitlab/#anchoring=false

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

    Привязка скролла: Предотвращает скачки

    Есть классический пример демонстрирующий проблему..

    https://codepen.io/chriscoyier/embed/oWgENp?theme-id=dark&default-tab=result

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

    Слева же применяется новая браузерная фича, которая привязывает скроллбар к видимой области. И после добавления блоков сверху, браузер автоматически смещает скроллбар вниз, чтобы видимый контент остался на месте.

    Привязка скролла: Выбор точки привязки

    Чтобы выбрать элемент для привязки скролла, браузер идёт по DOM от корня в поисках первого же элемента, попадающего в видимую область. Заходит в него и снова ищет первый попавшийся. И так далее. При этом совершенно не важно как элементы друг относительно друга расположены визуально. Важно лишь расположение их в доме и попадают ли они в видимую область. Разберём этот процесс на простом примере..

    Элемент 1 не видим, поэтому пропускаем. 2 видим, так что заходим в него. Тут и 2.2, и 2.3 видимы, поэтому заходим в первый. Далее ближайший видимый 2.2.2, внутри которых видимых больше нет, так что именно этот элемент становится якорем для привязки скролла. Браузер запоминает смещение верхней границы якорного элемента относительно вьюпорта и старается его сохранить постоянным.

    Привязка скролла: Подавление привязки

    Тут есть один нюанс - якорным элементом может быть только такой, для которого и для всех предков которого не запрещена привязка скролла. То есть элементы с запрещённой привязкой просто перескакиваются в поиске якорного элемента. А запрещена она может быть либо явно, через свойство overflow-anchor, либо неявно, при изменении css свойств влияющих на размеры и положение элемента.

    • overflow-anchor: none

    • top, left, right, bottom

    • margin, padding

    • Any width or height-related properties

    • transform

    Виртуализация: Распорки

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

    Виртуализация: Прокрутка вниз

    Рассмотрим несколько сценариев работы виртуализации с привязкой скролла. Для примера, возьмём компонент вертикального списка. Изначально видны элементы 3, 4 и 5, а сверху и снизу - распорки.

    Зелёная фаза - пользователь скроллит вниз. Как видно третий блок вышел из видимой области, а снизу образовалась дырка. Но пользователь это ещё не видит.

    Синяя фаза - срабатывает обработчик requestAnimationFrame и мы обновляем DOM: удаляем третий узел и добавляем шестой. Как видно, контент уехал вверх относительно видимой области, но пользователь это ещё не видит.

    Красная фаза - мы отдаём управление браузеру, а тот выбирает 4 элемент в качестве якоря и отскролливает чуть-назад. Так что пользователь видит лишь добавившийся снизу шестой элемент.

    Виртуализация: Прокрутка вверх

    Аналогичная ситуация и со скроллингом вверх. Разве что в качестве якоря выбирается четвёртый элемент, а не третий, так как третий изменил положение в DOM, то есть появился в данном случае.

    Виртуализация: Расширение

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

    Мы просто добавляем контент и браузер снова смещает скролл так, чтобы контент, что был изначально виден, остался на месте.

    Виртуализация: Превышение

    Возможна и обратная ситуация: контент уже полностью накрывает видимую область с запасом. Можно было бы удалить невидимые элементы, но лучше не трогать DOM лишний раз. Пусть лучше пользователь мотает скроллом туда-сюда без задержек на обновление DOM пока не появится дырка с одной из сторон - тогда имеет смысл удалить лишнее с противоположной стороны. И как следствие DOM не будет неограниченно разрастаться.

    Виртуализация: Скачок кенгуру

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

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

    Привязка скролла в действии

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

    https://nin-jin.github.io/my_gitlab/

    Привязка скролла: Поддержка

    Однако, есть проблема с интернет эксплорером нашего времени, который всё ещё не поддерживает привязку с скролла.

    Браузер

    overflow-anchor

    Chrome

    Firefox

    Safari

    Привязка скролла: Запасный выход

    Для отсталых браузеров необходимо детектировать поддержку привязки скролла и, если её нет, то фолбечиться до ленивого рендера, то есть менять DOM лишь ниже видимой области, но не выше.

    const anchoring_support = CSS.supports( 'overflow-anchor:auto' )
    
    
    if( anchoring_support ) {
        virtual render
    } else {
        lazy render
    }

    Проблема: Долгая раскладка

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

    https://nin-jin.github.io/habrcomment/#article=423889

    Минимизация расчётов лейаута

    Так как мы всё время меняем DOM внутри скроллящейся области, то это приводит к тому, что браузеру приходится постоянно пересчитывать лейаут, стили и дерево слоёв всего документа. Поэтому для скроллящихся областей имеет смысл задать следующее CSS свойство..

    [mol_scroll] {
        contain: content;
    }
    

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

    Прокрутка в отдельном потоке

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

    [mol_scroll] > * {
        transform: translateZ(0);
    }

    На глаз отличить синхронный скролл от асинхронного не сложно: синхронный подлагивает, но никогда не показывает дырок, а вот асинхронный плавный, но может показывать дырки, когда мы не успеваем дорендеривать контент.

    Плавная прокрутка (или нет)

    Применив все оптимизации мы получаем плавную прокрутку..

    https://nin-jin.github.io/habrcomment/#article=423889

    ..пока используем палец или автопрокрутку. Но стоит нам взяться за колёсико или тачпад, то оказывается, что привязка скролла порой не работает. Оказывается, что если открыть приложение во фрейме другого приложения, которое расположено на другом домене, то привязка скролла в Хроме перестаёт работать. Если для вас этот кейс имеет значение, то стоит его детектировать и фолбечиться до ленивого рендера.

    Логика поиска

    Скорость и отзывчивость - это, конечно, хорошо, но что насчёт поиска по странице? Он ведь ищет лишь по тому тексту, что есть в DOM, а мы тут рендерим лишь малую его часть. Делать нечего - надо перехватывать Ctrl+F, рисовать свой интерфейс поиска и искать самостоятельно. Для этого компоненты должны реализовывать метод, которому скармливаешь предикат, а он эмитит найденные пути от корня до компонент, соответствующих предикату.

    *find_path(
        check: ( view : View )=> boolean,
        path = [] as View[],
    ): Generator&lt; View[] > {
    
        path = [ ... path, this ]
    
        if( check( view ) ) return yield path
    
        for( const item of this.kids ) {
            yield* item.find_path( check, path )
        }
    
    }
    
    • Рекурсивно спускаемся по компонентам.

    • Отбираем соответствующие запросу.

    • Рисуем интерфейс перехода между найденным.

    Логика прокрутки к компоненту

    К сожалению, мало просто найти компоненты, нужен ещё и механизм прокрутки страницы к найденным компонентам. Для этого компонентам нужен ещё один метод, который находит путь к компоненту через дерево компонент, форсирует рендер компонент по этому пути, чтобы они оказались в DOM, после чего уже вызывает браузерный API прокрутки.

    scroll_to_view( view: View ) {
    
        const path = this.find_path( v => v === view ).next().value
    
        this.force_render( new Set( path ) )
    
        view.dom_node.scrollIntoView()
    
    }

    Логика форсирование рендеринга видимого

    Форсирование рендера для каждого типа компонент идёт по своему. Например, для вертикального списка оно выглядит так: ищем элемент списка, который должен быть отрендерен, после чего указываем, что отрендериться должен только он.

    force_render( path : Set< View > ): number {
    
        const items = this.rows
    
        const index = items.findIndex( item => path.has( item ) )
    
        if( index >= 0 ) {
            this.visible_range = [ index, index + 1 ]
            items[ index ].force_render( path )
        }
    
        return index
    }

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

    Работающий поиск

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

    https://nin-jin.github.io/habrcomment/#article=423889/search=vin

    Подсветка найденного реализуется опять же на уровне компонент. Я для этого просто использую компонент $mol_dimmer, которому скармливаешь строку текста, а он сам уже заботится о поддержке поиска и подсветки найденного.

    Печать

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

    @media (print) {
        [mol_scroll] {
        overflow: visible;
            contain: none;
            max-height: unset;
        }
    }

    Кроме того, нужно выключить всю виртуализацию.

    if( $mol_print.active() ) {
        // render all
    } else {
        // partial render
    }

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

    Доступность

    Но рано ещё выдыхать, надо подумать и о пользователях скрин ридеров. Дело в том, что читалки читают текст последовательно по строчкам. И читая очередную строку они фокусируют браузер именно на ней. Проблема может оказаться на последней видимой строке. Если следующая строка не видима? и мы её не отрендерим, то читалка просто скажет, что страница закончилась и остановится. Но если мы отрендерим чуть больше, то читалка сфокусирует браузер на последней отрендеренной строке, что приведёт к скроллингу так, чтобы она стала видимой, но тут отработает виртуализация и снизу отрендерится ещё одна строка. Таким образом даже слепые смогут потреблять ваш контент виртуализированно без особых проблем.

    https://nin-jin.github.io/my_gitlab/

    Можете сами попробовать, просто включив NVDA и закрыв глаза.

    Решаемые проблемы виртуализации

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

    • Оценка будущих размеров.

    • Скачки контента.

    • Тормоза при скроллинге.

    • Прокрутка к элементу.

    • Поиск по странице.

    • Доступность.

    Фундаментальные особенности

    Тем не менее у нас остался и ряд фундаментальных особенностей, с которыми придётся смириться..

    • Скачки скроллбара при неточной оценке размеров.

    • Scroll Anchoring может не работать в некоторых контекстах.

    • Копирование выделенного текста не работает.

    Бенчмарки: Скорость открытия и Отзывчивость

    Ладно, давайте погоняем бенчмарки. Понятное дело, что на огромных страницах виртуализация победит. Поэтому возьмём, что-то более типичное - небольшую мобильную страницу Хабра со 170 комментариями и откроем её на не самом слабом ноуте и жамкнем "показать 170". Таймлайн сверху показывает, что на формирование DOM через VueJS требуется три с половиной секунды, а потом ещё пол секунды требуется браузеру, чтобы всё это показать.

    Снизу же вы видите таймлайн открытия реализации этой страницы на $mol с виртуализацией. Как видно, треть секунду ушла на отображения статьи, ещё треть потребовалось браузеру, чтобы её показать, потом пришли данные комментариев и ещё за треть секунды они были обработаны: сформировано дерево компонент, вычислены минимальные размеры и тд. Но благодаря виртуализации DOM почти не поменялся, поэтому браузеру ничего не стоило это обработать.

    Итого: ускорение открытия не менее чем в 4 раза даже на сравнительно небольшом объёме данных.

    Бенчмарки: Отзывчивость

    Можем погонять и какие-нибудь синтетические бенчмарки. Например, dbmon.

    https://mol.js.org/perf/dbmon/-/

    Пока все реализации топчутся у меня вкруг 20 фпс, наивная реализация на $mol со встроенной виртуализацией показывает стабильные 60.

    Бенчмарки: Потребление памяти

    Нельзя забывать и про потребление оперативной памяти. Та реализация Хабра на VueJS на 170 комментариях отжирает 40 мегабайт хипа JS. Но если посмотреть понять вкладки, то это будет уже в 3 раза больше, так как самому браузеру нужно весь этот дом показывать. Если же открыть реализацию на $mol, где выводится статья, да ещё и две с половиной тысячи комментариев к ней, то мы получаем те же 40 мегабайт JS хипа. Но вкладка при этом кушает в два раза меньше, ибо браузеру показывать всего ничего - меньше тысячи DOM элементов.

    Вариант

    Память JS

    Память вкладки

    VueJS: 170 комментариев

    40 MB

    150 MB

    $mol: статья + 2500 комментариев

    40 MB

    90 MB

    И суть тут не в том, что $mol такой легковесный и быстрый, а в том, что в нём применена архитектурная оптимизация, дающая профита куда больше, чем обычная локальная оптимизация в бутылочном горлышке. Тут мы не ускорили формирование DOM, а сделали так, что скорость изменения DOM нас вообще перестала волновать. Ведь виртуализация позволяет рендерить на порядок больше данных, потребляя при этом в несколько раз меньше ресурсов.

    Бенчмарки: Гулять так гулять!

    Ну и, наконец, давайте сделаем немыслимое - загрузим разом 25 приложений..

    https://showcase.hyoo.ru/

    Некоторые из них отображают весьма не маленькие объёмы данных. Начиная ото всех существующих material design иконок. Заканчивая всеми продающимися сейчас типами лампочек. На моём ноуте всё это открывается за 6 секунд. Напомню, что одна только гитлабовская страница из начала моего выступления открывалась в 3 раза дольше. Почувствуйте, как говорится, разницу между тем, что веб представляет из себя сейчас, и каким он мог бы быть, если бы мы думали не только о том, как удовлетворить свои привычки, но и о том, какие привычки полезны.

    ООП против ФП

    Короче, виртуализация - классная тема. И давайте посмотрим какие у нас есть перспективы, что она появится в ныне популярных инструментах. Но сперва определимся с вопросом чем объект отличается от функции..

    • Объект: одно состояние - много действий.

    • Функция: много состояний - одно действие.

    Условный Angular использует концепцию объектов: каждый компонент - объект умеющий много разных действий. А вот в React популярна тема функциональных компонент - тут компонент имеет лишь одно действие - отрендерить своё содержимое в виртуальное дерево.

    Ортогональные действия

    Однако, при виртуализации, нам необходимо, чтобы каждый компонент умел делать разные дела..

    • Узнать минимальные размеры без полного рендера.

    • Частично отрендерить содержимое.

    • Проверить соответствие поисковому запросу.

    То есть объектная парадигма подходит для этого гораздо лучше, чем функциональная.

    Композиция против вёрстки

    Также стоит упомянуть и различные подходы к композиции компонент. Первый вариант - наиболее естественный - это прямая сборка интерфейса из компонент. Тут рендерер может спросить мол "колонка, вот какие у тебя минимальные размеры?" и колонка сможет их правильно вычислить.

    Column
        Row
            Search
            Icon
        Scroll
            Column
                Task
                Task
                Task

    Второй подход - более популярный - это оживление готовой HTML вёрстки. Тут фактический лейаут зависит от стилей, что применены к html элементам. Как-то проанализировать это, наверно, можно, но это весьма сложно, крайне медленно и не очень надёжно. А в вебе и так хватает точек отказа.

    <main class="panel">
        <div class="header">
            <input class="search">
            <img src="..." class="icon">
        </div>
        <div class="scroll">
            <div class="card">
            <div class="card">
            <div class="card">
        </div>
    </main>

    Перспективы во фреймворках

    Анализируя популярные инструменты, можно заметить, что React, например идёт вообще куда-то совсем не в ту сторону. Так что к нему прикрутить виртуализацию если и можно, то крайне сложно. И скорее всего не станут.

    Чуть ближе к виртуализации React Native, где нет никакого сырого HTML и всё строится из компонент. Но подражание html тут как собаке пятая нога.

    К Angular, Vue, Svelte прикрутить виртуализацию скорее всего будет проще, ибо там каждый компонент - это некоторый объект. Правда ориентация на вёрстку, вместо компонент, существенно осложняет внедрение виртуализации на уровне фреймворка, а не прикладного кода.

    В $mol же виртуализация работает уже давно, так что он опять всех задоминировал.

    Инструмент

    ООП

    КОП

    React

    React Native

    Vue

    Angular

    Svelte

    $mol

    Выбери виртуализацию

    Но я не предлагаю вам использовать $mol. Потому что, ну знаете, "использовать" - это какое-то потребительское отношение. Я вам предлагаю присоединиться к его разработке и тем самым получить максимальный профит.

    Ну либо вы можете разработать свой какой-то инструмент, основанный на виртуализации. В любом случае, какой бы вариант вы ни выбрали, обращайтесь к нам, мы поможем чем сможем.

    Ссылочки

    Обратная связь

    Превосходно: 34%

    • Вроде все знакомо, но нашёл для себя пару интересных нюансов.

    • Узнал кое что новое.

    • Все ок.

    • По моему мощнейший доклад, очень круто и интересно. Один момент. У Дмитрия сквозь доклад использовалась шутка: недостатки преподносились как преимущества (в духе контент прыгает - пользователь тренируется концентрировать внимание). Она удачная и смешная (правда так считаю, не из вежливости это пишу), но ее было слишком много. Например, был слайд, где было сразу много "преимуществ", и Дмитрий проговаривал по шутке на каждый пункт. Ломало темп. Сначала весело, а потом уже как-то хочется чтобы доклад дальше двигался. Но это минорное замечание, доклад ?

    • Изначально я слышал про $mol из комментариев на Хабре, многие из которых были неуместны / подавались в странной манере. В докладе я увидел, что автор - "видел некоторое дерьмо" (простите за мем) - понимает причины - предлагает здравые рассуждения по тому, как это чинить. Совершенно точно посмотрю на эту библиотеку, чтобы увидеть воплощение принципов из доклада на практике. Но даже если не буду использовать эту библиотеку в проде, разочарован не буду - настолько ценной и качественной я считаю информацию в этом докладе.

    • Подача, глубина доклада, практическая применимость, простота восприятия, новизна.

    • На удивление было мало $mol и много полезных вещей)

    Хорошо: 42%

    • Интересный доклад, но к сожалению не все было понятно, начиналось вроде все просто, но потом быстро вышло за моё понимание).

    • Карловский как всегда жжёт.

    • Очень круто и глубоко, но не очень понравилась подача. Хочется побольше "огонька".

    Нормально: 18%

    • Слишком узкая специализация

    • Что смотрел помню, а про что нет.

    • Доклад на мой взгляд получился последовательным, но не очень сбалансированным с точки зрения теории и практики. Тема доклада очень общая, а по итогу практическую пользу можно получить судя по всему, если использовать конкретный фреймворк. Из плюсов - понравился разбор проблемы на примере GitLab.

    Плохо: 6%

    • Интересный материал, интересная тема, большой потенциал технологии. Не понравился спикер и его отношения к другим технологиям, людям.

    • Оценка поставлена из-за несоответствия моих ожиданий и реальности =) Ожидания: я смогу применить полученные знания на своём проекте. Реальность: демонстрация своего фреймворка. Для проектов на любом другом фреймворке полученная информация неприменима. Если бы это было понятно из названия и описания, я бы не тратила время и пошла на другой доклад.

    Хабр такой Хабр

    Комментарии 55

      +1
      Ну очевидно, что уже и гугль сам оптимизировать не может — последние полгода, если не больше, страница рекомендаций youtube, та что содержит одни только превью роликов, тормозит нещадно, пролистал страниц пять — и уже все, меню под роликами уже не успевают рендериться со скоростью листания.
        +1
        Надеюсь, когда-нибудь поддержка virtual-scroller или чего-то подобного всё-таки появится в браузерах. Тогда заживём.
          0

          Не появится, эту инициативу уже завернули.

            +2
            Да я видел, но что-то похожее должно появиться. Сейчас каждый пилит свой велосипед для виртуализации списков — тысячи реализаций одного и того же. Должно появиться какое-то общее решение с поддержкой во всех браузерах.
              0

              Так оно и есть — content-visibility
              Но это опять не pull семантика, а push, которую толком не оптимизируешь.
              pull разве что может через houdini будет и то через костыли.

        +1
        А почему «Копирование выделенного текста не работает.»?
          +1

          Копируется только то, что находится в доме, а в доме у нас не всё.

          +2

          Старо-новогодний сюрприз. Ура! Есть чтиво на ночь. Аплодирую! Если у Дмитрия врождённое системное мышление и стремление к автоматизации ручного труда, то
          его можно удалить только один раз. Как аппендикс:)

            +2
            Важно, чтобы обработчик вызывался самым первым, чтобы никто не успел до него внести изменения в DOM. Тогда размеры и координаты элементов будут браться максимально быстро — из кеша. Поэтому, следующим шагом мы пробегаемся по всем интересным нам элементам и получаем их габариты.

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

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

            Возможно я где-то не прав.
              +2

              Да, всё так. Вся работа с домом по хорошему должна вестись в две фазы:


              • чтение
              • изменение

              К сожалению, нет стандартного механизма разделения этих фаз и приходится изобретать что-то своё.

              +2
              https://nin-jin.github.io/my_gitlab/

              Если успеть прокутить скрулбар несколько раз, до загрузки контента, то после загрузки он «прыгает».
                0

                Реализация ещё не идеальна, да.

                +1

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

                  0

                  Потому что нанимают "Senior React Developer" по объявлениям.

                    0

                    о, видите Дмитрий. вот тут ваша компетенция заканчивается и начинается ресёрч на тему ведения бизнеса) что бизнес считает проблемой, а что не считает. чтобы уметь смотреть на вещи не только глазами разработчика, а и предпринимателя, и дизайнера и т.д.

                      +4

                      Ну да, и в РЖД кто угодно может влезть в сеть за 20 минут потому, что там умеют смотреть глазами предпринимателя, дизайнера и тд.

                        +1
                        а проблема ли это и для кого? и как так вышло? вот что также важно исследовать. то есть происследовать откуда ноги растут. А не сразу следствие бежать решать.
                            0
                            Да, так вот где проблемы Дмитрий) А мы видим уже следствия в виде 2к DOM-элеметов на странице.

                            «Надо мной будут смеяться, но я — человек смелый, поэтому скажу: нужна разделяемая и обществом и государством… идеология. Любая. Ещё десять лет духовной нищеты и буду согласен даже на монархию… Только честную, пожалуйста. Без бутафорий. Дуэли тоже верните. И всем получившим высшее образование — личное дворянство автоматически.

                            После формирования идеологии формируется нечто большее чем жалкая „теория игр“, в рамках которой „живи сам и дай жить другому“. Появляется возможность выращивания не только профессионального этикета, но и высших (т.е. вне зависимости от мнения начальника) норм поведения.»
                      +4
                      Потому что нанимают "Senior React Developer" по объявлениям.

                      Жосткий троллинг 6+

                      Ну это потому, что нет объявлений где нанимают на позицию "Senior Mol Developer"

                    0
                    Помимо прямого сокращения количества DOM-элементов, отображаемых единовременно, можно пользоваться старыми добрыми таблицами для вывода данных (display: table-cell и т.д.) — таблицы гораздо менее затратны в плане сложности вычислений для браузера чем обычные блоки, и рендерятся существенно быстрее. Что, в сочетании с Intersection Observer, открывает интересные возможности по оптимизации. И да, css-таблица (в отличие от html) еще хороша тем, что в любой момент может перестать ей быть.
                      +4
                      таблицы гораздо менее затратны в плане сложности вычислений для браузера чем обычные блоки

                      Нет конечно. Только с table-layout: fixed, что превращает таблицу в набор колонок фиксированной ширины (его прекрасно можно сделать и другими способами, сейчас не 2001 год).
                      Таблицы с авто-лейаутом не "менее затратны", а строго наоборот, т.к. чисто по спецификации рендер каждой новой строки может начать заново рендерить всю часть таблицы до этой строки.

                        0

                        Надо, кстати, сравнить мою реализацию флоу таблиц с нативной. Моя делает это за 4 фиксированных фазы, без пересчётов сначала. Флоу обычных параграфов мне удалось сделать в 2 раза быстрее нативного.

                          +1
                          Хорошо, что это легко можно взять и самому проверить. Я вот пошел (в последних версиях хрома не сравнивал) и проверил. И да, если вывод условной строки подразумевает какой-то локальный лейаут — таблицы быстрее всего. Хотя раньше, по моему, разница была больше… А вот flex и grid — заметно медленнее даже чем inline-block.
                            0

                            Я бы не назвал flexbox "обычными блоками", с которыми вы сравнили таблицы. Флекс действительно небыстр, когда надо выводить очень много контента, но при большой сложности одного элемента расходы на то, чтоб уложить 10+К таких элементов флексом — перестают иметь какое-либо значение, узкое место не там.

                              0

                              Я сравнивал со всем, а с флексом и гридом — дополнительно. Прежде чем умничать, сравнили бы сами.

                                0

                                Может приведёте примеры кода, на которых сравнивали?

                        +1

                        Не знаю может я что-то делаю не так ну у меня в моём приложении чата 10000 сообщений чата рендерятся за пару секунд и довольно плавно скролятся

                          0

                          А вы попробуйте открыть его на мобилке.

                          +2
                          nin-jin.github.io/my_gitlab
                          Печать сломана, сохранение в файл сломано, поиск слова не подсвечивает
                            0

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

                          +1

                          В статье с таким универсальным названием ничего не написано про лейаут кастомных элементов при ресайзе страницы. Наверное с этим все хорошо. В смысле красоты:)

                            0

                            Горизонтальный список с переносами — это как раз про адаптацию к размерам контейнера.

                            +5

                            Уже прочитав заголовок я снова задался вопросом — хочу ли я, чтобы на хабре был virtual scroll в комментариях. И быстро пришёл к выводу — НЕТ. Потому что его сложно реализовать хорошо. Я занимался такими задачами и хорошо помню чего это стоило. Выводы и грабли на которые я наступил — примерно как в статье. Никогда не понимал почему nin-jin так топит за virt. scroll, учитывая сколько там подводных камней и какой поганый в итоге получается результат. У всех, не только у nin-jin


                            В первую очередь подумал о поиске. Больная тема. Сразу много нюансов. К тому же одна из самых важных для опытных пользователей. Открыл демку. Попробовал поскролить. Выделил произвольный текст. Вбил в поиск. Увидел что система вместо того, чтобы обеспечить около-нативное поведение зачем-то отрезала мне кусок от комментариев и скрыла всё остальное. Даже дочерний комментарий уже посмотреть нельзя. Ну думаю, ладно, может сейчас я кликну на нужный комментарий и снова вернусь в общее древо. Ан нет. Не могу. Ладно, не беда — нажму "крестик" в поиске и останусь на той же позиции. Ан нет. Не остался.


                            Ну и зачем такие комментарии? Только потому, что они не тормозят? Я подожду уж эти 10-15 секунд. Да неприятно. Но в результате я могу пользоваться страницей. А в этой демке — не могу.


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


                            Пока остаюсь при старом мнении — virt. scroll это всегда компромисс. Очень больной компромисс. Не стоит использоваться его без большой нужды. Для этого нужны серьёзные обстоятельства.

                              +1

                              P.S. понравилось название "кенгуру". Я это называл "crude scroll". По сути сколлинг был двух видов:


                              • soft: когда просто рендерим всё честно, всё что проскроллили и с небольшим резервом. После рендера кадра — образаем лишнее. Получается плавный и приятный опыт
                              • crude: когда угадываем где примерно оказался бы пользователь при честном скролле и рисуем кадр сразу оттуда. Насколько хорошо мы угадаем зависит от того насколько хорошо были подсчитаны размеры. А это целая наука :)
                                0
                                система вместо того, чтобы обеспечить около-нативное поведение зачем-то отрезала мне кусок от комментариев и скрыла всё остальное

                                Задача не "околонативное" поведение обеспечить, а помочь пользователю найти то, что он ищет. Один из вариантов — скрытие нерелевантного, что и было реализовано в этой демке.


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

                                Можно. Слева от сообщения есть экспандер.


                                не беда — нажму "крестик" в поиске и останусь на той же позиции. Ан нет. Не остался.

                                Это, как раз, из-за того, что я DOM меняю и привязка скролла подавляется. Отключил фильтрацию, теперь такого не происходит.


                                Ну и зачем такие комментарии? Только потому, что они не тормозят? Я подожду уж эти 10-15 секунд. Да неприятно. Но в результате я могу пользоваться страницей. А в этой демке — не могу.

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

                                  +1

                                  Проблем там куда больше чем описанная выше. Просто поиск у меня больная тема. Вот сейчас ещё раз открыл вижу что вы там кастомный скролл реализовали. Это тоже одна из таких вещей, который в здравом рассудке делать не стоит. Потому что всегда получится плохо. Вот и на этот раз. На кой чёрт он такой крошечный? Снайперов готовить? А ведь очень просто оставить обыкновенный, нативный для платформы, куда более продуманный. Я так понимаю вы скроллбаром не пользуетесь, и поэтому он такой… маковский. Apple кажется scrollbar-ы вообще не признаёт (к счастью его ещё можно включать в настройках).


                                  Но вернёмся к поиску. При больших объёмах данных это очень важный инструмент. Как я писал выше, я бы предпочёл чтобы virt. scroll-а не было вовсе, чем когда он есть, но с ним нет хорошего поиска. Я тысячи раз пользовался им в комментах, пытаясь найти нужную позицию. Это к я тому что изъян отнюдь не маленький. Если бы я был заказчиком я бы запросил удалить всю эту самодеятельность. Вы назвали её "скрытие нерелевантного". При том что по сути скрыли релевантное. Человек когда ищет комментарий — очень часто ищет его как якорь. Вроде: "это был рядом с комментарием про майонез".


                                  После исправления стало сильно лучше. Теперь поиском пользоваться, хотя и немного неприятно, т.к. очень ненативное поведение, но можно. Теперь он стал решать поставленные перед ним проблемы. Из мелочей: Подсветка найденного на скроллбаре отсутствует. Scroll прыгает к искомому не плавно (по понятным причинам). F3 не работает. Отступа между искомым и краем viewport-а нет. Режет глаз. Выделение искомого, имхо, опять же очень странное. Браузер найденное выделяет жёлтым фоном (кажется он ещё и в контрастность умеет). У вас это непривычный розовый текст. Браузер умеет искать по одной букве. Поисковая строка в браузере "не прыгает" пока в неё вбиваешь буквы. И не тормозит так сильно. При этом поиск происходит сразу, а не по Enter. Ну и т.д.


                                  Стоит отметить кое в чём такой поиск лучше хромовского — он не "нормализует" букву "ё" в букву "е" :) Кстати, вы учли нормализацию при поиске? Там есть свои отдельные подводные камни.


                                  Почти всё из вышеописанного (всё, кроме наверное F3) можно исправить. Но это надо поприседать. В частности выделение на скроллбаре — не самая тривиальная штука. Отсюда мой изначальный довод в силе: virt. scroll это почти всегда очень большой компромисс, т.к. он имеет очень много недостатков. И его не только не нужно делать по-умолчанию (бедные пользователи), а вообще стоит применять только там где без этого ну никак. Ну либо когда вы готовы сражаться до последнего и всё равно дать немного посредственный результат. Когда я делал нечто подобное (у меня правда задача была сложнее из-за огромных таблиц с чудовищными rowSpan-ми), я хорошо понимал зачем я это делаю: была нужда редактировать 900+ A4 страничные законы\бюджеты\whatever. Там вопрос стоит между: не делать вовсе, либо делать с virt.scroll-ом. Выбора нет.


                                  Здесь же ситуация спорная. На мобильниках virt.scroll скорее нужен, чем нет. Но на мой взгляд там придётся решать ещё 1 проблему: вложенные комментарии через большие отступы абсолютно неюзабельны в вертикальной раскладке. Там нужен вообще другой интерфейс. А на десктопе получается странная штука, которую надо полировать и UX всё равно будет совсем ненативным. И не факт что по итогу не полезут браузерные проблемы :D


                                  P.S. "который никто больше не заметил" — это очень печальный факт.

                                    0
                                    вижу что вы там кастомный скролл реализовали

                                    Это нативный скролл с изменёнными стилями скроллбара.


                                    Кстати, вы учли нормализацию при поиске?

                                    Неа, что ввели — то и ищется.


                                    кроме наверное F3

                                    Это, как раз, просто. Уже добавил.


                                    В частности выделение на скроллбаре — не самая тривиальная штука.

                                    Да вроде не особо сложная. Список найденного есть. Осталось получить оценки их позиций и нарисовать линейку. Впрочем, штука не очень полезная.


                                    Отступа между искомым и краем viewport-а нет.

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


                                    Браузер найденное выделяет жёлтым фоном (кажется он ещё и в контрастность умеет). У вас это непривычный розовый текст.

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


                                    Браузер умеет искать по одной букве.

                                    Нет никакой проблемы искать по одной букве, но и смысла тоже нет.


                                    Поисковая строка в браузере "не прыгает" пока в неё вбиваешь буквы.

                                    Зато закрывает часть страницы. Вообще, странно сравнивать демку, интерфейс которой сделан за 5 минут, с продакшен реализацией в браузере.


                                    И не тормозит так сильно.

                                    Ввожу "пр". Моя реализация выдаёт результат через пару секунд. Нативная прыгает секунд 10.

                                      +1
                                      Ввожу "пр". Моя реализация выдаёт результат через пару секунд. Нативная прыгает секунд 10.

                                      Проверил. Да, ваш сильно тормозит, но суммарно работает куда быстрее. Стандартный не тормозит совсем, но индексирует очень долго. Полагаю не помешает async поиск. Получится почти также быстро, но ещё и отзывчиво.


                                      Розовый — это акцентный цвет в светлой теме по умолчанию сейчас, но я думаю его поменять на более яркий

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


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

                                      Да нет же. Оно так систематично работает. Пиксель в пиксель к рамке. Но да, это конечно мелочь. Этой штуки кстати сильно не хватает и в стандартном барузерном API вроде плавного скролла. Плюс браузер скроллит к искомому тексту, а не в комментарию (хотя тут уже дискуссионно что лучше). И браузер не скроллит кверху, если элемент весь помещается в экран.


                                      Впрочем, штука не очень полезная.

                                      Имею противоложное мнение. Возможно дело привычки. Я привык по этим вещам прямо ориентироваться. Где угодно. Хоть в редакторе, хоть в браузере. К примеру в vsCode я люблю file map-ы вместо скроллов. Очень упрощает многие вещи.


                                      F3 Это, как раз, просто. Уже добавил.

                                      Теперь по F3 фокус попадает в поисковую строку. А зачем? Или это такое поведение на Mac? В Linux/Win по F3 "find next". Т.е. скролл к следующему элементу. Вот кстати ещё 1 грабля на которую можно наступить — разные комбинации клавиш. Я думаю если сильно вникать то типичный UX состоит из огромного количества мелочей.


                                      Ещё не хватает индикатора сколько всего найдено. Непонятно какое выделение из найденных текущее. Браузер имеет 2 стиля для этого.


                                      Это нативный скролл с изменёнными стилями скроллбара.

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


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


                                      Я думаю чем дольше копать тем больше мелочей выплывет. Слишком уж много UI тонкостей в современных UI.

                                        0
                                        Полагаю не помешает async поиск. Получится почти также быстро, но ещё и отзывчиво.

                                        Для этого нужна многопоточность, которую достаточно сложно прикрутить. Тут разве что квантование может помочь. Но тогда придётся реактивную логику поменять на интерактивную.


                                        К примеру в vsCode я люблю file map-ы вместо скроллов. Очень упрощает многие вещи.

                                        Какие?


                                        В Linux/Win по F3 "find next". Т.е. скролл к следующему элементу.

                                        Проще перемещаться по Enter/Shift+Enter.


                                        Непонятно какое выделение из найденных текущее. Браузер имеет 2 стиля для этого.

                                        Агась. Чтобы это поддержать, нужно будет сделать компонент-обёртку надо всем приложением. Чтобы компонентам была доступна ручка "текущий найденный компонент". Туда же можно будет вынести и хоткеи с кастомными линейками. Но это как-нибудь потом.


                                        не стоит менять браузерные стили скролла без острой нужды, если вы не враг своему пользователю

                                        Нативные скроллбары выглядят препаршиво.


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

                                        Из-за чего, чтобы поискать сначала приходится сначала отскроллить на самый верху, кликнуть в свободное место и только тогда начинать поиск. Такое себе поведение.

                                          +1
                                          Какие?

                                          В первую очередь сложные манипуляции с текстом со множественными курсорами. По карте файла видно где и какого рода есть выделения. А так как она ещё и цветная то видно примерно структуру кода.


                                          Нативные скроллбары выглядят препаршиво.

                                          Это проблема ОС, не наша. Функциональность должна быть выше потребностей дизайна.


                                          Проще перемещаться по Enter/Shift+Enter.

                                          Это если вы никак не взаимодействуете со страницей. Т.е. как только Input теряет фокус Enter/Shift+Enter перестают быть полезными. Просто элементарно что-нибудь скопировать в буфер обмена. Или просто привычка выделять что читаешь (у меня к примеру есть такая дурацкая привычка).


                                          Из-за чего, чтобы поискать сначала приходится сначала отскроллить на самый верху, кликнуть в свободное место и только тогда начинать поиск. Такое себе поведение.

                                          Ну выбирая между: "искать всегда с нуля" и "когда всё таки надо искать с нуля — вначале кликнуть на стрелку "вверх"" я выберу второе. По моему опыту поиск с текущей позиции очень желаемое поведение. Если скажем вам в редакторе сделать такой поиск (всегда с нуля) как быстро вы пойдёте писать issue разработчикам редактора? Я минут через 5 :-D

                                            0
                                            Это проблема ОС, не наша. Функциональность должна быть выше потребностей дизайна.

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


                                            Т.е. как только Input теряет фокус Enter/Shift+Enter перестают быть полезными.

                                            Жмёте F3 и оно снова в фокусе.


                                            По моему опыту поиск с текущей позиции очень желаемое поведение.

                                            Лично мне поиск с текущей позиции нужен примерно никогда.

                                              0
                                              а на мобилках нативные скроллбары вообще невидимые

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


                                              Жмёте F3 и оно снова в фокусе.

                                              Т.е. F3 + Enter. И не дай бог вы проскроллили с прошлого раза. Это не учтётся.


                                              Лично мне поиск с текущей позиции нужен примерно никогда.

                                              У меня сложилось впечатление что вам вообще не нужен поиск. И скроллбар. И наверное многое другое. А страдать пользователям. Потому что программист решил что так будет лучше. Предпочтения отдельно взятого разработчика становятся выше того, как у пользователя обычно всё работает. То к чему он привык. Ну такое.


                                              Мне кажется с таким подходом лучше заниматься backend разработкой. Без обид… Но ведь правда.

                                                0
                                                Тут же они оформлены аккуратной полосой, не бросающейся в глаза.

                                                В эпоху флешплейера, заказчики часто заказывали кастомные скроллбары под оригинальный фирстайл. Чтобы им дизайном как то выделяться из массовки. Все унифицируется. И это плохо, т.к не различимо. Можно конечно пенять на технологии. Допустим в часах. Корпуса "а-ля тунец" стали использовать повсеместно и теперь без шильдика вы фирму не отличите "от китайца". Раньше это были только калибры. Но их извне не было видно.

                                          +1
                                          Неа, что ввели — то и ищется.

                                          Забыл про эту штуку. Вне демки (в реальном применении) так точно нельзя. UTF8 слишком муторная вещь. Человек тупо может не найти слово в котором какая-нибудь Ё записана через 2 code point-а. С другими языками, полагаю, граблей ещё больше.


                                          В общем я не хочу сильно критиковать демку. Демка на то и демка. Спасибо за неё. И за статью. Но по ней, хорошо видно, насколько это неблагодарная работа делать virt. scroll-ы. Насколько много велосипедов нужно переизобретать или просто игнорировать. Многие вещи кажутся очень простыми при первом приближении. Но по факту являются сложными.

                                            0
                                            Человек тупо может не найти слово в котором какая-нибудь Ё записана через 2 code point-а.

                                            А если оно ещё и с опечаткой написано, то вообще туши свет..


                                            Многие вещи кажутся очень простыми при первом приближении. Но по факту являются сложными.

                                            Сложными они получаются при попытке повторить все нюансы натива. Особенно в свете разного нативного поведения на разных платформах. Если думать о потребностях пользователя всё становится куда проще.

                                              +1
                                              А если оно ещё и с опечаткой написано, то вообще туши свет..

                                              Шутки шутками, но кто-то тут писал что Mac многие годы самую обыкновенную Ё писал через 2 code-point-а. И это создавал массу сложностей людям. А в UTF куда больше проблем, чем только вариативность записи буквы. Там даже uppercase и lowercase проблемные. Нормализация must have.


                                              Если думать о потребностях пользователя всё становится куда проще.

                                              Ну вот ваша демка яркий пример того, что оно и даром такое не нужно. Тормоза стерпеть ещё можно, а многочисленные нарушения UX только тогда, когда по-другому ну никак.


                                              Причём обратите внимание, одно дело страдать с такими вот решениями тогда когда на странице пара тысяч комментариев. Но зачем все эти костыли и лишения на странице, которая и без того не тормозит? Скажем вот эта страница, на которой мы сейчас пишем. В итоге делать 2 UI? Один удобный для типовых сценариев и второй с virt-скроллингом для громоздких случаев?

                                                0
                                                Mac многие годы самую обыкновенную Ё писал через 2 code-point-а

                                                А вот это уже должно решаться на уровне ОС, а не разбрасыванием костылей в каждом месте работы со строками.


                                                Там даже uppercase и lowercase проблемные.

                                                Поиск в демке идёт через приведение к нижнему регистру так-то.


                                                Тормоза стерпеть ещё можно, а многочисленные нарушения UX только тогда, когда по-другому ну никак.

                                                Вы хотите сказать, что ждать на мобилке по 20 секунд появления очередных 3 комментариев — это терпимый UX?


                                                Но зачем все эти костыли и лишения на странице, которая и без того не тормозит? Скажем вот эта страница, на которой мы сейчас пишем. В итоге делать 2 UI?

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

                                                  0
                                                  Вы хотите сказать, что ждать на мобилке по 20 секунд появления очередных 3 комментариев — это терпимый UX?

                                                  Комментариев у хабра на мобилке можно сказать и нет вовсе. Выше я уже писал что там надо писать отдельный продуманный UI, адаптированный, малые экраны и touch-и. Там будет куда больше проблем чем только тормоза. Никаких чудес. Другая вселенная = другие UI


                                                  А вот это уже должно решаться на уровне ОС, а не разбрасыванием костылей в каждом месте работы со строками.

                                                  Что именно должно решаться на уровне ОС? Все проблемы UTF? :) Их там куда больше, чем эта вот одна. И ОС куда больше одной. И версий у ОС тоже. Не совсем понимаю с чем вы пытаетесь спорить. С тем что текст перед поиском необходимо нормализовывать (а в идеале ещё и стемить)? Ну если поиск не нужен, то можно и не делать.

                                    0
                                    Оптимизировать DOM можно и нужно всегда.

                                    Приведенный пример — вместо 8 элементов -> 3 элемента, вложенность 5 — выглядит цветочками.
                                    Ягодки в реальном мире:
                                    Битрикс — платные шаблоны Аспро, вложенность 32 и более, страница товара 5000 элементов.
                                    Вордпресс — платные шаблоны, themeforest и т.д., если попалась вложенность меньше 24, то это просто удача)))
                                    Тильда, не к ночи будэ помянута… — дичайшие простыни на любой чих.

                                    Верстайте ручками!
                                      0
                                      Однако, при виртуализации, нам необходимо, чтобы каждый компонент умел делать разные дела…
                                      — Узнать минимальные размеры без полного рендера.
                                      — Частично отрендерить содержимое.
                                      — Проверить соответствие поисковому запросу.
                                      То есть объектная парадигма подходит для этого гораздо лучше, чем функциональная.

                                      А что из этого нельзя сделать в мире React, например?
                                        0

                                        В мире Реакта всё это можно сделать лишь на прикладном уровне, но не на уровне фреймворка.

                                          0
                                          А почему это плохо и поэтому $mol лучше?

                                          ИМХО наоборот хорошо, что нет скрытой магии, но при необходимости можно накрутить довольно сложные решения.
                                            0

                                            По вашему VDOM и хуки не являются "скрытой магией"?
                                            Во фронтенде и так сейчас много сложных решений. Фронтенду сейчас крайне не хватает простоты.

                                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                      Самое читаемое