@daformat/react-swipeable-cards

中文文档
1.0.4发布于 2026/05/19Zero-Clause BSDTypeScriptES Module零依赖近 30 天下载 9858

无头反应可刷卡自旋组件,类似于 Tinder, 没有依赖性

中文使用文档
文档目录16 节
  1. 安装
  2. 演示
  3. 快开始
  4. 样式示例
  5. 构成部分结构
  6. 构成部分
  7. SwipeableCards.Root
  8. SwipeableCards.Cards
  9. SwipeableCards.CardWrapper
  10. SwipeableCards.SwipeLeftButton 编号 : SwipeRightButton 编号 : S…
  11. 钩子
  12. SwipeableCards.useSwipeableCardsContext()
  13. SwipeableCards.useSwipeableCardsStack()
  14. SwipeableCards.useProgrammaticSwipe()
  15. CSS 自定义属性
  16. 导出类型

反应可刷卡

NPM Version NPM Downloads
Follow daformat on GitHub Follow daformat on X

无头反应可刷卡旋转木马组件,类似于Tinder,没有 运行时间依赖。 拖放,或按程序刷卡 左,右, 上下两相. 可在屏幕外丢弃卡片或将卡片发到堆栈后端 永远循环。 由网络动画 API 所建,用于平滑的,有动力的认知 动画和中枢加权自转,在指下感觉自然.

安装

npm install @daformat/react-swipeable-cards
yarn add @daformat/react-swipeable-cards
pnpm add @daformat/react-swipeable-cards
bun add @daformat/react-swipeable-cards
deno add npm:@daformat/react-swipeable-cards

演示

https://hello-mat.com/design-engineering/组件/可刷卡

快开始

import { SwipeableCards } from "@daformat/react-swipeable-cards";

const cards = [
  { id: "1", card: <div className="card">Card 1</div> },
  { id: "2", card: <div className="card">Card 2</div> },
  { id: "3", card: <div className="card">Card 3</div> },
];

export function App() {
  return (
    <SwipeableCards.Root
      cards={cards}
      emptyView={<div>No more cards</div>}
      onSwipe={(direction, cardId) => {
        console.log(`Swiped ${cardId} ${direction}`);
      }}
    >
      <SwipeableCards.Cards />
      <SwipeableCards.SwipeLeftButton>👎</SwipeableCards.SwipeLeftButton>
      <SwipeableCards.SwipeRightButton>👍</SwipeableCards.SwipeRightButton>
    </SwipeableCards.Root>
  );
}

构成部分是 无头: 盒子里没有东西是样式的。 你决定 如何看牌,你决定如何铺出堆栈(典型的是CSS) 组件所暴露的自定义属性,参见 CSS 自定义属性) (中文(简体) ).

样式示例

考虑到以下jsx:

// full source: https://github.com/daformat/hello-mat/blob/master/pages/design-engineering/component/swipeable-cards.tsx

<SwipeableCards.Root
  cards={[...cards /* omitted for brevity */]}
  className={styles.cards_root}
  data-style={"stacked-offset" /* "stacked-rotation" | "minimal" */}
  swipeStyle={"sendToBack"}
  sendToBackMargin={16}
  loop
>
  <SwipeableCards.Cards
    visibleStackLength={4}
    style={{ aspectRatio: "650 / 400" }}
  />
</SwipeableCards.Root>

我用这个来形容 演示

/* full source: https://github.com/daformat/hello-mat/blob/master/components/SwipeableCards/SwipeableCards.module.scss */

