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

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

−30%
Размер изображений
При том же визуальном качестве, на каждой фотографии сайта.
Один клик
Уточнение поиска
Выбор категории из подходящего набора, а не из всего каталога.
0 px
Дёрганье модальных окон
Открытие модального окна больше не сдвигает страницу.

Более быстрые изображения

Каждое изображение проходит через нас. Пользователи загружают JPEG-файл своего кухонного стола весом 4 МБ; мы меняем его размер для карточек товаров, страниц товаров и миниатюр и навсегда отдаём эти копии. Конвейер был настроен несколько лет назад и работал, но у него было две проблемы, которые мы игнорировали.

Проблема первая: мы хранили каждое изображение в том формате, в котором оно было загружено. JPEG оставался JPEG, PNG оставался PNG. Мы агрессивно уменьшали размер, но никогда не перекодировали в более компактный современный формат, оставляя примерно треть байтов каждой фотографии впустую.

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

Обе проблемы решаемы.

Один формат для каждой фотографии

Мы стандартизировали единый современный формат изображений на входе. В каком бы формате вы ни загружали, JPEG, PNG, анимированный GIF, мы перекодируем в более компактный. Для анимированных GIF мы оставляем первый кадр; фотографии товаров, не анимация, и нам лучше иметь компактный статичный кадр, чем громоздкую зацикленную картинку, о которой никто не просил.

То же визуальное качество. Примерно на 30% меньше байтов на каждое изображение. Применяется к каждой новой загрузке с первого дня спринта.

Сообщаем браузеру, что важно

Это был выигрыш покрупнее. Браузеру можно подсказать, какое изображение на странице самое важное, «главное»,, чтобы он загружал его раньше всего остального. Мы добавили эти подсказки в трёх местах:

  • Главное изображение на странице товара: наивысший приоритет. Боковые миниатюры явно ниже, чтобы не конкурировали.
  • Первый ряд карточек на главной и в поисковой ленте: высокий. Ряды ниже видимой области ждут, пока вы к ним не прокрутите.
  • Декоративные изображения (маленькие иконки категорий и т. п.): низкий, и загружаются только по запросу.
High
Low
Low
Low
Low

The hero downloads first; thumbnails wait their turn.

Один тихий баг, который мы попутно поймали

Галерея на странице товара выбирала размер фотографии исходя из того порядка, в котором сервер случайно их возвращал, а не из того, какой размер действительно был нужен. Иногда главным оказывалась 300-пиксельная миниатюра; иногда миниатюрой становилась версия в полном разрешении. Свежий показ теперь «ощущается» чётче, потому что нужный размер оказывается в нужном слоте.

Более умная фильтрация поиска

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

Filters
Search in title only
Категория
Взрослые велосипеды
(64)
Детские велосипеды
(22)
Запчасти для велосипедов
(41)
Аксессуары
(18)
Бренд
Btwin
(31)
Trek
(14)
Specialized
(9)
Cube
(7)
Цена
€120€2 400

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

Для числовых атрибутов (цена, вес, вместимость и т. п.) мы заменили старые поля «введите минимум и максимум» на ползунок, чьи границы совпадают с реальным диапазоном, присутствующим в результатах. Если самое дешёвое подходящее объявление стоит €120, ползунок начинается с €120. Больше не нужно вводить минимум €10, когда ни один результат всё равно не дешевле €120.

Старая боковая панель

Статичное дерево категорий (все узлы каталога). Пустые поля минимума/максимума для цены. Никакого понимания, в каких категориях действительно есть результаты, пока не прокликаешь их все.

Новая боковая панель

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

Когда набор результатов пуст

Ещё одна тонкость: если вы перефильтровали и получили ноль результатов, вся боковая панель исчезает. Уточнять нечего, фильтр-чипы и флажки были бы лишь визуальным шумом. Страница схлопывается до единственной кнопки «Сбросить все фильтры» по центру. Один клик, и вы снова там, где боковая панель может помочь.

Более отточенные ощущения при просмотре

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

Шапка уходит с дороги

Белая навигационная панель вверху каждой страницы теперь уезжает за пределы экрана, когда вы прокручиваете вниз, и возвращается, когда вы прокручиваете вверх, классический паттерн «шапка уходит с дороги, пока я читаю». Браузеры, которые не поддерживают лежащую в основе возможность, просто оставляют панель видимой; ничего не ломается.

Более плавные переходы карточка → товар

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

Заголовки переносятся аккуратнее

Названия товаров, заголовки карточек, заголовки разделов, ни один из них больше не оставляет одинокое слово-сироту на последней строке. Браузер балансирует переносы строк за нас. Одно объявление CSS, применённое по всему сайту, сразу видно везде, где переносится заголовок.

Больше никакого горизонтального дёрганья при открытии модальных окон

Раньше: открываешь модальное окно «поделиться», страница дёргается на 15 пикселей вправо, когда полоса прокрутки исчезает, и всё под ней сдвигается. Теперь: зарезервированный отступ держит место независимо от того, есть полоса прокрутки или нет. Модальные окна открываются без рывков страницы.

Более быстрое переключение языка

Форматирование дат на вашем языке раньше поставлялось в комплекте с данными форматирования для всех остальных поддерживаемых языков, около 100 КБ локализационных данных, хотя вам всегда был нужен лишь один. Теперь мы загружаем только тот язык, которым вы пользуетесь. Меньше бандл, быстрее первая отрисовка.

Уборка под капотом

Куча мелких правок, которые по отдельности вы бы вряд ли заметили, но которые проявляются как более чистые трассировки в наших дашбордах производительности. Ни одна из них не изменила поведение, которое вы бы увидели. Главные цифры:

−1 RTT
Рукопожатие API
Соединение прогревается до того, как уйдёт первый запрос.
idle
Инициализация аналитики
Вне критического пути; запускается в периоды простоя браузера.
less
Болтовня телеметрии
Мы урезали частоту отправки диагностических данных.
audit
Ревизия бандла
Мы составили карту тяжёлых модулей; сокращения в очереди на следующий спринт.

Что мы (пока) не выпустили

Несколько пунктов начали, но они не вошли в этот спринт:

  • Фильтрация по нескольким значениям атрибута. Интерфейс принимает несколько отмеченных значений на атрибут (можно отметить три бренда), но сегодня на стороне сервера учитывается только одно. Следующий спринт.
  • Более умный пересчёт фасетов. Сейчас при отметке «ASUS» остальные варианты брендов исчезают из списка. Отшлифованная версия пересчитывает каждый фасет исключая собственный фильтр, чтобы варианты оставались видимыми. Выполнимо; просто более дорогой путь запроса.
  • Изображения AVIF. Ещё компактнее того, что мы отдаём сегодня. Стоят большей работы кодировщика на стороне сервера; отложено, пока не проведём замеры.

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