一、 背景说明
开发一个可自定义组件化门户配置页面,期间采用了vue框架作为前端视图引擎,作为一个刚入手vue的萌新,开发第一个功能就遇到了拦路虎。需要一个拖动并且可改变大小的容器盒子。当时查看vue开发文档,查找github都没找到一个自己欢心的实现,所以与其求人,还不如求己。所以vuedragx这个轮子就有了,x
代表它不止可拖动。
github地址:https://github.com/464884492/...
二、效果图
三、开发思路
- 通过鼠标移动实现组件移动,改变大小,一定需要操作dom,查看vue官方文档,从实用性,已经通用性,选择开发一个自定义vue指令
- 通过鼠标移动产生的位移,动态改变大小或位置
- 通过事件通知方式,实现更新bind值
所以有了以上思路,就需要一次掌握三个重要知识
- vue 如果开发一个自定义指令
- 鼠标移动过程中,MouseEvent对象各种值的含义
- 如何使用并分发一个自定义事件
3.1 vue指令
记住,不管使用什么样的框架,需要学习某种技能,需要学习api各种方法的用法,最好的途径就是查看对应官方文档
vue 开发自定义指令地址 https://cn.vuejs.org/v2/guide...
通读一篇,然后敲黑板画重点
3.1.1 什么时候使用Vue指令
官方是么说的 在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令
显而易见,通过需要使用鼠标产生的位移,通过啥啥实在是比较为难在下了
3.1.2 如何开发
针对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...,开发vuedragx需要MouseEvent中最重要的两个属性MouseEvent.pageX
、MouseEvent.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 属性的别名.
所以,他们之间的关系应该是这样的
offsetX ,因为点击的document,所以此时offsetx=clientx=x,movementX 不好画图,读者就自己联想了。
3.3 自定义事件
用过jQuery的小伙伴,应该多少了解 trigger
`triggerHandler`方法,触发自定义事件,有了jq的帮助,自定义事件用的是那么得心应手,如丝版顺滑。做为对比,我们先看看jq是如何使用自定义事件的
// 添加一个适当的事件监听器
$('#foo').on('custom', function(event, param1, param2) {
alert(param1 + "\n" + param2);
});
$('#foo').trigger('custom', ['Custom', 'Event']);
对,就是这么简单,定义,传参一气呵成!
不过用Vue后,这些功能都要用原生的代码实现,业界也越来越强调原生代码呼声也越来越高。或许这句是es标准逐步完善,浏览器逐步兼容新api带来的利好。这里应该向IE6致敬(逃)。
言归正传,标准接口是如何使用自定义事件的呢?于是有找到了MDN https://developer.mozilla.org...,找到了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加上可容忍的边沿值做比较,分别分以下几种情况
- 左边 left-edge<pageX<left+edge&& top+edge<pageY<top+height-edge
- 右边 left+widht-edge<pageX<left+widht+edge&& top+edge<pageY<top+height-edge
- 上边 top-edge<pageY<top+edge && left+edge<pageX<left+width-edge
- 下边 top+heigth-edge<pageY<top+height+edge && left+edge<pageX<left+width-edge
- 左上角 top-edge<pageY<top+edge&&left-edge<pageX<left+edge
- 左下角 top+height-edge<pageY<top+height+edge&&left-edge<pageX<left+edge
- 右上角 top-edge<pageY<top+edge&&left+width-edge<pageX<left+width+edge
- 右下角 top+height-edge<pageY<top+height+edge&&left+width-edge<pageX<left+widht+edge
所以具体代码是这样的
判断当前鼠标是否在拖动对象上就没有那么费劲了,直接在onmousemove事件中通过 e.target.classList.contains(cfg.dragBarClass)
判断即可
4.2 调整大小
通过计算得到鼠标移动的deltx和delty值,分别更新width和heigth属性。当前在向左和向上调整大小,还需要调整对一个的left和height属性值
4.3 拖动
拖动,不改变目标对象大小,直接用计算的deltx和delty更新对应的left和top属性即可,如果需要限制移动区域,需要计算父容器对应内边距的坐标
function setConstraint(data) {
if (cfg.dragContainerId) {
let constraintDom = document.querySelector("#" + cfg.dragContainerId);
let constraintRect = constraintDom.getBoundingClientRect();
if (data.left <= 0) data.left = 0;
if (data.top <= 0) data.top = 0;
if (data.top + data.height+data.borderTop+data.borderBottom >= constraintRect.height) data.top = constraintRect.height - data.height-data.borderTop-data.borderBottom;
if (data.left + data.width+data.borderLeft+data.borderRight > constraintRect.width) data.left = constraintRect.width - data.width-data.borderLeft-data.borderRight;
}
}
五、总结
虽然功能不大,但要把每一个环节说清楚,感觉还是很费力。真羡慕那些会写书的开发人员,文档能力那是相当的好。写文档真的比不了写代码,不过还是要写,不然别人怎么知道你代码是做啥的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。