[Разработка веб-сайтов, JavaScript, Интерфейсы, Big Data, TypeScript] Автоматическая виртуализация рендеринга произвольной вёрстки

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
14-Янв-2021 23:31

Здравствуйте, меня зовут Дмитрий Карловский и я.. прибыл к вам из недалёкого будущего. Недалёкого, потому что там уже всё и все тормозят. Писец подкрался к нам незаметно: сначала перестали расти мощности компьютеров, потом пропускная способность сетей. А пользователи… они продолжали генерировать контент как не в себя. В итоге, за считанные годы 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. Зато, такую оптимизацию инструмента достатоxно реализовать один раз? и далее наслаждаться ею во всех своих приложениях не тратя дополнительное время на оптимизацию.
  • Поддерживает полтора фреймворка ❌
  • Работает само по себе ✅
Оптимизации: РезюмеТак что именно на оптимизации инструментов я и предлагаю вам сконцентрировать свои усилия. И далее мы разберём, что нужно для добавления виртуализации на уровне фреймворка.ОптимизацияСтоит того?Вёрстка❌Прикладной код❌Инструментарий✅Виртуализация браузераСамое простое, что можно сделать, - это воспользоваться браузерной виртуализацией, которая появилась сравнительно недавно. Итак, открываем гитлаб и видим как всё лагает при движении мыши и вводе текста. Теперь произносим пару волшебных слов в девтулзах вокруг стилей "файла"..
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, а сверху и снизу - распорки.
Зелёная фаза - пользователь скроллит вниз. Как видно третий блок вышел из видимой области, а снизу образовалась дырка. Но пользователь это ещ1 не видит.Синяя фаза - срабатывает обработчик requestAnimationFrame и мы обновляем DOM: удаляем третий узел и добавляем шестой. Как видно, контент уехал вверх относительно видимой области, но пользователь это ещё не видит.Красная фаза - мы отдаём управление браузеру, а тот выбирает 4 элемент в качестве якоря и отскролливает чуть-назад. Так что пользователь видит лишь добавившийся снизу шестой элемент.Виртуализация: Прокрутка вверхАналогичная ситуация и со скроллингом вверх. Разве что в качестве якоря выбирается четвёртый элемент, а не третий, так как третий изменил положение в DOM, то есть появился в данном случае.
Виртуализация: РасширениеМожет так оказаться, что контент не достигает краёв видимой области с обоих концов.
Мы просто добавляем контент и браузер снова смещает скролл так, чтобы контент, что был изначально виден, остался на месте.Виртуализация: ПревышениеВозможна и обратная ситуация: контент уже полностью накрывает видимую область с запасом. Можно было бы удалить невидимые элементы, но лучше не трогать DOM лишний раз. Пусть лучше пользователь мотает скроллом туда-сюда без задержек на обновление DOM пока не появится дырка с одной из сторон - тогда имеет смысл удалить лишнее с противоположной стороны. И как следствие DOM не будет неограниченно разрастаться.
Виртуализация: Скачок кенгуруНельзя не упомянуть особый случай, когда пользователь хватает за скроллбар тащит в другой конец документа. Если никак дополнительно не обрабатывать этот кейс, то придётся отрендерить много элементов, чтобы накрыть видимую область. При этом больше их часть будет невидима, а значит много работы проделана зря. А лишняя работа - это лишние задержки, порой недопустимо большие.
Чтобы такого не происходило нужно просекать, что все элементы вышли из видимой области, удалять их все полностью и начинать рендеринг с нуля, отталкиваясь от текущего смещения скролла. Нам нужно пропустить столько элементов, чтобы размер распорки оказался чуть меньше расстояния до верхней границы видимой области. Таким образом первый элемент гарантированно накроет верхнюю её границу. И остаётся лишь дорендерить столько элементов, чтобы накрыть ещё и нижнюю границу. Привязка скролла в действииЕсли всю эту логику мы реализуем аккуратно, тогда не будет никаких скачков, даже несмотря на то, что какие-то элементы окажутся не той высоты, что мы рассчитывали.https://nin-jin.github.io/my_gitlab/Привязка скролла: ПоддержкаОднако, есть проблема с интернет эксплорером нашего времени, который всё ещё не поддерживает привязку с скролла.Браузерoverflow-anchorChrome✅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< 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, которому скармливаешь строку текста, а он сам уже заботится о поддержке поиска и подсветки найденного.ДоступностьНо рано ещё выдыхать, надо подумать и о пользователях скрин ридеров. Дело в том, что читалки читают текст последовательно по строчкам. И читая очередную строку они фокусируют браузер именно на ней. Проблема может оказаться на последней видимой строке. Если следующая строка не видима? и мы её не отрендерим, то читалка просто скажет, что страница закончилась и остановится. Но если мы отрендерим чуть больше, то читалка сфокусирует браузер на последней отрендеренной строке, что приведёт к скроллингу так, чтобы она стала видимой, но тут отработает виртуализация и снизу отрендерится ещё одна строка. Таким образом даже слепые смогут потреблять ваш контент виртуализированно без особых проблем.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 MB150 MB$mol: статья + 2500 комментариев40 MB90 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%
  • Интересный материал, интересная тема, большой потенциал технологии. Не понравился спикер и его отношения к другим технологиям, людям.
  • Оценка поставлена из-за несоответствия моих ожиданий и реальности =) Ожидания: я смогу применить полученные знания на своём проекте. Реальность: демонстрация своего фреймворка. Для проектов на любом другом фреймворке полученная информация неприменима. Если бы это было понятно из названия и описания, я бы не тратила время и пошла на другой доклад.

===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_razrabotka_vebsajtov (Разработка веб-сайтов), #_javascript, #_interfejsy (Интерфейсы), #_big_data, #_typescript, #_virtual_scroll, #_infinite_scrolling, #_$mol, #_gitlab, #_habrahabr_usability, #_razrabotka_vebsajtov (
Разработка веб-сайтов
)
, #_javascript, #_interfejsy (
Интерфейсы
)
, #_big_data, #_typescript
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 22-Ноя 13:16
Часовой пояс: UTC + 5