.cards_root {
  --ease-out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1);

  [data-swipeable-cards-cards] {
    display: grid;
    grid-template-columns: 1fr;
    place-content: center;
    position: relative;
    touch-action: none;
    z-index: 1;

    [data-swipeable-cards-card-wrapper] {
      align-content: center;
      align-self: center;
      grid-area: 1 / 1;
      opacity: calc(
        1 - clamp(0, var(--stack-index0) - var(--visible-stack-length), 1)
      );
      touch-action: none;
      transform-origin: center 0;
      transition: all 0.2s var(--ease-out-cubic);
      transition-property: opacity, scale, padding-top, margin-top;
      width: 100%;
      will-change: opacity, scale, padding-top, margin-top, transform;
    }
  }

  &:is([data-style="stacked-offset"], [data-style="stacked-rotation"]) {
    [data-swipeable-cards-cards] {
      [data-swipeable-cards-card-wrapper] {
        scale: calc(
          100% - min(var(--stack-index0), var(--visible-stack-length)) * 10%
        );
      }
    }
  }

  &[data-style="stacked-offset"] {
    [data-swipeable-cards-cards] {
      [data-swipeable-cards-card-wrapper] {
        // Pick whatever vertical step you want between stacked cards.
        --card-top-distance: clamp(16px, 1vw, 32px);
        --p: calc(
          var(--card-top-distance) *
            max(var(--visible-stack-length) - var(--stack-index0), 0)
        );
        --m: calc(
          var(--card-top-distance) *
            (
              var(--visible-stack-length) - max(
                  var(--visible-stack-length) - var(--stack-index0),
                  0
                )
            )
        );
        margin-top: calc(var(--m) * -1);
        padding-top: calc(var(--p));
      }
    }
  }
}

构成部分结构

/* Provides context to all swipeable card components */
<SwipeableCards.Root cards={cards} emptyView={...}>
  {/* The stack container */}
  <SwipeableCards.Cards>
    {/* Optional: bring your own renderer; otherwise the default renders one CardWrapper per card */}
    {(stack) =>
      stack.map((card) => (
        <SwipeableCards.CardWrapper key={card.id} card={card} />
      ))
    }
  </SwipeableCards.Cards>
  {/* Optional programmatic swipe buttons */}
  <SwipeableCards.SwipeLeftButton />
  <SwipeableCards.SwipeRightButton />
  <SwipeableCards.SwipeUpButton />
  <SwipeableCards.SwipeDownButton />
</SwipeableCards.Root>

构成部分

SwipeableCards.Root

最外道的包装 提供上下文, 设置拖曳状态, 并听 改为 pointermove 编号 : pointerup 在文档上。 显示 a <div>。 。 。 .

这个 loop 道克是受歧视的结合的一部分: loop 实值 false (或 省略了) 你 必须 通过 emptyView; 当 loop 实值 true 你们 绝对不能 通过 emptyView。 。 。 .

Prop类型默认说明
cards (必须)CardWithId[]—— 说堆起的首发牌. 最后一件是顶卡.
emptyViewReactNode—— 说需要时间 loop 实值 false。当堆栈为空时显示.
loopbooleanfalse何时 true,被刷卡动画到堆栈后部而不是被取出. emptyView 无法在此模式中设置 .
onSwipe(direction: SwipeDirection, cardId: string) => void—— 说在卡牌承诺出道(动画完成前)之后,每张刷卡一次拨打.
swipeStyle"discard" | "sendToBack""discard""discard" 将牌从屏幕上扔出并淡出. "sendToBack" 将它滑过堆叠边缘,然后在其他牌下进行动画.
sendToBackMarginnumber0额外的像素,卡片在被发送到后方之前会经过堆栈边缘. 仅在 swipeStyle 实值 "sendToBack"。 。 。 .
getCardElement(element: Element) => Element第一个孩子接收包装元件并返回实际的卡片元件来进行相撞检测. 默认为 element.firstElementChild ?? element,因此默认情况下,包装上的任何粘贴都排除在外。 当可见卡不是包装人的第一个孩子时(例如当您将额外布局元素嵌入包装人内部时)被覆盖.
swipeDirectionsAllowedSwipeDirection | AllowedSwipeDirection[]全部4个限制可以刷卡的方向 。 接受单个值或数组 SwipeDirection | "x" | "y" | "horizontal" | "vertical"。快捷键扩展至相应的对("x"编号 :"horizontal" → ["left", "right"], (中文(简体) ), "y"编号 :"vertical" → ["up", "down"]) (中文(简体) ). 约束指针拖到允许的轴/方向并禁用匹配的方向按钮.
refRef<HTMLDivElement>—— 说转发到根 <div>。 。 。 .
...propsComponentPropsWithoutRef<"div">—— 说所有标准 <div> 道具( r)className, (中文(简体) ), style, (中文(简体) ), children(见a/c.5/49/70,附件).

