原文来自:juejin.cn/post/6908404553431908365,作者 羊村长
基于 vite + vue3 + composition api 做的卡片拖拽,代码相对简洁
大帅刚做了一版类似探探的飞卡效果组件,十分炫酷!
只可惜不是vue3版本,下面带大家看看如何正确搬运到vue3中。
绝对抄袭,如有不同,纯属巧合😁
视频版
飞卡原理
核心点有三:卡片堆叠布局、拖动卡片和飞卡
布局主要利用z-index和absolute定位;
拖动主要利用几个touch事件:touchstart,touchmove,touchcancel,touchend;
飞卡主要利用勾股定理😁
详情参见原文,不再赘述。
组件化
这里抽取组件是核心,先看看FlyCard组件template中的结构:
@touchstart="touchStart"
@touchmove="touchMove"
@touchcancel="touchCancel"
@touchend="touchCancel">
复制代码
注意这里省略了所有样式,替换所有view为div,每张卡片预留了具名插槽方便外界传入内容进来。
只有卡片1需要监听事件,最后预留一张空卡等待“上位”😁
那么,使用FlyCard组件时,需要使用v-slot指令分发内容,来看看demo-tantan.vue
复制代码
这里的分发内容进去,完整写法应该是
注意这里使用vite,图片src是动态设置的,需要做特殊处理,否则不能正常显示:
import img1 from "../assets/1.jpg";
复制代码cards: [{img: img1}]
复制代码
逻辑代码拆分
目前FlyCard接近400行,不太容易维护了,我们可以用Composition API拆分它们。
观察一下不难发现,拖动逻辑只有卡片1需要,所以这一部分的数据和逻辑控制是独立的,完全可拆分出来。
因此创建use/touch.js,抽取这部分逻辑代码,思路是:
抽取useTouch函数,接收卡片属性和回调函数等
响应数据就是上面的left,top这些
控制它们的逻辑是touchStart这些
组织在一起并导出供外界使用,日后还能复用在其他项目
抽取useTouch,接口如下:
function useTouch(props, {
onDragStart,
onDragMove,
onDragStop,
onThrowStart,
onThrowDone,
onThrowFail,
}) {}
复制代码
传入卡片属性后面计算逻辑要用到,还要留出事件回调,这样外界可以做一些额外事情:
响应式数据创建
const cardOneState = reactive({
left: 0,
top: 0,
startLeft: 0,
startTop: 0,
isDrag: false,
isThrow: false,
needBack: false,
isAnimating: false,
})
复制代码
控制逻辑:替换大量this.xxx,类似下面这样:
function touchStart(e) {
if (cardOneState.isAnimating) return;
cardOneState.isDrag = true;
cardOneState.needBack = false;
cardOneState.isThrow = false;
// ......
}
复制代码
这里有一个例外是getDistance方法,这是一个工具方法,外界不需要它,完全可以放到utils中去。
下面是飞卡逻辑和卡片回弹逻辑,它们需要处理另外几张卡的状态
const otherCardsState = reactive({
left2: 0,
top2: 0,
width2: 0,
height2: 0,
// ...
});
function resetAllCardDown() {/*...*/}
function resetAllCard() {/*...*/}
function makeCardThrow() {/*...*/}
function makeCardBack() {/*...*/}
复制代码
生命周期钩子处理
import { onMounted } from "vue";
function useTouch() {
// ...
onMounted(() => {
resetAllCard()
})
}
复制代码
最后导出接口:
return {
...toRefs(cardOneState),
...toRefs(otherCardsState),
touchStart,
touchMove,
touchCancel,
};
复制代码
重构完成,useTouch()长这样
组件内使用
下面在FlyCard里面使用useTouch,额外暴露一下emits选项,组件输入输出更明确。
import useTouch from "../use/touch";
export default {
props: {},
emits: [
"onDragStart",
"onDragMove",
"onDragStop",
"onThrowFail",
"onThrowStart",
"onThrowDone",
],
setup(props, { emit }) {
const touchState = useTouch(props, {
onDragStart: () => emit("onDragStart"),
onDragMove: (obj) => emit("onDragMove", obj),
onDragStop: (obj) => emit("onDragStop", obj),
onThrowFail: () => emit("onThrowFail"),
onThrowStart: () => emit("onThrowStart"),
onThrowDone: () => emit("onThrowDone"),
});
return { ...touchState };
},
};
复制代码
可以看到FlyCard组件简洁多了,组件由接近400行缩减至200行
敲黑板
重构完成了,这是我们使用vue3 composition api的一次小实践,好处显而易见:
我们的组件更简洁、易维护了
我们的业务逻辑可复用了
我们的代码完全消除了this,更有利于支持ts
重构过程我们加强了对业务的理解,这些代码都不是我写的,但是我很快就搞清楚了组件真正需要的接口有哪些,哪些方法只是touch内部需要并不需要暴露出去的。
思考
大家观察其他卡片的操作代码,不难发现,它们很有规律,应该很容易进一步抽象成更加通用、可复用的逻辑,比如我能不能动态指定卡片的数量,而不是像现在这样写死,这样大大限制了它的通用性。这个留给大家实现,可以给我的项目提pr。
代码仓库
视频版
作者:杨村长
链接:juejin.cn/post/6908404553431908365
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。