Розробка системи сравнення товарів
Сравнення товарів — функція, яка упрощає вибір у категоріях з високою когнітивним навантаженням: електроніка, побутова техніка, автозапчастини, будівельні матеріали. Користувач додає кілька позицій і бачить їх характеристики в єдиному табличному вигляді. Реалізація здається простою, поки не зіткнешся з різнородними атрибутами, вкладеними категоріями і вимогами до продуктивності.
Архітектурні рішення
Перше питання: де зберігати список сравнення — на сервері або в браузері?
LocalStorage / SessionStorage: немає привязки до аккаунта, миттєвий запис, працює без авторизації. Обмеження — дані не переносяться між пристроями. Для більшості B2C-магазинів достатньо.
Серверна сесія (Redis): список привязаний до сесії, зберігається при переходах, синхронізується між вкладками. Потрібна авторизація або анонімний токен сесії.
БД з привязкою до користувача: постійне зберігання, історія сравнень. Надлишково для більшості задач, оправдано у B2B-кабінетах.
Оптимальна схема: LocalStorage + merge із серверним списком при авторизації (аналогічно корзині).
Модель даних атрибутів
Головна складність — атрибути різних товарів можуть не совпадати. Телевізор має «діагональ», холодильник — «об'єм камери», і обидва можуть попасти в одну таблицю сравнення, якщо користувач випадково додав різні категорії.
Схема атрибутів:
attributes (
id, name, unit, type, -- тип: numeric | text | boolean | range
category_id, -- атрибут належить категорії
comparable, -- показувати ли в сравненні
highlight_if_best -- підсвічувати ли найкраще значення
)
product_attributes (
product_id, attribute_id, value_numeric, value_text, value_boolean
)
Флаг comparable дозволяє виключити з таблиці сравнення атрибути типу «артикул» або «країна виробництва», які не дають інформації для вибору.
Логіка «найкращого значення»
Підсвітлення найкращого значення в колонці — важливий UX-деталь: користувач одразу бачить, у якого товара більше ОЗУ або менше енергоспоживання. Вимагає знання семантики атрибута:
type AttributeComparison = {
direction: 'higher_is_better' | 'lower_is_better' | 'none';
};
function highlightBest(values: number[], direction: AttributeComparison['direction']): number {
if (direction === 'higher_is_better') return Math.max(...values);
if (direction === 'lower_is_better') return Math.min(...values);
return NaN; // не підсвічуємо
}
Це поле highlight_direction зберігається в таблиці attributes. Для атрибутів типу «колір» або «матеріал» підсвітлення не застосовується.
Відображення таблиці сравнення
Класична структура: рядки — атрибути, колонки — товари. Але при 20+ атрибутів потрібна групування:
Загальні характеристики
├── Бренд
├── Країна виробництва
└── Гарантія
Дисплей
├── Діагональ
├── Розширення
└── Частота оновлення
Продуктивність
├── Процесор
├── ОЗУ
└── Накопичувач
Групи схлопуються/розширюються. Додатковий фільтр — «Показати тільки відмінності» — ховає рядки, де у всіх товарів однакове значення. Це ключова фіча: у таблиці з 50 рядків часто 30 совпадають.
function filterDifferences(rows: ComparisonRow[]): ComparisonRow[] {
return rows.filter(row => {
const values = row.products.map(p => p.value);
return new Set(values).size > 1; // залишаємо тільки рядки з різними значеннями
});
}
Sticky-шапка та горизонтальний scroll
При 4–5 товарах в сравненні таблиця виходить за ширину екрану. Рішення:
- Горизонтальний scroll контейнера, при цьому ліва колонка (назви атрибутів) — position: sticky; left: 0
- Шапка з фото та назвами товарів — position: sticky; top: 0 (або фіксується при скролі вниз)
- На мобайлі: горизонтальний свайп через
overflow-x: autoзscroll-snap-type: x mandatory
CSS-трюк для sticky-шапки з горизонтальним scrollом — обидва sticky повинні застосовуватися до різних елементів. Шапка sticky у батьківському скролл-контейнері, ліва колонка sticky у тому ж контейнері.
Обмеження кількості товарів в сравненні
Рекомендуємий ліміт: 3–5 товарів. Більше — таблиця нечитабельна. При спробі додати 6-й показуємо сповіщення: «Уберіть один товар, щоб додати новий». UX-паттерн: замість блокування пропонуємо замінити один з існуючих.
Кнопка «Додати в сравнення» на карточці товара змінює стан: додано/видалено. Стан синхронізований глобально (Zustand, Redux Toolkit) — при додаванні на сторінці каталога кнопка на сторінці товара теж показує «додан».
Плаваюча панель сравнення
Поки користувач листає каталог и додає товари, внизу екрану (або сбоку) показується фіксована панель з поточним списком сравнення та кнопкою «Сравнити». Реалізація: fixed-позиціонований компонент, з'являється коли список непустий, анімація через CSS transform translateY.
Інтеграція з каталогом
На сторінці категорії рядом с кожним товаром — чекбокс або іконка «сравнити». При відмічених 2+ товарах — з'являється CTA-кнопка «Сравнити вибране». Це конверсійний паттерн: користувач не уходит на окрему сторінку до тих пір, поки не вибрав хоча б двох кандидатів.
Sharing та deep links
URL сторінки сравнення повинен містити список товарів: /compare?ids=42,117,203. Це дозволяє:
- Поділитися посиланням (в B2B — відправити колегу)
- Повернутися до сравнення через історію браузера
- Індексувати популярні сравнення в пошуковиках (з noindex на довгих хвостах)
Терміни
- Базове сравнення (LocalStorage, статична таблиця, кнопка на карточці): 1–2 тижні
- Повноцінна система (групування атрибутів, підсвітлення найкращого, sticky-шапка, плаваюча панель, deep links): 2–4 тижні
- Інтеграція з існуючим каталогом на нестандартній моделі даних додає 1–2 тижні
Система сравнення окупляється у категоріях, де середній чек високий та користувач витрачає час на вибір — техніка, інструмент, обладнання. У магазинах з простим ассортиментом пріоритет нижче.







