更新记录
1.0.0(2026-10-07)
下载此版本
[1.0.0] - 2026-08-25
新增
- 首次发布
- 支持卡片堆叠展示
- 支持手势左右滑动
- 支持无限循环
- 支持自动播放
- 支持点击事件回调
- 支持配置缩放、偏移、旋转、透明度等参数
平台兼容性
uni-app(3.8.5)
| Vue2 |
Vue2插件版本 |
Vue3 |
Vue3插件版本 |
Chrome |
Chrome插件版本 |
Safari |
Safari插件版本 |
app-vue |
app-nvue |
Android |
iOS |
鸿蒙 |
| √ |
1.0.0 |
√ |
1.0.0 |
√ |
1.0.0 |
√ |
1.0.0 |
√ |
√ |
√ |
√ |
√ |
| 微信小程序 |
支付宝小程序 |
抖音小程序 |
百度小程序 |
快手小程序 |
京东小程序 |
鸿蒙元服务 |
QQ小程序 |
飞书小程序 |
小红书小程序 |
快应用-华为 |
快应用-联盟 |
| √ |
√ |
√ |
√ |
√ |
√ |
√ |
√ |
√ |
√ |
√ |
√ |
CardStack 卡片堆叠组件
一款支持手势拖拽滑动、无限循环、自动播放的卡片堆叠组件,适用于 Tinder 式卡片交互、商品推荐、AI 问答等场景。
特性
- ✅ 手势拖拽滑动(左右切换卡片)
- ✅ 无限循环 / 有限模式
- ✅ 自动播放(可配置间隔)
- ✅ 用户交互后暂停 / 恢复自动播放
- ✅ 堆叠错落效果(缩放、偏移、旋转、透明度可配置)
- ✅ 点击事件回调
- ✅ 兼容 App / H5 / 微信小程序全平台
基本使用
<template>
<view class="page">
<card-stack
ref="cardStack"
:items="reviewList"
height="520rpx"
:loop="true"
:autoplay="true"
:stopOnUserSwipe="false"
:duration="300"
:stack-depth="3"
:stack-offset-x="10"
:stack-offset-y="8"
:stack-scale-step="0.025"
:max-rotate="60"
:swipe-threshold="0.22"
:short-swipe-velocity="0.45"
@change="onCardChange"
@cardClick="onRightCardClick"
>
<template #card="{ item }">
<view class="review-card">
<view class="review-card__header">
<!-- <image
class="avatar"
:src="item.avatar"
mode="aspectFill"
/> -->
<view class="user-info">
<view class="name">
{{ item.name }}
</view>
<view class="age">
{{ item.age }}岁 | {{ item.gender }}
</view>
</view>
</view>
<view class="review-card__content">
{{ item.content }}
</view>
<view class="review-card__button">
购买同款
</view>
</view>
</template>
</card-stack>
</view>
</template>
<script>
export default {
data() {
return {
reviewList: [
{
id: 1,
avatar: '/static/avatar/1.png',
name: 'j的name',
age: 42,
gender: '男',
content: '效果真的不错,每天两粒,吃了快一个月。'
},
{
id: 2,
avatar: '/static/avatar/2.png',
name: '阿源',
age: 38,
gender: '男',
content: '确实实用,现在指标已经明显下降。'
},
{
id: 3,
avatar: '/static/avatar/3.png',
name: '王先生',
age: 31,
gender: '男',
content: '坚持使用了一段时间,整体感觉不错。'
},
{
id: 4,
avatar: '/static/avatar/4.png',
name: 'JamesCard',
age: 42,
gender: '男',
content: '连续使用之后效果比较明显。'
}
]
}
},
methods: {
onCardChange(e) {
console.log('当前卡片:', e.index)
console.log('切换方向:', e.direction)
},
onRightCardClick(e) {
console.log('当前卡片:', e.index)
},
}
}
</script>
<style lang="scss" scoped>
.page {
padding: 30rpx;
}
.review-card {
height: 100%;
box-sizing: border-box;
padding: 30rpx;
background: #fff;
border-radius: 20rpx;
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.08);
}
.review-card__header {
display: flex;
align-items: center;
}
.avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
}
.user-info {
margin-left: 20rpx;
}
.name {
font-size: 30rpx;
font-weight: 600;
}
.age {
margin-top: 8rpx;
font-size: 24rpx;
color: #999;
}
.review-card__content {
margin-top: 40rpx;
font-size: 28rpx;
line-height: 1.8;
color: #333;
}
.review-card__button {
margin-top: 40rpx;
height: 72rpx;
line-height: 72rpx;
text-align: center;
border: 1px solid #ddd;
border-radius: 36rpx;
color: #666;
}
</style>
Props
| 属性 |
类型 |
默认值 |
说明 |
items |
Array |
[] |
数据源列表 |
height |
String / Number |
'420rpx' |
容器高度 |
initialIndex |
Number |
0 |
初始索引 |
loop |
Boolean |
true |
是否无限循环 |
duration |
Number |
300 |
动画时长(ms) |
stackDepth |
Number |
3 |
堆叠层数(可见卡片数) |
stackOffsetX |
Number |
10 |
每层水平偏移(px) |
stackOffsetY |
Number |
8 |
每层垂直偏移(px) |
stackScaleStep |
Number |
0.025 |
每层缩放步长 |
maxRotate |
Number |
7 |
最大旋转角度(deg) |
swipeThreshold |
Number |
0.22 |
滑动判定阈值(占容器宽度比例) |
shortSwipeVelocity |
Number |
0.45 |
快速滑动速度阈值 |
longSwipeMs |
Number |
300 |
长按判定时间(ms) |
resistance |
Number |
0.25 |
边界阻尼系数 |
autoplay |
Boolean |
false |
是否自动播放 |
autoplayInterval |
Number |
3000 |
自动播放间隔(ms) |
stopOnUserSwipe |
Boolean |
true |
用户滑动后是否永久停止自动播放 |
resumeAutoplayDelay |
Number |
5000 |
用户滑动后恢复自动播放延迟(ms) |
Events
| 事件名 |
参数 |
说明 |
change |
{ index, item, direction } |
卡片切换时触发 |
cardClick |
{ index, item } |
点击卡片时触发 |
Slot
| 插槽名 |
参数 |
说明 |
card |
{ item, index, active, position } |
卡片内容自定义 |
Slot 参数说明
| 参数 |
说明 |
item |
当前卡片数据 |
index |
当前卡片索引 |
active |
是否为当前活动卡片 |
position |
堆叠位置(0 当前,1 下一层,-1 上一层) |
方法
通过 ref 调用:
<card-stack ref="stackRef" :items="list" />
<script>
export default {
methods: {
goNext() {
this.$refs.stackRef.next(); // 切换到下一张
},
goPrev() {
this.$refs.stackRef.prev(); // 切换到上一张
},
},
};
</script>
| 方法 |
说明 |
next() |
切换到下一张(左滑) |
prev() |
切换到上一张(右滑) |
使用示例
1. 基础卡片堆叠
<card-stack :items="list" height="420rpx">
<template #card="{ item }">
<view class="card">{{ item.title }}</view>
</template>
</card-stack>
2. 自动播放
<card-stack
:items="list"
:autoplay="true"
:autoplay-interval="3000"
height="420rpx"
>
<template #card="{ item }">
<view class="card">{{ item.title }}</view>
</template>
</card-stack>
3. 用户滑动后 3 秒恢复自动播放
<card-stack
:items="list"
:autoplay="true"
:autoplay-interval="3000"
:stop-on-user-swipe="false"
:resume-autoplay-delay="3000"
height="420rpx"
>
<template #card="{ item }">
<view class="card">{{ item.title }}</view>
</template>
</card-stack>
4. 卡片点击
<card-stack
:items="list"
height="420rpx"
@cardClick="onCardClick"
>
<template #card="{ item }">
<view class="card">{{ item.title }}</view>
</template>
</card-stack>
<script>
export default {
methods: {
onCardClick(e) {
console.log('点击了:', e.item);
},
},
};
</script>
自动播放行为说明
| 用户操作 |
自动播放行为 |
| 触摸屏幕(未滑动) |
暂停 |
| 滑动后回弹(没滑走) |
立即恢复 |
| 滑动后滑走 |
stopOnUserSwipe: true 永久停止
false 延迟 resumeAutoplayDelay 后恢复 |
| 触摸但横轴判定失败 |
立即恢复 |
注意事项
- 卡片内容高度:建议在插槽内使用固定高度的卡片,避免布局跳动
- 图片性能:图片较大时建议使用 CDN 压缩参数,减少渲染开销
items 变化:列表变化会自动重启自动播放
loop 为 false:滑到边界时会触发回弹,不会循环
兼容性
| 平台 |
支持 |
| App |
✅ |
| H5 |
✅ |
| 微信小程序 |
✅ |
| 支付宝小程序 |
✅ |
| 抖音小程序 |
✅ |
更新日志
详见 changelog.md
License
MIT