---
title: "Карусель отзывов на React с нативной привязкой прокрутки"
description: "Карусель отзывов на React без слайдер-библиотеки: CSS scroll-snap центрирует карточку, анимация по прокрутке раскрывает остальные веером. Точки, стрелки, исходник."
canonical: "https://react-horizontal-scrolling-menu.dev/ru/testimonial-carousel"
image: "https://react-horizontal-scrolling-menu.dev/og.png"
---

# Карусель отзывов на React, на нативной привязке прокрутки

Блок отзывов на любом маркетинговом сайте: одна карточка в центре, соседние наклонены позади неё, точки снизу, свайп или клик — к следующей. Слайдер-библиотеки продают ровно это. У браузера уже есть обе половины — `scroll-snap-type` для остановки и анимация по прокрутке для наклона, — а эта библиотека добавляет то, что не относится ни к одной из них: знание, какая карточка текущая.

M**Mia Chen**Product designer

“Setup took an afternoon and the first report landed in my inbox the next morning. I have not opened the old spreadsheet since.”

T**Tomás Reyes**Engineering manager

“The weekly digest is the only email my team reads end to end. Short, specific, and it names the person who unblocked you.”

A**Aisha Okafor**Founder

“I wanted numbers I could defend in a board meeting. Every figure links back to the source, so nobody has to take my word for it.”

J**Jonas Lindqvist**Data analyst

“Exports are plain CSV with sane column names. That alone saved me a script I had been maintaining for two years.”

P**Priya Natarajan**Operations lead

“Support answered a Sunday question in twenty minutes with a fix, not a ticket number. That is what I pay for.”

L**Lucas Moreau**Freelance developer

“The API does exactly what the docs say, and the docs are one page. I shipped the integration before lunch.”

Свайпайте, перетаскивайте или пользуйтесь стрелками и точками — ряд каждый раз останавливается на карточке.

