更新记录

1.0.0(2026-09-03) 下载此版本

翻转卡片组件


平台兼容性

uni-app

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟

其他

多语言 暗黑模式 宽屏模式

SL-WX-FlipCard 翻转卡片组件

组件简介

基于 CSS Transform 体系实现的点击翻转卡片组件。内置 7 种切换动画(左右翻转、上下翻转、旋风旋转、淡入淡出、底部滑入、右侧滑入、缩放切换),支持单列 / 多列网格布局、互斥展开(一次只翻一张)、正/背面插槽自定义、批量翻转与还原的对外 API。H5 / 微信小程序通用。

修复说明:在部分 WebView / 小程序环境下 backface-visibility: hidden 仅视觉透明,仍会拦截二次点击。组件已通过 opacity + visibility + pointer-events 三重控制 + 动画半程切换策略彻底解决「翻转后点不回来」的问题,7 种动画全部通过同一可见性机制保障交互一致性。

目录结构

components/SL-WX-FlipCard
└── SL-WX-FlipCard.vue

核心特性

  • 7 种内置动画:通过 animation 属性切换,涵盖 3D 翻牌类(flipY / flipX)、2D 趣味类(rotate)、轻量类(fade)、滑动类(slideUp / slideLeft)、透视缩放类(zoom)共 7 种风格。3D 类动画自动启用 perspective + preserve-3d,2D 类动画自动关闭 3D 上下文以减少合成层开销。

  • 单列 / 多列自由切换columnCount 设置列数(1\~N),内部使用 CSS Grid 自适应排列,列间距、行间距、外层内边距均可独立配置。

  • 点击翻转(全端统一):点击任意卡片在正/背面之间切换,H5 与小程序行为完全一致;所有动画均保证"翻转后二次点击必定翻回"。

  • 互斥展开exclusive = true 时,一次只能有一张卡片处于背面,适合「问答」「翻牌游戏」「单选卡片」等场景。

  • 任意宽高比:通过 aspectRatio(如 1 / 116 / 101 / 1.2)控制卡片形状。

  • 正/背面内容完全自定义:默认根据数据 key 自动渲染「图片 + 底部渐变标题/描述」,也可通过 #front / #back 具名插槽完全重写。

  • 无图模式自动适配:未提供图片字段时,正面白底深色字,背面浅灰底蓝字,居中对齐显示纯文字。

  • 对外方法:暴露 flip(index, flipped)reset(),通过 $refs 调用。

动画类型一览(animation prop)

动画值 名称 类型 效果说明 适用场景
flipY 左右翻转 3D 沿垂直中线做 Y 轴 180° 翻转(经典扑克牌翻牌),默认动画 商品卡片 / 相册 / 记忆翻牌游戏
flipX 上下翻转 3D 沿水平中线做 X 轴 180° 翻转 答题卡 / 门票/票据
rotate 旋风旋转 2D 绕中心旋转 180° 并搭配弹性缩放(cubic-bezier 回弹),正背面文字正向显示 抽奖 / 转盘 / 活泼的运营位
fade 淡入淡出 2D 正面淡出并放大至 1.1 倍,背面从 0.9 倍淡入至 1.0 倍 相册 / 资讯 / 视觉温和的场景
slideUp 底部滑入 2D 背面从卡片底部 100% 处滑入,正面同步向上滑出 列表项详情 / 问答 / 提示卡
slideLeft 右侧滑入 2D 背面从卡片右侧 100% 处滑入,正面同步向左滑出 横向分页式卡片 / 翻页式展示
zoom 缩放切换 2D 正面缩小至 0.2× 消失,背面从 0.2× 放大到 1.0× 出现(分阶段 opacity 平滑过渡) 照片集 / 作品展示 / 主题切换卡片

小提示:同一页面多次切换 animation 时,建议先调用 reset() 清空所有卡片的翻转态,避免处于"半翻转"中间态时动画算法不同导致的错位感。