根上的数据属性集

属性数值说明
data-swipeable-cards-root""总是在场。 标记上下文查看的根 .

SwipeableCards.Cards

堆货箱 设置您卡片样式使用的 CSS 自定义属性 计算堆叠卡的z-index,缩放,不透明等. 显示 a <div> 外加内脏 <div data-empty> 当堆栈是 空( 空视图总是渲染出, 这样您就可以在外/ 外切换 - 自由的) (英语).

Prop类型默认说明
visibleStackLengthnumber4可见的外观中应该出现多张从堆起的卡片. 用来计算 --visible-stack-length 你的风格.
childrenReactNode | (stack: CardWithId[]) => ReactNode每张卡片一张或预发子或接收当前堆栈并返回节点的函数 。 需要每张卡定制时使用函数表.
refRef<HTMLDivElement>—— 说转发到卡片 <div>。 。 。 .
...propsOmit<ComponentPropsWithoutRef<"div">, "children">—— 说所有标准 <div> 道具除外 children (被儿童功能变体所压倒).

卡片容器上的数据属性集

属性数值说明
data-swipeable-cards-cards""总是在场。 内部用来定位堆栈.
data-empty""设置在持有空视图的内包装上 .

SwipeableCards.CardWrapper

单张牌的包装纸 拥有该卡的指针交互—— 只有顶卡回复 pointerdown显示 a <div> 包含 无论是 card.card 或者,如果你通过了 children,你自己的内容.

Prop类型默认说明
card (必须)CardWithId—— 说这个 { id, card } 从此包装符所代表的堆栈中输入.
childrenReactNodecard.card覆盖卡片自己的JSX. 用于卡片上方的分层控制,同时保留包装处理指针事件.
refRef<HTMLDivElement>—— 说转发到包装器 <div>。 。 。 .
...propsComponentPropsWithoutRef<"div">—— 说所有标准 <div> 道具来. onPointerDown 财务报告和审定财务报表 onDragStart 与内部处理者合并.

每个包装上的数据属性集

属性数值说明
data-swipeable-cards-card-wrapper""总是在场。 用于内部列出堆放项目.
data-swipeable-cards-id页:1 card.id让您瞄准来自 CSS 或查询选择器的特定卡片.
data-swipeable-cards-top-card"true" | "false""true" 当此包装目前为顶卡时。 用于用属性选择器突出显示可操作卡 .

SwipeableCards.SwipeLeftButton 编号 : SwipeRightButton 编号 : SwipeUpButton 编号 : SwipeDownButton

程序刷新按钮。 单击一个会触发当前顶部的擦拭 以随机键、速度和旋转为方向的卡片,所以 重复点击感觉自然。 当堆栈为空时自动取消操作或 刷卡工作已经开始。 显示 a <button>。 。 。 .

Prop类型默认说明
onClickMouseEventHandler<HTMLButtonElement>—— 说在刷子被触发前同步调用 。 可用于分析,声音效果,或同步伴奏UI动画.
refRef<HTMLButtonElement>—— 说已转交 <button>。 。 。 .
...propsComponentPropsWithoutRef<"button">—— 说所有标准 <button> 道具来.

钩子

SwipeableCards.useSwipeableCardsContext()

返回完整的内部环境。 必须在里面使用 SwipeableCards.Root。 。 。 .

