[转]仿探探拖拽卡片效果Vue3实现

Vue.js

原文来自:juejin.cn/post/6908404553431908365,作者 羊村长
基于 vite + vue3 + composition api 做的卡片拖拽,代码相对简洁

大帅刚做了一版类似探探飞卡效果组件,十分炫酷!

只可惜不是vue3版本,下面带大家看看如何正确搬运到vue3中。

绝对抄袭,如有不同,纯属巧合😁

视频版

喜欢看视频的同学移步这里: Vue 3.0 仿探探飞卡组件

飞卡原理

核心点有三:卡片堆叠布局拖动卡片飞卡

布局主要利用z-indexabsolute定位;

拖动主要利用几个touch事件:touchstart,touchmove,touchcancel,touchend

飞卡主要利用勾股定理😁

详情参见原文,不再赘述。

组件化

这里抽取组件是核心,先看看FlyCard组件template中的结构:

<div>
  <div>
    <div class="card"
         @touchstart="touchStart"
         @touchmove="touchMove"
         @touchcancel="touchCancel"
         @touchend="touchCancel">
      <slot name="firstCard"></slot>
    </div>
    <div class="card">
      <slot name="secondCard"></slot>
    </div>
    <div class="card">
      <slot name="thirdCard"></slot>
    </div>
    <div class="card">
    </div>
  </div>
</div>
复制代码

注意这里省略了所有样式,替换所有viewdiv,每张卡片预留了具名插槽方便外界传入内容进来。

只有卡片1需要监听事件,最后预留一张空卡等待“上位”😁

那么,使用FlyCard组件时,需要使用v-slot指令分发内容,来看看demo-tantan.vue

<fly-card>
  <template #firstCard>
    <div v-if="cards[0]" class="tantanCard">
      <img :src="cards[0].img" mode="aspectFill" />
    </div>
  </template>
  <!--省略其他几个template-->
</fly-card>  
复制代码

这里的<template #firstCard>分发内容进去,完整写法应该是<template v-slot:firstCard>

注意这里使用vite,图片src是动态设置的,需要做特殊处理,否则不能正常显示:

import img1 from "../assets/1.jpg";
复制代码
cards: [{img: img1}]
复制代码

逻辑代码拆分

目前FlyCard接近400行,不太容易维护了,我们可以用Composition API拆分它们。

观察一下不难发现,拖动逻辑只有卡片1需要,所以这一部分的数据和逻辑控制是独立的,完全可拆分出来。

image-20201218090053511 image-20201218090302103

因此创建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()长这样

image-20201218105201015 image-20201218105251740

组件内使用

下面在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。

image-20201218112352669

代码仓库

github.com/57code/flyc…

视频版

喜欢看视频的同学移步这里: Vue 3.0 仿探探飞卡组件

作者:杨村长
链接:juejin.cn/post/6908404553431908365
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!