Props

属性 类型 默认值 说明
list Array [] 数据列表
columnCount Number 2 列数,1 为单列,2+ 为多列
animation String 'flipY' 动画类型:flipY / flipX / rotate / fade / slideUp / slideLeft / zoom
aspectRatio String '1 / 1' 卡片宽高比(width / height),如 '16 / 10''1 / 1.2'
duration Number 600 动画时长(ms),可见性切换点自动取 duration/2
radius Number 16 卡片圆角(rpx)
columnGap Number 20 列间距(rpx)
rowGap Number 20 行间距(rpx)
padding Number 24 外层容器内边距(rpx)
exclusive Boolean false 是否互斥展开,true 时一次只能翻转一张
frontImageKey String 'frontImage' 正面图片字段名
frontTitleKey String 'frontTitle' 正面标题字段名
frontDescKey String 'frontDesc' 正面描述字段名
backImageKey String 'backImage' 背面图片字段名
backTitleKey String 'backTitle' 背面标题字段名
backDescKey String 'backDesc' 背面描述字段名

Events

事件名 回调参数 说明
itemtap { item, index, flipped } 点击卡片时触发,返回当前项数据、索引和翻转后的状态
onItemTap({ item, index, flipped }) {
  console.log('第' + index + '张,翻转后:' + flipped, item)
}

Slots

插槽名 作用域参数 说明
front { item, index } 自定义卡片正面内容(覆盖默认的「图片+标题+描述」渲染)
back { item, index } 自定义卡片背面内容(覆盖默认的「图片+标题+描述」渲染)

对外方法

<sl-wx-flip-card ref="flip" :list="list" animation="zoom" />
方法名 参数 说明
flip (index?, flipped?) 手动翻转。传 index 翻转指定卡片;不传(或传 null)则作用于全部;flipped = true/false 强制指定状态,不传则切换
reset - 将所有卡片还原到正面
this.$refs.flip.flip(2, true)    // 第 3 张翻到背面
this.$refs.flip.flip(0)          // 切换第 0 张
this.$refs.flip.flip(null, true) // 全部翻开
this.$refs.flip.reset()          // 全部还原

使用示例

示例 1:2 列图片卡片 + 缩放动画(zoom)

<template>
  <sl-wx-flip-card
    ref="flip"
    :list="list"
    :column-count="2"
    animation="zoom"
    :aspect-ratio="'1 / 1'"
    :duration="550"
    :column-gap="16"
    :row-gap="16"
    @itemtap="onTap"
  />
</template>

<script>
import SlWxFlipCard from '@/components/SL-WX-FlipCard/SL-WX-FlipCard.vue'

export default {
  components: { SlWxFlipCard },
  data() {
    return {
      list: [
        {
          frontImage: 'https://picsum.photos/seed/a/600/600',
          frontTitle: '雪山之境',
          frontDesc: '点击缩放查看',
          backImage: 'https://picsum.photos/seed/ab/600/600',
          backTitle: '喜马拉雅',
          backDesc: '海拔 8848 米,常年积雪。'
        },
        {
          frontImage: 'https://picsum.photos/seed/b/600/600',
          frontTitle: '城市夜景',
          frontDesc: '霓虹闪烁',
          backImage: 'https://picsum.photos/seed/bb/600/600',
          backTitle: '上海外滩',
          backDesc: '万国建筑博览群。'
        }
      ]
    }
  },
  methods: {
    onTap({ item, index, flipped }) {
      console.log(item, index, flipped)
    }
  }
}
</script>

示例 2:问答卡片 + 旋风动画(rotate) + 互斥

<template>
  <sl-wx-flip-card
    :list="qaList"
    :column-count="3"
    animation="rotate"
    :aspect-ratio="'1 / 1.2'"
    :exclusive="true"
    front-title-key="q"
    back-title-key="aTitle"
    back-desc-key="a"
  />
</template>