const {
  loop,
  emptyView,
  cards,
  stack,
  setStack,
  discardedCardId,
  setDiscardedCardId,
  dragStateRef,
  animationRef,
  commitSwipe,
  swipeStyle,
  getCardElement,
  sendToBackMargin,
  rootRef,
  swipeDirections,
  swipeDirectionsRef,
} = SwipeableCards.useSwipeableCardsContext();
财产类型说明
loopboolean | undefined反映 loop 道具传到 Root。 。 。 .
emptyViewReactNode空视图传至 Root (仅在 loop 实值 false) (中文(简体) ).
cardsCardWithId[]原画 cards 道具传到 Root启用 stack {\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}现场的变异名单.
stackCardWithId[]现相堆. 牌被刷去时被取出(或旋转,回转时被取出).
setStackDispatch<SetStateAction<CardWithId[]>>一定要替换堆栈。 用于按需添加新牌.
discardedCardIdstring卡片的ID 正在动画中,或者 "" 如果没有在刷卡.
setDiscardedCardIdDispatch<SetStateAction<string>>改善上述情况(需要外部援助).
dragStateRefRefObject<DraggingState>可变拖放状态(指向位置,速度,支点,当前被拖放的元素). 拖曳时读取自定义 UI 反馈 .
animationRefRefObject<Animation[]>目前运行的Web动画API动画为活性刷卡.
commitSwipe(manual?: boolean, event?: PointerEvent) => void将当前拖动状态提交到刷卡中(或者如果手势太小,将卡返回到堆栈中).
swipeStyle"discard" | "sendToBack"反映 swipeStyle 道具 (英语).
getCardElement(element: Element) => Element反映 getCardElement 道具 (英语).
sendToBackMarginnumber反映 sendToBackMargin 道具 (英语).
rootRefRefObject<HTMLDivElement>对根 <div> 执行人: SwipeableCards.Root。 。 。 .
swipeDirectionsSwipeDirection[]已规范的允许刷取方向列表( 默认为全部 4) 。 消费者在 swipeDirections 道具变化.
swipeDirectionsRefRefObject<SwipeDirection[]>在事件处理器没有重新装订的收听器的情况下,将上面的镜像反射到任何读取的地方.

SwipeableCards.useSwipeableCardsStack()

