{"id":19750518,"url":"https://github.com/57code/flycard-in-vue3","last_synced_at":"2025-07-25T23:07:25.901Z","repository":{"id":97536811,"uuid":"322224652","full_name":"57code/flycard-in-vue3","owner":"57code","description":"飞卡特效vue3版","archived":false,"fork":false,"pushed_at":"2020-12-21T02:16:30.000Z","size":500,"stargazers_count":27,"open_issues_count":0,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-10T21:27:46.498Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Vue","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/57code.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-12-17T08:08:30.000Z","updated_at":"2024-07-17T08:33:04.000Z","dependencies_parsed_at":"2023-03-04T16:30:36.772Z","dependency_job_id":null,"html_url":"https://github.com/57code/flycard-in-vue3","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/57code%2Fflycard-in-vue3","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/57code%2Fflycard-in-vue3/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/57code%2Fflycard-in-vue3/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/57code%2Fflycard-in-vue3/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/57code","download_url":"https://codeload.github.com/57code/flycard-in-vue3/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241080458,"owners_count":19906299,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-12T02:35:51.207Z","updated_at":"2025-02-28T00:46:34.033Z","avatar_url":"https://github.com/57code.png","language":"Vue","readme":"## 备战2021，仿探探拖拽卡片效果Vue3实现\n\n大帅刚做了一版类似`探探`的`飞卡`效果组件，十分炫酷！\n\n只可惜不是vue3版本，下面带大家看看如何正确搬运到vue3中。\n\n`绝对抄袭，如有不同，纯属巧合`😁\n\n\n![Dec-18-2020 11-07-03](https://gitee.com/57code/picgo/raw/master/Dec-18-2020%2011-07-03.gif)\n\n\n\n### 飞卡原理\n\n核心点有三：`卡片堆叠布局`、`拖动卡片`和`飞卡`\n\n布局主要利用`z-index`和`absolute`定位；\n\n拖动主要利用几个touch事件：`touchstart`,`touchmove`,`touchcancel`,`touchend`；\n\n飞卡主要利用`勾股定理`😁\n\n详情参见[原文](https://juejin.cn/post/6906143905922678797)，不再赘述。\n\n\n\n### 组件化\n\n这里抽取组件是核心，先看看`FlyCard`组件template中的结构：\n\n```html\n\u003cdiv\u003e\n  \u003cdiv\u003e\n    \u003cdiv class=\"card\"\n         @touchstart=\"touchStart\"\n         @touchmove=\"touchMove\"\n         @touchcancel=\"touchCancel\"\n         @touchend=\"touchCancel\"\u003e\n      \u003cslot name=\"firstCard\"\u003e\u003c/slot\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"card\"\u003e\n      \u003cslot name=\"secondCard\"\u003e\u003c/slot\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"card\"\u003e\n      \u003cslot name=\"thirdCard\"\u003e\u003c/slot\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"card\"\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\n\u003e 注意这里省略了所有样式，替换所有`view`为`div`，每张卡片预留了`具名插槽`方便外界传入内容进来。\n\u003e\n\u003e 只有卡片1需要监听事件，最后预留一张空卡等待“上位”😁\n\n\n\n那么，使用`FlyCard`组件时，需要使用`v-slot`指令分发内容，来看看`demo-tantan.vue`\n\n```html\n\u003cfly-card\u003e\n  \u003ctemplate #firstCard\u003e\n    \u003cdiv v-if=\"cards[0]\" class=\"tantanCard\"\u003e\n      \u003cimg :src=\"cards[0].img\" mode=\"aspectFill\" /\u003e\n    \u003c/div\u003e\n  \u003c/template\u003e\n  \u003c!--省略其他几个template--\u003e\n\u003c/fly-card\u003e  \n```\n\n\u003e 这里的`\u003ctemplate #firstCard\u003e`分发内容进去，完整写法应该是`\u003ctemplate v-slot:firstCard\u003e`\n\n\u003e 注意这里使用`vite`，图片`src`是动态设置的，需要做特殊处理，否则不能正常显示：\n\u003e\n\u003e ```js\n\u003e import img1 from \"../assets/1.jpg\";\n\u003e ```\n\u003e\n\u003e ```js\n\u003e cards: [{img: img1}]\n\u003e ```\n\n\n\n### 逻辑代码拆分\n\n目前FlyCard接近`400`行，不太容易维护了，我们可以用`Composition API`拆分它们。\n\n观察一下不难发现，拖动逻辑只有`卡片1`需要，所以这一部分的数据和逻辑控制是独立的，完全可拆分出来。\n\n\u003cimg src=\"https://gitee.com/57code/picgo/raw/master/image-20201218090053511.png\" alt=\"image-20201218090053511\" style=\"zoom:25%;\" /\u003e\n\n\u003cimg src=\"https://gitee.com/57code/picgo/raw/master/image-20201218090302103.png\" alt=\"image-20201218090302103\" style=\"zoom:30%;\" /\u003e\n\n\n\n因此创建`use/touch.js`，抽取这部分逻辑代码，思路是：\n\n- 抽取useTouch函数，接收卡片属性和回调函数等\n- 响应数据就是上面的left，top这些\n- 控制它们的逻辑是touchStart这些\n- 组织在一起并导出供外界使用，日后还能复用在其他项目\n\n\n\n抽取`useTouch`，接口如下：\n\n```js\nfunction useTouch(props, {\n  onDragStart,\n  onDragMove,\n  onDragStop,\n  onThrowStart,\n  onThrowDone,\n  onThrowFail,\n}) {}\n```\n\n\u003e 传入卡片属性后面计算逻辑要用到，还要留出事件回调，这样外界可以做一些额外事情：\n\n\n\n响应式数据创建\n\n```js\nconst cardOneState = reactive({\n  left: 0,\n  top: 0,\n  startLeft: 0,\n  startTop: 0,\n  isDrag: false,\n  isThrow: false,\n  needBack: false,\n  isAnimating: false,\n})\n```\n\n\n\n控制逻辑：替换大量`this.xxx`，类似下面这样：\n\n```js\nfunction touchStart(e) {\n  if (cardOneState.isAnimating) return;\n  cardOneState.isDrag = true;\n  cardOneState.needBack = false;\n  cardOneState.isThrow = false;\n  // ......\n}\n```\n\n\u003e 这里有一个例外是`getDistance`方法，这是一个工具方法，外界不需要它，完全可以放到utils中去。\n\n\n\n下面是飞卡逻辑和卡片回弹逻辑，它们需要处理另外几张卡的状态\n\n```js\nconst otherCardsState = reactive({\n  left2: 0,\n  top2: 0,\n  width2: 0,\n  height2: 0,\n  // ...\n});\n\nfunction resetAllCardDown() {/*...*/}\nfunction resetAllCard() {/*...*/}\nfunction makeCardThrow() {/*...*/}\nfunction makeCardBack() {/*...*/}\n```\n\n\n\n生命周期钩子处理\n\n```js\nimport { onMounted } from \"vue\";\n\nfunction useTouch() {\n  // ...\n  onMounted(() =\u003e {\n    resetAllCard()\n  })\n}\n```\n\n\n\n最后导出接口：\n\n```js\nreturn {\n  ...toRefs(cardOneState),\n  ...toRefs(otherCardsState),\n  touchStart,\n  touchMove,\n  touchCancel,\n};\n```\n\n\n\n重构完成，useTouch()长这样\n\n\u003cimg src=\"https://gitee.com/57code/picgo/raw/master/image-20201218105201015.png\" alt=\"image-20201218105201015\" style=\"zoom:30%;\" /\u003e\n\n\u003cimg src=\"https://gitee.com/57code/picgo/raw/master/image-20201218105251740.png\" alt=\"image-20201218105251740\" style=\"zoom:30%; \" /\u003e\n\n\n\n### 组件内使用\n\n下面在`FlyCard`里面使用`useTouch`，额外暴露一下`emits`选项，组件`输入输出`更明确。\n\n```js\nimport useTouch from \"../use/touch\";\n\nexport default {\n  props: {},\n  emits: [\n    \"onDragStart\",\n    \"onDragMove\",\n    \"onDragStop\",\n    \"onThrowFail\",\n    \"onThrowStart\",\n    \"onThrowDone\",\n  ],\n  setup(props, { emit }) {\n    const touchState = useTouch(props, {\n      onDragStart: () =\u003e emit(\"onDragStart\"),\n      onDragMove: (obj) =\u003e emit(\"onDragMove\", obj),\n      onDragStop: (obj) =\u003e emit(\"onDragStop\", obj),\n      onThrowFail: () =\u003e emit(\"onThrowFail\"),\n      onThrowStart: () =\u003e emit(\"onThrowStart\"),\n      onThrowDone: () =\u003e emit(\"onThrowDone\"),\n    });\n    return { ...touchState };\n  },\n};\n```\n\n\u003e 可以看到FlyCard组件简洁多了，组件由接近400行缩减至200行\n\n\n\n### 敲黑板\n\n重构完成了，这是我们使用vue3 composition api的一次小实践，好处显而易见：\n\n- 我们的组件更简洁、易维护了\n- 我们的业务逻辑可复用了\n- 我们的代码完全消除了`this`，更有利于支持ts\n- 重构过程我们加强了对业务的理解，这些代码都不是我写的，但是我很快就搞清楚了组件真正需要的接口有哪些，哪些方法只是touch内部需要并不需要暴露出去的。\n\n\n\n### 思考\n\n大家观察其他卡片的操作代码，不难发现，它们很有规律，应该很容易进一步抽象成更加通用、可复用的逻辑，比如我能不能动态指定卡片的数量，而不是像现在这样写死，这样大大限制了它的通用性。这个留给大家实现，可以给[我的项目](https://github.com/57code/flycart-in-vue3)提pr。\n\n\u003cimg src=\"https://gitee.com/57code/picgo/raw/master/image-20201218112352669.png\" alt=\"image-20201218112352669\" style=\"zoom:30%;\" /\u003e\n\n\n\n### 代码仓库\n\nhttps://github.com/57code/flycart-in-vue3\n\n\n\n### 关注杨村长\n\n关于该案例就说到这里，希望抛砖引玉，引出更多好的内容出现。\n\n我近期的文章（感谢掘友的鼓励与支持🌹🌹🌹）：\n\n- [🔥又是一夜，这篇Composition-API实操还觉得短吗](https://juejin.im/post/6892017198450081800) 198👍\n- [🔥拿下vue3你要做好这些准备](https://juejin.im/post/6866373381424414734) 62👍\n- [🔥闪电五连鞭：Composition API原理深度剖析](https://juejin.im/post/6894993303486332941) 49👍\n- [🔥我的非凡2020 | 掘金年度征文](https://juejin.cn/post/6904058925482672141) 35👍\n\n我的视频教程（感谢掘友的鼓励与支持🌹🌹🌹）：\n\n- [【全网首发】Vue3.0光速上手「持续更新中」](https://www.bilibili.com/video/BV1Wh411X7Xp) 523👍\n- [【面霸养成】天天造轮子 (每天一更，建议收藏)](https://www.bilibili.com/video/BV13v411C7VC) 69👍\n- [【源码解读】村长vue3源码剖析](https://www.bilibili.com/video/BV1iT4y137yj) 60👍\n- [【快乐1024】给程序员同胞在线发老婆！](https://www.bilibili.com/video/BV16z4y1o7Wg)31👍\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F57code%2Fflycard-in-vue3","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F57code%2Fflycard-in-vue3","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F57code%2Fflycard-in-vue3/lists"}