使用vue自定义指令构建拖放插件

我们都知道html5的拖放特性,利用它可以很方便的实现拖拽和放置功能,比如一些选择类操作的使用场景,让用户去拖拽比鼠标点击更容易接受和理解。今天我们就利用这一特性,结合vue的自定义指令,来实现一个简单但是实用的拖放插件。

为什么叫它插件?因为我们的目标不是开发一个vue组件,而是两个vue的自定义指令,并且最终会把这两个自定义指令封装到一个es6的class里,在实际项目中引入就可以很方便的使用了。

大部分的拖放使用场景都是把一些待选元素从A区域拖放到B区域。这里就涉及到两个概念,一个是可拖拽,一个是可放置,待选元素一定是可以被拖拽的,而目标区域(容器)一定是可以放置的。

如果我们开发一个可拖拽的vue组件,或者开发一个可放置的组件,那仅仅是这个组件可拖放,此时如果需求变更,又需要另外一个组件也支持拖放,那我们仍需要为另一个组件也编写拖放的代码。又或者其他项目也需要拖放功能了,我们也要重新开发。这样非常不利于维护和复用,而vue的自定义指令很好的帮我们解决了这个问题,我们只需要在组件(包括普通的dom元素)上添加自定义指令,就可以使这个组件(元素)可拖放,这样就可以灵活的去使用了。

除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令。注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。

综上,本文的目标需要完成两个自定义指令:

  • v-drag 使组件可拖拽
  • v-drop 使组件可放置

目标已经很明确了,那就开始动手吧!由于我们要让这两个指令可在任意组件上发挥作用,因此需要注册Vue全局指令。

Vue.directive('drag', {
   bind(el, binding, vnode){
        //只调用一次,指令第一次绑定到元素时调用。
        //在这里可以进行一次性的初始化设置。
    }
})
Vue.directive('drop', {
   bind(el, binding, vnode){
        //
    }
})

如果你的项目是vue-cli搭建的,你可以把这段代码写在main.js里vue初始化的上方。

我们先在drag指令的bind钩子里编写代码,bind只调用一次,并且是在指令第一次绑定到元素时调用,因此我们用了bind钩子。这个指令的目标是让组件(元素)可拖拽,所以我们设置el的draggable为true

el.draggable = true;
el.ondragstart = (event)=>{
  event.dataTransfer.setData("Text", "your data...");
}

当元素被拖拽时,会先触发ondragstart事件,通常我们都会在这个事件里为event的dataTransfer设置拖拽数据,目的是当元素被放置时,目标容器可以获取拖拽过来的数据,如果拖放不能传递数据,那将是没有意义的。上面的代码调用dataTransfer的setData方法设置拖拽数据,setData的参数1表示数据类型,参数2表示要传递的数据。

很不幸,拖拽数据目前仅支持字符串,如果你想传递复杂对象,可以将数据序列化

接下来我们为drop指令的bind钩子编写代码,这个指令的目的是让组件(元素)可放置,因此我们需要为元素的ondragover(拖拽经过事件)、ondrop(放置事件)编写handler,这两个handler要阻止事件的默认行为。

el.ondragover = (event)=>{
  event.preventDefault(); //阻止默认行为
}
el.ondrop = (event)=>{
  event.preventDefault();
  let dragData = event.dataTransfer.getData('Text'); //获取拖拽数据
}

我们通过event.dataTransfer的getData方法可以获取到拖拽开始事件中设置的拖拽数据。

现在你就可以把这两个指令加到任何组件上了,加了v-drag的组件可以被拖动,加了v-drop的组件可以放置并接收拖拽数据。

<MyComponent v-drag></MyComponent>

<MyContainer v-drop></MyContainer>

新的问题来了,我们进行拖拽操作是为了传递数据,然而传递数据的开始阶段,我们是在自定义指令drag的bind钩子里进行的,传递数据的接收阶段,我们是在drop的bind钩子里进行的,那么,数据从哪儿来?到哪儿去?很显然,数据应该来自组件,也应该传递给另一个组件,否则我们把指令写到vue组件上就没有任何意义了。

好在自定义指令的钩子函数为我们提供了访问组件最简单有效的方式:那就是钩子函数的第三个参数vnode,vnode有一个属性是componentInstance,这个componentInstance就是自定义指令的宿主:vue组件实例!

接下来就很容易了,我们只需要为添加了v-drag的组件定义一个获取拖拽数据的接口,为添加了v-drop的组件定义一个接收拖拽数据的接口即可。虽然vue组件并不支持接口的定义,但我们可以约定好这两个方法名,在组件的method中进行实现即可。

//自定义组件内部
methods:{
  getDragData(){ //约定getDragData为获取组件拖拽数据的接口方法
    return this.id; //假设这个组件被拖拽时,需要将id传递出去
  }
  setDragData(data){ //约定setDragData为组件接收拖拽数据的接口方法
    this.appendNewChildById(data); //假设这个组件接收id来生成新元素
  }
}

然后改写我们自定义指令设置和传递拖拽数据的代码:

let dragValue = "";
if(vnode.componentInstance.getDragData != undefined){
  dragValue = vnode.componentInstance.getDragData();
}
event.dataTransfer.setData("Text", dragValue);

v-drop指令中的ondrop事件

let dragValue = event.dataTransfer.getData('Text');
if(vnode.componentInstance.setDragData != undefined){
  vnode.componentInstance.setDragData(dragValue);
}

我们在访问组件的接口方法时加了 if 判断,因为没有接口的约束,组件可能并没有实现这些方法。

好啦,到这里我们已经完全实现了组件拖放的自定义指令,虽然很简单,但是很实用也很灵活,基本可以满足日常拖拽的需求,让我们总结一下整个流程吧!

  • 自定义全局指令 v-drag、v-drop
  • 需要拖拽的组件实现获取数据的接口方法
  • 需要放置的组件实现接收数据的接口方法
  • drag指令访问组件的接口方法获取数据
  • drop指令访问组件的接口方法传递数据

我们将全局自定义指令的相关代码封装到一个es6的class里面,并作为一个单独的js文件放到项目里,或者发布到npm上,然后在main.js里导入这个类,调用静态初始化方法,即可完成全局指令的注册。这样一来,项目当中的任意组件都可以使用v-drag和v-drop了,上面总结的五个步骤,只需要实现第2、3条即可。

这个案例的全部代码我会抽时间发布到npm上,也会同步更新到我的github账户,如果大家急需,请在评论区留言进行索取,也欢迎大家针对这个案例发表不同意见。

欢迎大家在评论区留言自己想了解的前端话题,我会继续推出更多精彩的文章!

原创不易,有钱的捧个钱场,给个打赏

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容

  • 基于Vue的一些资料 内容 UI组件 开发框架 实用库 服务端 辅助工具 应用实例 Demo示例 element★...
    尝了又尝阅读 1,137评论 0 1
  • UI组件 element- 饿了么出品的Vue2的web UI工具套件 Vux- 基于Vue和WeUI的组件库 m...
    小姜先森o0O阅读 9,348评论 0 72
  • UI组件 element - 饿了么出品的Vue2的web UI工具套件 Vux - 基于Vue和WeUI的组件库...
    流过阅读 3,314评论 1 35
  • UI组件 element- 饿了么出品的Vue2的web UI工具套件 Vux- 基于Vue和WeUI的组件库 m...
    你猜_3214阅读 11,004评论 0 118
  • 11个超棒的 iOS 开发学习网站 iOS应用性能调优的25个建议和技巧 如何让iOS 保持界面流畅?这些技巧你知...
    Ice丶泽阅读 239评论 0 0