仅返回当前状态的便捷钩 stack (记取. 使用这个 在只关心堆栈的组件中,以避免重接 不相关的上下文变化.

const stack = SwipeableCards.useSwipeableCardsStack();

SwipeableCards.useProgrammaticSwipe()

将内置方向按钮赋予权力. 用它来制造自己的刷子 触发器。 如果周围的树不包括一个 SwipeableCards.Cards 触发时装入容器.

const { trigger, swipeStyle, swipeDirections } =
  SwipeableCards.useProgrammaticSwipe();
财产类型说明
trigger(configure: (state: DraggingState, rect: DOMRect) => void) => void触发上卡的刷卡.  configure 调回调取( 零) 拖放状态和包装器的绑定矩形; 设置 velocityX/Y, (中文(简体) ), pivotX/Y, (中文(简体) ), startX/Y, (中文(简体) ), lastX/Y。 。 。 .
swipeStyle"discard" | "sendToBack"显示活动 swipeStyle 因此,您的自定义按钮可以选择寻找任一模式的快速值.
swipeDirectionsSwipeDirection[]已规范的允许方向列表, 用于在不允许您自定义触发时使其失效 .

示例: "超像" 向上刷

function SuperLikeButton() {
  const { trigger } = SwipeableCards.useProgrammaticSwipe();
  return (
    <button
      type="button"
      onClick={() =>
        trigger((state, rect) => {
          state.velocityX = 0;
          state.velocityY = -(rect.height / 442) * 4;
          state.pivotX = 0;
          state.pivotY = -0.4;
          state.startY = 0;
          state.lastY = -1;
        })
      }
    >
      ⭐
    </button>
  );
}

CSS 自定义属性

组件设置这些 CSS 自定义属性,以便您能够完全驱动 视觉堆栈外观来自您的 CSS, 不含道具钻取值 进入每张卡片.

打开 SwipeableCards.Cards

财产说明
--stack-length目前堆栈中的牌数 .
--visible-stack-length“peek”中的牌数(最上方牌后可见的上部),盖到 visibleStackLength。 。 。 .

每个 SwipeableCards.CardWrapper

财产说明
--stack-index由上而上地取出1个卡片索引. 顶卡是 1。 。 。 .
--stack-index0从上取出卡的0基指数. 顶卡是 0。 。 。 .

示例样式

[data-swipeable-cards-card-wrapper] {
  position: absolute;
  inset: 0;
  transform-origin: center top;
  transition:
    scale 0.3s,
    translate 0.3s;
  scale: calc(1 - var(--stack-index0) * 0.05);
  translate: 0 calc(var(--stack-index0) * clamp(16px, 1vw, 32px));
  z-index: var(--stack-length);
  pointer-events: none;
}

[data-swipeable-cards-top-card="true"] {
  pointer-events: auto;
  cursor: grab;
}

[data-swipeable-cards-top-card="true"]:active {
  cursor: grabbing;
}

导出类型

import type {
  CardWithId,
  SwipeDirection,
  AllowedSwipeDirection,
  SwipeStyle,
  GetCardElement,
  StackRenderer,
  DraggingState,
  BaseSwipeableCardsProps,
  NotLoopingSwipeableProps,
  LoopingSwipeableProps,
  SwipeableCardsProps,
  SwipeableCardsCardsProps,
  SwipeableCardsCardWrapperProps,
} from "@daformat/react-swipeable-cards";
类型形状
CardWithId{ id: string; card: JSX.Element }
SwipeDirection"left" | "right" | "up" | "down"
AllowedSwipeDirectionSwipeDirection | "x" | "y" | "horizontal" | "vertical" ——被大会接受 swipeDirections 道具 (英语).
SwipeStyle"discard" | "sendToBack"
GetCardElement(element: Element) => Element
StackRenderer(stack: CardWithId[]) => ReactNode
DraggingState可变拖动状态对象(指针位置,速度,中枢,当前被拖动的元素). 见 dragStateRef 见上文.
SwipeableCardsProps歧视工会 NotLoopingSwipeableProps 财务报告和审定财务报表 LoopingSwipeableProps。 。 。 .

版本与发布记录

@daformat/react-swipeable-cards 共发布 4 个版本,首次发布于 2026/05/03;过去 12 个月发布了 4 个版本。

版本发布日期距今
1.0.4latest2026/05/195 个月前
1.0.32026/05/185 个月前
1.0.22026/05/096 个月前
1.0.12026/05/036 个月前

关于 @daformat/react-swipeable-cards 的常见问题

@daformat/react-swipeable-cards 是什么?

无头反应可刷卡自旋组件,类似于 Tinder, 没有依赖性

如何安装 @daformat/react-swipeable-cards?

在项目目录运行 npm install @daformat/react-swipeable-cards,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(>=18)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@daformat/react-swipeable-cards 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@daformat/react-swipeable-cards 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@daformat/react-swipeable-cards 可以商用吗?

@daformat/react-swipeable-cards 使用 Zero-Clause BSD 许可。声明的许可为 Zero-Clause BSD,请阅读许可原文确认使用条件。以上为概括,不构成法律意见,请以许可原文为准。

@daformat/react-swipeable-cards 的下载量有多少?

根据 npm 官方下载统计,@daformat/react-swipeable-cards 在 2026/10/11 – 2026/11/09 这 30 天内被下载 9,858 次,日均约 329 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@daformat/react-swipeable-cards 还在维护吗?

最新版本 1.0.4 发布于 2026/05/19(5 个月前);至今共发布 4 个版本,其中过去 12 个月发布了 4 个;首次发布于 2026/05/03。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。