Розроблення функції порівняння товарів для інтернет-магазину
Функція порівняння товарів на карточці — вбудований механізм додавання в список порівняння, інтегрований у контекст перегляду каталогу та окремих товарів. На відміну від окремої системи порівняння, тут йдеться про UX-компонент: кнопку, плаваючу панель та сторінку порівняння, щільно інтегровані в основний інтерфейс магазину.
Точки входу в порівняння
Користувач може додати товар у порівняння з кількох місць:
Карточка в листингу: невелика кнопка чи іконка поруч з "У корзину". На десктопі — з'являється при наведенні, на мобайлі — завжди видна. Не повинна конкурувати за розмір з основною CTA (кнопкою покупки).
Сторінка товару: кнопка "Порівняти" поруч з розділом характеристик чи в блоку вторинних дій.
Сторінка порівняння: кнопка "Додати ще" — відкриває пошук чи перехід у каталог.
Стан кнопки: додано/не додано — синхронізовано глобально. При додаванні з листингу кнопка на сторінці товару теж відображає стан.
Збереження стану
// Zustand store для списку порівняння
interface CompareStore {
items: number[]; // масив product_id
maxItems: number; // лімітліміт (зазвичай 3–5)
add: (id: number) => void;
remove: (id: number) => void;
clear: () => void;
has: (id: number) => boolean;
}
const useCompareStore = create<CompareStore>()(
persist(
(set, get) => ({
items: [],
maxItems: 4,
add: (id) => {
const { items, maxItems } = get();
if (items.length >= maxItems) {
toast.error(`Можна порівнювати не більше ${maxItems} товарів`);
return;
}
if (!items.includes(id)) set({ items: [...items, id] });
},
remove: (id) => set({ items: get().items.filter(i => i !== id) }),
clear: () => set({ items: [] }),
has: (id) => get().items.includes(id),
}),
{ name: 'compare-list' } // зберігаємо в localStorage
)
);
Плаваюча панель порівняння
Коли користувач листає каталог та додає товари, внизу екрана з'являється fixed-панель з поточним списком.
function CompareBar() {
const { items, remove, clear } = useCompareStore();
if (items.length === 0) return null;
return (
<div className="fixed bottom-0 left-0 right-0 z-50 bg-white border-t shadow-lg p-4
translate-y-0 transition-transform duration-300">
<div className="max-w-screen-xl mx-auto flex items-center gap-4">
<span className="text-sm text-gray-500">
Порівняння: {items.length} товар
</span>
<div className="flex gap-2 flex-1">
{items.map(id => (
<CompareBarItem key={id} productId={id} onRemove={() => remove(id)} />
))}
</div>
<Link href={`/compare?ids=${items.join(',')}`}>
<Button>Порівняти</Button>
</Link>
<button onClick={clear} className="text-gray-400 hover:text-gray-600">
Очистити
</button>
</div>
</div>
);
}
CompareBarItem — маленьке фото + назва + кнопка видалення. Назва обрізується до 2–3 слів. При додаванні нового товару — анімація (товар «прилітає» у панель).
Сторінка порівняння
Дані товарів завантажуються за масивом ID з URL:
// /compare?ids=42,117,203
const ids = searchParams.get('ids')?.split(',').map(Number) ?? [];
const { data: products } = useSWR(
ids.length ? `/api/compare?ids=${ids.join(',')}` : null,
fetcher
);
API-endpoint повертає товари з повним набором атрибутів для порівняння. Важливо: якщо ID не існує чи товар знятий з продажу — повертаємо часткові дані з флагом unavailable, а не помилку.
Таблиця порівняння
Специфіка e-commerce-порівняння (на відміну від окремої системи порівняння): тут користувач зазвичай порівнює 2–4 товари однієї категорії, тому атрибути більш однорідні.
Ключові UX-паттерни:
Закріплення шапки з фото та цінами при вертикальному скролі:
.compare-header {
position: sticky;
top: var(--navbar-height);
z-index: 10;
background: white;
}
Підсвітлення різниць: строки, де значення різняться — виділяються (фон, bold). Строки з однаковими значеннями — складаються або відображаються приглушено.
Кнопки дій у шапці: "У корзину" / "Видалити з порівняння" прямо під фото кожного товару, без потреби переходити на карточку.
«Додати ще»: остання колонка — placeholder з пошуком. Користувач може додати товар прямо зі сторінки порівняння через inline-пошук.
Порівняння з контекстом: «найкращий вибір»
Опціональна фіча: система помічає «переможця» у кожній характеристиці. Реалізація через флаг highlight_if_best в атрибуті + логіку визначення найкращого значення (min/max для числових).
function CompareCell({ value, isBest, attributeDirection }: Props) {
return (
<td className={cn('p-3 text-center', isBest && 'bg-green-50 font-semibold text-green-700')}>
{value}
{isBest && <span className="ml-1 text-xs">✓</span>}
</td>
);
}
Не застосовувати до атрибутів типу «колір», «матеріал» — для них «найкращого» не існує.
Синхронізація між вкладками
Якщо користувач відкрив кілька вкладок, список порівняння повинен бути одинаковим. localStorage за замовчуванням не сповіщає інші вкладки. Рішення — storage event:
window.addEventListener('storage', event => {
if (event.key === 'compare-list') {
useCompareStore.getState().hydrate(JSON.parse(event.newValue ?? '{}'));
}
});
Zustand з persist-плагіном обробляє це автоматично при правильній налаштуванні.
SEO-аспекти
Сторінки порівняння з конкретними ID (/compare?ids=42,117) — закрити від індексації (noindex). Вони не дають SEO-цінності та створюють дублі. Canonical — нема сенсу проставляти.
Якщо генеруються «популярні порівняння» з редакційним контентом («Порівняння iPhone 15 vs Samsung S24»), такі сторінки — статичні, зі своїм URL та унікальним текстом. Вони індексуються та ранжуються за порівняльними запитами.
Аналітика
- Які товари чаще всього порівнюють разом — сигнал для «схожих товарів»
- Конверсія зі сторінки порівняння: з яких пар замовляють, а з яких уходять
- Який товар частіше «перемагає» в порівняннях (беруть його, а не альтернативу)
-- Пари товарів, які частіше всього порівнюють
SELECT
LEAST(product_a, product_b) AS p1,
GREATEST(product_a, product_b) AS p2,
COUNT(*) AS compare_sessions
FROM compare_sessions
GROUP BY 1, 2
ORDER BY 3 DESC;
Терміни
- Кнопка + localStorage + плаваюча панель: 3–5 робочих днів
- Сторінка порівняння з таблицею атрибутів та підсвітленням різниць: 1–2 тижні
- З «найкращим вибором», inline-додаванням, аналітикою: 2–3 тижні