[Редактировать этот пример вживую в Storybook](https://asmyshlyaev177.github.io/react-horizontal-scrolling-menu/?path=/story/examples-scrollsnap--scroll-snap)

## Для чего здесь на самом деле слайдер-библиотека

Карусель отзывов — каноническая установка Swiper: три видимые карточки, средняя плоская, точки пагинации, автовоспроизведение. Каждое из этого — либо CSS, либо несколько строк на API этой библиотеки:

-   **Остановка на карточке** — это `scroll-snap-type: x mandatory` на ряде и `scroll-snap-align: center` на элементах. Прокрутка касанием, колесом и клавиатурой остаётся нативной и сама замедляется до карточки.
-   **Наклон** — анимация по прокрутке на каждой карточке, так что угол следует за положением прокрутки пиксель в пиксель, и JavaScript ничего не измеряет. Chromium и Safari 26 рисуют его; Firefox пока держит за флагом и показывает плоские карточки, а правило защищено `@supports`.
-   **Точкам и стрелкам** нужно знать текущую карточку: `onScroll` находит ближайшую к центру ряда, а `scrollToItem(el, 'smooth', 'center')` шагает к соседу или прыгает к карточке точки, по построению попадая в точку привязки.

## Единственное, что надо сделать правильно: перетаскивание

Контейнер с обязательной привязкой привязывает каждую программную запись в `scrollLeft`, поэтому [рецепт перетаскивания мышью](https://react-horizontal-scrolling-menu.dev/ru/examples/mouse-drag.md) как есть дёргался бы от карточки к карточке. [Пример привязки прокрутки](https://react-horizontal-scrolling-menu.dev/ru/examples/scroll-snap.md) показывает решение: выключить привязку на время жеста, при отпускании плавно доехать до ближайшей карточки через `scrollToItem` и вернуть привязку CSS, когда движение успокоится, — включи раньше, и будет прыжок вместо плавного хода. Касанию ничего из этого не нужно: ряд — настоящий контейнер прокрутки.

## Автовоспроизведение, цикл и что остаётся вашим

[Автовоспроизведение](https://react-horizontal-scrolling-menu.dev/ru/examples/autoplay.md) — таймер, вызывающий тот же `scrollToItem`, с паузой при наведении, фокусе и уменьшенном движении. [Бесконечный цикл](https://react-horizontal-scrolling-menu.dev/ru/examples/infinite-loop.md) тоже сочетается: телепорт сдвигает на длину цикла, то есть на целое число точек привязки. Карточки — ваши компоненты: аватар, оценка, цитата, логотип, — а геометрия — три пользовательских свойства: ширина карточки, отступ и наклон.

## Паттерн в минимальном виде

Привязка и наклон — в таблице стилей; стрелки и точки — одно измерение и один `scrollToItem`. Демо выше — это плюс карточки.

TestimonialCarousel.tsx

```
function TestimonialCarousel({ reviews }: { reviews: Review[] }) {
  const [active, setActive] = React.useState(0);

  return (
    <ScrollMenu
      scrollContainerClassName="snap-rail"
      itemClassName="snap-slot"
      onScroll={(api) => setActive(nearestIndex(api, reviews))}
      LeftArrow={<Arrow id={reviews[active - 1]?.id} />}
      RightArrow={<Arrow id={reviews[active + 1]?.id} />}
    >
      {reviews.map((review) => (
        <ReviewCard itemId={review.id} key={review.id} {...review} />
      ))}
    </ScrollMenu>
  );
}

// The card whose center is closest to the rail's center.
function nearestIndex(api: publicApiType, reviews: Review[]) {
  const rail = api.scrollContainer.current!;
  const middle = rail.scrollLeft + rail.clientWidth / 2;
  const distances = reviews.map(({ id }) => {
    const slot = api.getItemElementById(id) as HTMLElement;
    return Math.abs(slot.offsetLeft + slot.offsetWidth / 2 - middle);
  });
  return distances.indexOf(Math.min(...distances));
}

// Arrows step one card: the neighbour of the centered one, centered.
function Arrow({ id }: { id?: string }) {
  const api = React.useContext<publicApiType>(VisibilityContext);
  const step = () => {
    const el = id && api.getItemElementById(id);
    if (el) api.scrollToItem(el, 'smooth', 'center');
  };
  return <button disabled={!id} onClick={step}>→</button>;
}

/* .snap-rail — the browser lands every swipe on a card:
     scroll-snap-type: x mandatory;
     padding-inline: calc(50% - var(--card) / 2);
   .snap-slot — scroll-snap-align: center;
   The fan is the snap.css panel on the homepage. */
```

## Или установите как компонент shadcn

Элемент реестра [snap-carousel](https://react-horizontal-scrolling-menu.dev/r/snap-carousel.json) поставляет весь паттерн — привязку, веер, стрелки, точки и перетаскивание, укладывающееся на ближайшую карточку, — как компонент со стилями Tailwind в вашем `components/ui/`. Карточки приносите свои:

shadcn

```
npx shadcn@latest add https://react-horizontal-scrolling-menu.dev/r/snap-carousel.json
```

## Похожие примеры

-   [Карусель с привязкой прокруткиКарточки прилипают к центру и раскрываются веером на CSS — без движка карусели.](https://react-horizontal-scrolling-menu.dev/ru/examples/scroll-snap.md)
-   [АвтовоспроизведениеСамо-продвигающийся цикл с доступным поведением паузы.](https://react-horizontal-scrolling-menu.dev/ru/examples/autoplay.md)
-   [Бесконечный циклБесшовный цикл на публичном API — без изменений в библиотеке.](https://react-horizontal-scrolling-menu.dev/ru/examples/infinite-loop.md)

[Все примеры (23)](https://react-horizontal-scrolling-menu.dev/ru/examples.md)

---

More examples: <https://react-horizontal-scrolling-menu.dev/examples.md>
Library summary for LLMs: <https://react-horizontal-scrolling-menu.dev/llms.txt>