<script>
export default {
  data() {
    return {
      qaList: [
        { q: '1+1=?', aTitle: '答案:2', a: '十进制加法的基础结果。' },
        { q: '地球是圆的吗?', aTitle: '答案:是', a: '两极稍扁、赤道略鼓的椭球体。' },
        { q: '水的化学式?', aTitle: '答案:H₂O', a: '2 个氢原子 + 1 个氧原子。' }
      ]
    }
  }
}
</script>

示例 3:横向滑入动画(slideLeft) + 单列大卡片

<template>
  <sl-wx-flip-card
    :list="list"
    :column-count="1"
    animation="slideLeft"
    :aspect-ratio="'16 / 10'"
    :radius="20"
    :duration="500"
  />
</template>

示例 4:淡入淡出 + 插槽自定义内容

<template>
  <sl-wx-flip-card
    :list="list"
    :column-count="2"
    animation="fade"
    :duration="500"
  >
    <template #front="{ item }">
      <view class="front">
        <image :src="item.cover" mode="aspectFill" class="front__img" />
        <view class="front__tag">{{ item.tag }}</view>
      </view>
    </template>
    <template #back="{ item }">
      <view class="back">
        <text class="back__price">¥ {{ item.price }}</text>
        <text class="back__desc">{{ item.detail }}</text>
        <button size="mini" type="primary">立即购买</button>
      </view>
    </template>
  </sl-wx-flip-card>
</template>

示例 5:外部控制 + 上下翻转(flipX)

<template>
  <view>
    <view class="btns">
      <button @tap="$refs.flip.flip(null, true)">全部翻开</button>
      <button @tap="$refs.flip.flip(null, false)">全部正面</button>
      <button @tap="$refs.flip.reset()">全部还原</button>
    </view>
    <sl-wx-flip-card
      ref="flip"
      :list="list"
      :column-count="3"
      animation="flipX"
    />
  </view>
</template>

动画实现原理简述

所有 7 种动画共用同一套可见性切换机制,保证交互统一且不会出现"点不回来":

  1. 可见性切换时机visibilitypointer-events 使用 0s duration 的 transition,延迟到 duration/2 执行——即在动画半程(卡片垂直于屏幕 / 两面都不可见的瞬间)完成事件命中面的切换。
  2. 正反双向延迟方向不同:未翻转 → 翻转过程中,前半程点击仍命中正面、后半程命中背面;反向同理,避免刚点下一瞬间被另一张面抢走事件。
  3. 3D 与 2D 的区分:外层 .sl-flip-cell--3d(flipY / flipX)才加 perspective: 1200px.sl-flip-cell--2d(其余 5 种)关闭,减少小程序 WebView 不必要的 GPU 合成层开销。

注意事项

  1. 3D 兼容性perspectivetransform-style: preserve-3dbackface-visibility 已加 -webkit- 前缀。若低端 Android 机型在 flipY / flipX 下偶现闪烁,可缩短 duration400~500ms,或改用 2D 动画(rotate / fade / slideX / zoom)。

  2. 切换动画时请 reset:运行中从一种动画切到另一种时(例如演示页的切换条),建议先调用 $refs.flip.reset() 把所有卡还原,避免处于翻转态的卡片因两套变换算法差异产生视觉"跳变"。

  3. 二次点击必响应:7 种动画均通过同一套 opacity + visibility + pointer-events 在半程(duration/2)切换命中面,保证正面 → 背面 → 再正面的完整循环链路畅通。

  4. 字段 key 映射:默认渲染依赖 6 个字段;若业务字段名不同,用 frontImageKey 等重映射或直接用 #front / #back 插槽自定义。

  5. 列数切换:动态改 columnCount 不会清已翻状态。若要切列数时一起重置,watch columnCount 后调 $refs.flip.reset()

  6. 互斥 + 批量 flipexclusive = trueflip(null, true) 只会让最后一张翻转(语义约束)。若要全翻开,先把 exclusivefalse

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议