基于Vue 撸一个指令实现拖拽功能


Posted in Javascript onOctober 09, 2019

之前撸了一个copy 指令,这次再撸一个拖拽指令。。

具体是个什么蛇皮玩意儿呢,大概就像介样:

基于Vue 撸一个指令实现拖拽功能 

emmm。。没错,看起来就是如此的鸡肋,但是莫得办法,大佬喜欢啊。

由于我们项目中用的是 element-ui ,所有这个指令只针对 element-ui 的对话框组件哈,如果你们用的别的 ui 库也有这个需求的,涂涂改改应该也能用。。

其实这个拖拽的原理还是很简单的:

1.首先鼠标按下( onmousedown )

  • 记录目标元素当前的 left 和 top 值

2.鼠标移动( onmousemove )

  • 计算每次移动的横向距离 ( disX ) 和纵向距离 ( disY )
  • 并改变元素的 left ( left = left + disX )和 top ( top = top + disY )值

3.鼠标松开( onmouseup )

完成一次拖拽,做一些收尾工作

  • left 和 top 值容易获取,关键是 disX 和 disY 怎么计算呢?

容我先普及一哈:

  • clientX :表示鼠标当前的 X 坐标
  • clientY :表示鼠标当前的 Y 坐标

那么伪代码就是:

  • disX = 鼠标按下时的 clientX - 鼠标松开时的 clientX
  • disY = 鼠标按下时的 clientY - 鼠标松开时的 clientY

就这么简单,好了,下面就开始撸代码了。

// 这个助手方法下面会用到,用来获取 css 相关属性值
const getAttr = (obj, key) => (
  obj.currentStyle
  ? obj.currentStyle[key]
  : window.getComputedStyle(obj, false)[key]
);
const vDrag = {
  inserted(el) {
   /**
    * 这里是跟据 dialog 组件的 dom 结构来写的
    * target: dialog 组件的容器元素
    * header:dialog 组件的头部区域,也是就是拖拽的区域
    */
    const target = el.children[0];
    const header = target.children[0];
    // 鼠标手型
    header.style.cursor = 'move';
    header.onmousedown = (e) => {
      // 记录按下时鼠标的坐标和目标元素的 left、top 值
      const currentX = e.clientX;
      const currentY = e.clientY
      const left = parseInt(getAttr(target, 'left'));
      const top = parseInt(getAttr(target, 'top'));
      document.onmousemove = (event) => {
        // 鼠标移动时计算每次移动的距离,并改变拖拽元素的定位
        const disX = event.clientX - currentX;
        const disY = event.clientY - currentY;
        target.style.left = `${left + disX}px`;
        target.style.top = `${top + disY}px`;
        // 阻止事件的默认行为,可以解决选中文本的时候拖不动
        return false;
      }
      // 鼠标松开时,拖拽结束
      document.onmouseup = () => {
        document.onmousemove = null;
        document.onmouseup = null;
      };
    }
  },
  // 每次重新打开 dialog 时,要将其还原
  update(el) {
    const target = el.children[0];
    target.style.left = '';
    target.style.top = '';
  },
  // 最后卸载时,清除事件绑定
  unbind(el) {
    const header = el.children[0].children[0];
    header.onmousedown = null;
  },
};
export default vDrap;

这样就实现了 最简单 的拖拽了,这样就 ok 了吗? 当然不是,这样会有什么问题呢?就是如果用力过猛把整个弹框都拖到可视区域之外了,那就抠不出来了。

所以还得完善一下,判断四个方向的边界,如果超过边界值就不动了。边界值实际上就是在屏幕上能拖动的最大距离也就是 disX 和 disY 的最大值

  • 上边界: target.offsetTop
  • offsetTop :这里可以表示目标元素( target )上边框距离页面顶部的距离
  • 下边界: body.height - target.offsetTop - header.height
  • header.height :预留高度,表示往下可以拖到只留下可拖拽区域在外面
  • 左边界: target.offsetLeft + target.width - 50
  • offsetLeft :这里可以表示目标元素左边框距离页面左边的距离
  • 50 :表示预留的宽度,可以自己随便定只要大于 0 即可,表示往左再怎么拖也会留下 50px 的宽度在外面
  • 右边界: body.width - target.offsetLeft - 50

这里 50 同上,表示往左再怎么拖也会留下 50px 的宽度在外面

这里计算边界值的方法有多种,大家可以去尝试自己的想法。然后我粗略的画了一个图,帮助理解,虽然感觉只有我自己看得懂。哈哈。。。

基于Vue 撸一个指令实现拖拽功能 

下面用代码实现边界判断就 ok了

