一个能拖动,能调整大小,能更新bind值的vue指令-vuedragx

一、 背景说明

开发一个可自定义组件化门户配置页面,期间采用了vue框架作为前端视图引擎,作为一个刚入手vue的萌新,开发第一个功能就遇到了拦路虎。需要一个拖动并且可改变大小的容器盒子。当时查看vue开发文档,查找github都没找到一个自己欢心的实现,所以与其求人,还不如求己。所以vuedragx这个轮子就有了,x代表它不止可拖动。
github地址:https://github.com/464884492/vuedragx

二、效果图

Alt text

三、开发思路

  • 通过鼠标移动实现组件移动,改变大小,一定需要操作dom,查看vue官方文档,从实用性,已经通用性,选择开发一个自定义vue指令
  • 通过鼠标移动产生的位移,动态改变大小或位置
  • 通过事件通知方式,实现更新bind值

所以有了以上思路,就需要一次掌握三个重要知识

  1. vue 如果开发一个自定义指令
  2. 鼠标移动过程中,MouseEvent对象各种值的含义
  3. 如何使用并分发一个自定义事件

3.1 vue指令

记住,不管使用什么样的框架,需要学习某种技能,需要学习api各种方法的用法,最好的途径就是查看对应官方文档

vue 开发自定义指令地址 https://cn.vuejs.org/v2/guide/custom-directive.html
通读一篇,然后敲黑板画重点

  1. 什么时候使用Vue指令

官方是么说的 在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令

显而易见,通过需要使用鼠标产生的位移,通过啥啥实在是比较为难在下了

  1. 如何开发

针对vue指令,官方给开发这提供了几个勾子函数,来注入开发者开发的功能,所谓的钩子函数,个人理解也就是在正常的代码中插入预埋的未实现的函数接口

vue对开发着提供(bind、inserted、update、componentUpdated、unbind)注入点。

  • bind 只调用一次,指令第一次绑定到元素时调用,主要完成指令初始化设置
  • inserted 被绑定元素插入父节点时调用,官网对这里有一个强调 仅保证父节点存在,但不一定被插入文档中 个人认为这个保证父节点,是只存在与所谓的虚dom中,只是没有实际应用与真实的dom中,不知道这个理解是否正确?
  • update 所在组件的VNode更新时调用,但也可能发生在其子VNode更新之前,所以提供的参数中包含 vnode oldVnode,具体是否需要响应操作,可以比较这两个node中对应的值是否变化
  • componentUpdated 指令所在组件的 VNode极其子VNode全部更新后调用,此钩子函数的补充,就可以解决update钩子,不及时更新问题。
  • unbind 只调用一次,指令与元素解绑是调用

每一个钩子都带有参数 el、binding、vnode、oldVnode

  • el 当前绑定的dom
  • binding 对象,包含以下属性
    • name 指令名称,不过个人理解此参数感觉没有使用的场景
    • value 指令bind的值,如果是可运行的表达式,将返回计算结果。如果绑定的是一个对象,将返回此对象,vuedragx,采用此属性传入个性化配置信息。
    • oldValue 指令绑定的前一个值,仅在 update 和 componentUpdated 钩子函数中可用
    • expression 字符串形式的指令表达式,就是在代码开发中v-dragx=”A”,这个A就是表达式
    • arg 传给指令的参数,如 v-dragx:foo,参数为foo,注意标识符 :
    • modifiers 一个包含修饰符的对象,如:v-dragx.dragable.resizeable,修饰符对象为{dragable:true,resizeable:true},不过vuedragx并没有使用此方法传值,统一通过binding.value传值
    • vnode Vue 编译生成的虚拟节点
    • oldVnode 上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用

关于vue指令的钩子函数,参数等相关说明,基本从官网引用过来的。我觉得有必要再次在强调以下,使用第三方的框架,最好途径就是查阅官方文档。其次可以看看对应的源码,如果有的话。

3.2 MouseEvent

上边在vue指令介绍中,强调了查阅官方文档的重要性,那么针对javascript的api又该在那里查阅呢?当然是 MDN,没有第二

MDN中关于MouseEvent说明的地址: https://developer.mozilla.org/zh-CN/docs/Web/API/MouseEvent,开发vuedragx需要MouseEvent中最重要的两个属性MouseEvent.pageXMouseEvent.pageY

  • MouseEvent.pageX 相对于整个文档的x(水平)坐标。此属性包含了元素滚动的距离 ,官方解释为 这个属性将基于文档的边缘,考虑任何页面的水平方向上的滚动。举个例子,如果页面向右滚动 200px 并出现了滚动条,这部分在窗口之外,然后鼠标点击距离窗口左边 100px 的位置,pageX 所返回的值将是 300。

