反应可刷卡
无头反应可刷卡旋转木马组件,类似于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[] | —— 说 | 堆起的首发牌. 最后一件是顶卡. |
emptyView | ReactNode | —— 说 | 需要时间 loop 实值 false。当堆栈为空时显示. |
loop | boolean | false | 何时 true,被刷卡动画到堆栈后部而不是被取出. emptyView 无法在此模式中设置 . |
onSwipe | (direction: SwipeDirection, cardId: string) => void | —— 说 | 在卡牌承诺出道(动画完成前)之后,每张刷卡一次拨打. |
swipeStyle | "discard" | "sendToBack" | "discard" | "discard" 将牌从屏幕上扔出并淡出. "sendToBack" 将它滑过堆叠边缘,然后在其他牌下进行动画. |
sendToBackMargin | number | 0 | 额外的像素,卡片在被发送到后方之前会经过堆栈边缘. 仅在 swipeStyle 实值 "sendToBack"。 。 。 . |
getCardElement | (element: Element) => Element | 第一个孩子 | 接收包装元件并返回实际的卡片元件来进行相撞检测. 默认为 element.firstElementChild ?? element,因此默认情况下,包装上的任何粘贴都排除在外。 当可见卡不是包装人的第一个孩子时(例如当您将额外布局元素嵌入包装人内部时)被覆盖. |
swipeDirections | AllowedSwipeDirection | AllowedSwipeDirection[] | 全部4个 | 限制可以刷卡的方向 。 接受单个值或数组 SwipeDirection | "x" | "y" | "horizontal" | "vertical"。快捷键扩展至相应的对("x"编号 :"horizontal" → ["left", "right"], (中文(简体) ), "y"编号 :"vertical" → ["up", "down"]) (中文(简体) ). 约束指针拖到允许的轴/方向并禁用匹配的方向按钮. |
ref | Ref<HTMLDivElement> | —— 说 | 转发到根 <div>。 。 。 . |
...props | ComponentPropsWithoutRef<"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 | 类型 | 默认 | 说明 |
|---|---|---|---|
visibleStackLength | number | 4 | 可见的外观中应该出现多张从堆起的卡片. 用来计算 --visible-stack-length 你的风格. |
children | ReactNode | (stack: CardWithId[]) => ReactNode | 每张卡片一张 | 或预发子或接收当前堆栈并返回节点的函数 。 需要每张卡定制时使用函数表. |
ref | Ref<HTMLDivElement> | —— 说 | 转发到卡片 <div>。 。 。 . |
...props | Omit<ComponentPropsWithoutRef<"div">, "children"> | —— 说 | 所有标准 <div> 道具除外 children (被儿童功能变体所压倒). |
卡片容器上的数据属性集
| 属性 | 数值 | 说明 |
|---|---|---|
data-swipeable-cards-cards | "" | 总是在场。 内部用来定位堆栈. |
data-empty | "" | 设置在持有空视图的内包装上 . |
SwipeableCards.CardWrapper
单张牌的包装纸 拥有该卡的指针交互——
只有顶卡回复 pointerdown显示 a <div> 包含
无论是 card.card 或者,如果你通过了 children,你自己的内容.
| Prop | 类型 | 默认 | 说明 |
|---|---|---|---|
card (必须) | CardWithId | —— 说 | 这个 { id, card } 从此包装符所代表的堆栈中输入. |
children | ReactNode | card.card | 覆盖卡片自己的JSX. 用于卡片上方的分层控制,同时保留包装处理指针事件. |
ref | Ref<HTMLDivElement> | —— 说 | 转发到包装器 <div>。 。 。 . |
...props | ComponentPropsWithoutRef<"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 | 类型 | 默认 | 说明 |
|---|---|---|---|
onClick | MouseEventHandler<HTMLButtonElement> | —— 说 | 在刷子被触发前同步调用 。 可用于分析,声音效果,或同步伴奏UI动画. |
ref | Ref<HTMLButtonElement> | —— 说 | 已转交 <button>。 。 。 . |
...props | ComponentPropsWithoutRef<"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();
| 财产 | 类型 | 说明 |
|---|---|---|
loop | boolean | undefined | 反映 loop 道具传到 Root。 。 。 . |
emptyView | ReactNode | 空视图传至 Root (仅在 loop 实值 false) (中文(简体) ). |
cards | CardWithId[] | 原画 cards 道具传到 Root启用 stack {\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}现场的变异名单. |
stack | CardWithId[] | 现相堆. 牌被刷去时被取出(或旋转,回转时被取出). |
setStack | Dispatch<SetStateAction<CardWithId[]>> | 一定要替换堆栈。 用于按需添加新牌. |
discardedCardId | string | 卡片的ID 正在动画中,或者 "" 如果没有在刷卡. |
setDiscardedCardId | Dispatch<SetStateAction<string>> | 改善上述情况(需要外部援助). |
dragStateRef | RefObject<DraggingState> | 可变拖放状态(指向位置,速度,支点,当前被拖放的元素). 拖曳时读取自定义 UI 反馈 . |
animationRef | RefObject<Animation[]> | 目前运行的Web动画API动画为活性刷卡. |
commitSwipe | (manual?: boolean, event?: PointerEvent) => void | 将当前拖动状态提交到刷卡中(或者如果手势太小,将卡返回到堆栈中). |
swipeStyle | "discard" | "sendToBack" | 反映 swipeStyle 道具 (英语). |
getCardElement | (element: Element) => Element | 反映 getCardElement 道具 (英语). |
sendToBackMargin | number | 反映 sendToBackMargin 道具 (英语). |
rootRef | RefObject<HTMLDivElement> | 对根 <div> 执行人: SwipeableCards.Root。 。 。 . |
swipeDirections | SwipeDirection[] | 已规范的允许刷取方向列表( 默认为全部 4) 。 消费者在 swipeDirections 道具变化. |
swipeDirectionsRef | RefObject<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 因此,您的自定义按钮可以选择寻找任一模式的快速值. |
swipeDirections | SwipeDirection[] | 已规范的允许方向列表, 用于在不允许您自定义触发时使其失效 . |
示例: "超像" 向上刷
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" |
AllowedSwipeDirection | SwipeDirection | "x" | "y" | "horizontal" | "vertical" ——被大会接受 swipeDirections 道具 (英语). |
SwipeStyle | "discard" | "sendToBack" |
GetCardElement | (element: Element) => Element |
StackRenderer | (stack: CardWithId[]) => ReactNode |
DraggingState | 可变拖动状态对象(指针位置,速度,中枢,当前被拖动的元素). 见 dragStateRef 见上文. |
SwipeableCardsProps | 歧视工会 NotLoopingSwipeableProps 财务报告和审定财务报表 LoopingSwipeableProps。 。 。 . |