// ...
// 以上代码省略
header.onmousedown = (e) => {
  // ...
  // 以上代码省略
  // 分别计算四个方向的边界值
  const minLeft = target.offsetLeft + parseInt(getAttr(target, 'width')) - 50;
  const maxLeft = parseInt(getAttr(document.body, 'width')) - target.offsetLeft - 50;
  const minTop = target.offsetTop;
  const maxTop = parseInt(getAttr(document.body, 'height'))
   - target.offsetTop - parseInt(getAttr(header, 'height'));
  document.onmousemove = (event) => {
    // 鼠标移动时计算每次移动的距离,并改变拖拽元素的定位
    const disX = event.clientX - currentX;
    const disY = event.clientY - currentY;
    // 判断左、右边界
    if (disX < 0 && disX <= -minLeft) {
     target.style.left = `${left - minLeft)}px`;
    } else if (disX > 0 && disX >= maxLeft) {
     target.style.left = `${left + maxLeft}px`;
    } else {
     target.style.left = `${left + disX}px`;
    }
    // 判断上、下边界
    if (disY < 0 && disY <= -minTop) {
     target.style.top = `${top - minTop)}px`;
    } else if (disY > 0 && disY >= maxTop) {
     target.style.top = `${top + maxTop}px`;
    } else {
     target.style.top = `${top + disY}px`;
    }
    return false;
  };
}

这样注册之后就可以使用了:

<el-dialog v-drag title="对话框" :visible.sync="dialogVisible"></el-dialog>

总结

以上所述是小编给大家介绍的Vue 指令实现拖拽功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

Javascript 相关文章推荐
Javascript实例教程(19) 使用HoTMetal(3)
Dec 23 Javascript
HTML Dom与Css控制方法
Oct 25 Javascript
让ie6也支持websocket采用flash封装实现
Feb 18 Javascript
处理及遍历XML文档DOM元素属性及方法整理
Aug 23 Javascript
一个非常全面的javascript URL解析函数和分段URL解析方法
Apr 12 Javascript
浅谈JSON中stringify 函数、toJosn函数和parse函数
Jan 26 Javascript
JQuery实现级联下拉框效果实例讲解
Sep 17 Javascript
js将table的每个td的内容自动赋值给其title属性的方法
Oct 13 Javascript
JS实现touch 点击滑动轮播实例代码
Jan 19 Javascript
详解ES6中的代理模式——Proxy
Jan 08 Javascript
jQuery easyui datagird编辑行删除行功能的实现代码
Sep 20 jQuery
js实现圆形菜单选择器
Dec 03 Javascript
解决Vue动态加载本地图片问题
Oct 09 #Javascript
Vue3 中的数据侦测的实现
Oct 09 #Javascript
vue3实现v-model原理详解
Oct 09 #Javascript
bootstrap+spring boot实现面包屑导航功能(前端代码)
Oct 09 #Javascript
使用Webpack提升Vue.js应用程序的4种方法(翻译)
Oct 09 #Javascript
微信小程序本地存储实现每日签到、连续签到功能
Oct 09 #Javascript
Vue.js实现大转盘抽奖总结及实现思路
Oct 09 #Javascript
You might like
相对路径转化成绝对路径
2007/04/10 PHP
php header示例代码(推荐)
2010/09/08 PHP
PHP基于工厂模式实现的计算器实例
2015/07/16 PHP
PHP实现动态执行代码的方法
2016/03/25 PHP
javascript 数组的方法集合
2008/06/05 Javascript
网络图片延迟加载实现代码 超越jquery控件
2010/03/27 Javascript
腾讯与新浪的通过IP地址获取当前地理位置(省份)的接口
2010/07/26 Javascript
详解jQuery插件开发中的extend方法
2013/11/19 Javascript
用javascript替换URL中的参数值示例代码
2014/01/27 Javascript
javascript的创建多行字符串的7种方法
2014/04/29 Javascript
原生JS实现美图瀑布流布局赏析
2015/09/07 Javascript
学习使用grunt来打包JavaScript和CSS程序的教程
2016/01/04 Javascript
原生js封装的一些jquery方法(详解)
2016/09/20 Javascript
Angularjs实现带查找筛选功能的select下拉框示例代码
2016/10/04 Javascript
javascript事件捕获机制【深入分析IE和DOM中的事件模型】
2016/12/15 Javascript
利用angularjs1.4制作的简易滑动门效果
2017/02/28 Javascript
实现jquery放大镜的两种方法
2018/02/22 jQuery
微信小程序实现两边小中间大的轮播效果的示例代码
2018/12/07 Javascript
vue的.vue文件是怎么run起来的(vue-loader)
2018/12/10 Javascript
js前端面试之同步与异步问题详解
2019/04/03 Javascript
JS实现的tab切换并显示相应内容模块功能示例
2019/08/03 Javascript
微信小程序 授权登录详解(附完整源码)
2019/08/23 Javascript
[55:03]完美世界DOTA2联赛PWL S2 LBZS vs FTD.C 第二场 11.20
2020/11/20 DOTA
浅谈终端直接执行py文件,不需要python命令
2017/01/23 Python
scrapy爬虫完整实例
2018/01/25 Python
Django网络框架之创建虚拟开发环境操作示例
2019/06/06 Python
Python中IP地址处理IPy模块的方法
2019/08/16 Python
pycharm 添加解释器的方法步骤
2020/08/31 Python
阿玛尼美妆俄罗斯官网:Giorgio Armani Beauty RU
2020/07/19 全球购物
精伦电子Java笔试题
2013/01/16 面试题
计算机应用专业推荐信
2013/11/13 职场文书
超市业务员岗位职责
2013/12/05 职场文书
学生会竞选演讲稿纪检部
2014/08/25 职场文书
2015年市场部工作总结
2015/04/30 职场文书
2015年党务公开工作总结
2015/05/19 职场文书
2015年秋学期师德师风建设工作总结
2015/10/23 职场文书