做为延伸,分别查看了MonuseEvent中其他对应坐标的属性

  • MouseEvent.clientX 提供事件发生时的应用客户端区域的水平坐标 (与页面坐标PageX不同,clientX不包含滚动条的距离),所以pageX =clientX+横向滚动的距离
  • MouseEvent.movementX 它提供了当前事件和上一个mousemove事件之间鼠标在水平方向上的移动值,此值可以获取每次鼠标移动的增量,在开发vuedargx没有关注此属性,不然可减少一部分计算代码了。
  • MouseEvent.offsetX 规定了事件对象与目标节点的内填充边(padding edge)在 X 轴方向上的偏移量
  • MouseEvent.screenX 供了鼠标相对于屏幕坐标系的水平偏移量 不包含滚动调距离
  • MouseEvent.x 是 MouseEvent.clientX 属性的别名.

所以,他们之间的关系应该是这样的

Alt text

offsetX ,因为点击的document,所以此时offsetx=clientx=x,movementX 不好画图,读者就自己联想了。

3.3 自定义事件

用过jQuery的小伙伴,应该多少了解 trigger``triggerHandler方法,触发自定义事件,有了jq的帮助,自定义事件用的是那么得心应手,如丝版顺滑。做为对比,我们先看看jq是如何使用自定义事件的

1 // 添加一个适当的事件监听器
2 $('#foo').on('custom', function(event, param1, param2) {
3 alert(param1 + "\n" + param2);
4 });
5 $('#foo').trigger('custom', ['Custom', 'Event']);

对,就是这么简单,定义,传参一气呵成!
不过用Vue后,这些功能都要用原生的代码实现,业界也越来越强调原生代码呼声也越来越高。或许这句是es标准逐步完善,浏览器逐步兼容新api带来的利好。这里应该向IE6致敬(逃)。
言归正传,标准接口是如何使用自定义事件的呢?于是有找到了MDN https://developer.mozilla.org/zh-CN/docs/Web/API/CustomEvent,找到了demo

// 添加一个适当的事件监听器
obj.addEventListener("custom", function(e) {
console.log(JSON.stringify(e.detail));
})
// 创建并分发事件
var event = new CustomEvent("custom", {"detail":{"Custom":true}});
obj.dispatchEvent(event)

在我看来,定义事件的样子,基本没变,唯一参数传递变得复杂了。总有那么一丢丢不美好。

四、实现

4.1 操作判断

鼠标在目标对象上下左边边缘移动时,鼠标需要显示不同的resize样式,提醒用户当前可操作的类型。基本思路是获取当前对象的在文档对应的left和top值,与上边讲解的MouseEvent中的 pageX、pageY加上可容忍的边沿值做比较,分别分以下几种情况

  1. 左边 left-edge<pageX<left+edge&& top+edge<pageY<top+height-edge
  2. 右边 left+widht-edge<pageX<left+widht+edge&& top+edge<pageY<top+height-edge
  3. 上边 top-edge<pageY<top+edge && left+edge<pageX<left+width-edge
  4. 下边 top+heigth-edge<pageY<top+height+edge && left+edge<pageX<left+width-edge
  5. 左上角 top-edge<pageY<top+edge&&left-edge<pageX<left+edge
  6. 左下角 top+height-edge<pageY<top+height+edge&&left-edge<pageX<left+edge
  7. 右上角 top-edge<pageY<top+edge&&left+width-edge<pageX<left+width+edge
  8. 右下角 top+height-edge<pageY<top+height+edge&&left+width-edge<pageX<left+widht+edge

所以具体代码是这样的

Alt text
判断当前鼠标是否在拖动对象上就没有那么费劲了,直接在onmousemove事件中通过
e.target.classList.contains(cfg.dragBarClass)判断即可

4.2 调整大小

通过计算得到鼠标移动的deltx和delty值,分别更新width和heigth属性。当前在向左和向上调整大小,还需要调整对一个的left和height属性值

4.3 拖动

拖动,不改变目标对象大小,直接用计算的deltx和delty更新对应的left和top属性即可,如果需要限制移动区域,需要计算父容器对应内边距的坐标

 1 function setConstraint(data) {
 2 if (cfg.dragContainerId) {
 3 let constraintDom = document.querySelector("#" + cfg.dragContainerId);
 4 let constraintRect = constraintDom.getBoundingClientRect();
 5 if (data.left <= 0) data.left = 0;
 6 if (data.top <= 0) data.top = 0;
 7 if (data.top + data.height+data.borderTop+data.borderBottom >= constraintRect.height) data.top = constraintRect.height - data.height-data.borderTop-data.borderBottom;
 8 if (data.left + data.width+data.borderLeft+data.borderRight > constraintRect.width) data.left = constraintRect.width - data.width-data.borderLeft-data.borderRight;
 9 }
10 }

五、总结

虽然功能不大,但要把每一个环节说清楚,感觉还是很费力。真羡慕那些会写书的开发人员,文档能力那是相当的好。写文档真的比不了写代码,不过还是要写,不然别人怎么知道你代码是做啥的。

猜你喜欢

转载自www.cnblogs.com/yfrs/p/vuedragx.html