Loops: интерфейс просмотра коротких видео с вертикальным листанием

Требуется реализовать интерфейс просмотра коротких видео с вертикальным листанием, аналогичный TikTok / Instagram Reels, с поддержкой mobile и desktop.

Технические условия:
  • Использовать только vanilla JavaScript (без фреймворков)
  • Разрешены современные Web APIs (IntersectionObserver, scroll-snap и т.п.)
  • Видео для наполнения интерфейса — по ссылке
Дизайн:
  • Визуальный стиль не является ключевым фактором оценки.
  • Достаточно минимального UI
Формат сдачи:
  • Репозиторий (GitHub)
  • Короткий README с инструкцией запуска и описанием решения

Решение

Для начала стоит провести исследование и посмотреть как подобный функционал работает у других. Помимо упомянутых в задании TikTok / Instagram Reels я посмотрел на Youtube Shorts.

Пользовательское взаимодействие у сервисов немного отличается. Tiktok и Instagram при нажатии на пробел прокручивают ленту вниз, Youtube останавливает/запускает воспроизведение текущего видео.

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

Идея

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

При этом ни один из сервисов не показывает высоких показателей в метриках PageSpeed / Lighthouse особенно в разделе Performance. Буду считать нефункциональным требованием достижения как минимум зелёной зоны по всем метрикам PageSpeed

Реализация

Я сделал базовую разметку в которой есть слайдер, несколько слайдов с видео и кнопки, похожие на те, что используются в референсных сервисах. При использовании атрибутов "muted" и "autoplay" у тега video можно получить проигрывание сразу при загрузке страницы еще до инициализации джаваскрипта.

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

Основной скрипт загружается перед закрытием тега body и использует Intersection Observer API для добавления/удаления узлов в DOM в зависимости от направления прокрутки. Обработчики событий реагируют на нажатия клавиш Space, стрелки курсора (вверх/вниз), на клики по видео и кнопке "mute".

Большое количество DOM-узлов может привести к просадкам производительности. Чтобы этого избежать я реализовал простую виртуализацию. Узлы добавляются при прокрутке вниз и удаляются при прокрутке в обратном направлении. При этом узлы добавляются по одному, в то время как у референсных сервисов похожий подход добавляет сразу группу узлов (10+).

Сборка

Описана в единственном Makefile и полагается только на системные утилиты *nix и небольшое количество бинарных программ доступных в дереве исходников. Сборка протестирована на MacOS и Linux.

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

Исходный код

Доступен на GitHub, или виде архива. Инструкции по запуску и сборке доступны в README.

Демо

Реализованный интерфейс доступен по ссылке.

Протестировано на десктопных и мобильных устройствах: MacOS Chrome, MacOS Firefox, Android Chrome, Android Firefox. Apple iOS не тестировалась по причине отсутствия актуального устройства.

PageSpeed

Результаты тестов PageSpeed доступны по ссылкам: mobile, desktop

Результаты референсных сервисов:

Instagram: mobile, desktop

TikTok: mobile, desktop

YouTube: mobile, desktop

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

Оптимизации

Для уменьшения количества активых узлов DOM-дерева стоит доделать виртуализацию. Сейчас она в зачаточном виде.

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

Для предотвращения простого скачивания проигрываемых видео можно загружать их в блобы и рисовать на Canvas (как реализовано в YouTube и TikTok).

Эти и другие оптимизации возможны в связке с бэкендом.

Техдолг

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

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

Для кэширования на стороне клиента имеет смысл построить приложение вокруг Service Worker'a.