javascript实现PC网页里的拖拽效果


Posted in Javascript onMarch 14, 2016

几年前,我参与设计开发一个房产网的项目,我负责前端工作,由于项目经理要求比较高,参考了很多房产类网站比较优秀的功能,想把别人比较优秀的设计和想法集合到一起,那时的设计稿和功能实现,简直就是改了又改,今天做好的一个很好的效果,可能第二天就要推到重来,算了,不说这些了,还是说说我们今天要讲解的案例吧,不知道大家访问过搜房网没有(完全没有做广告之嫌,搜房网,可以给点广告费不),其中有一个功能产品经理特别喜欢,那,就是下面的这个:

javascript实现PC网页里的拖拽效果

这是现在的效果,可能改了一些,原来的效果是,里面的这张图是可以上下左右拖动的,然后房子上面的显示的楼栋号,也跟着图片一起移动,当时js能力还不行,未能实现项目经理的要求,不过后来项目经理又把这个效果推掉了,换了另外的一个效果

尽管项目经理不想要这个效果了,但是当时就在我心里留下了一个节,到今天都忘不了这个梗。

好了,这就是我今天想写这篇博客的初衷,希望能给想实现这类拖拽效果,但是不知道该怎么去实现的同学,提供一种思路,不给青春留遗憾,当然实现拖拽的方法有很多,这里就只介绍JavaScript中的一种方法,慢慢体会一下其中的原理!

好了,梗也说完了,开始正题,我们先要明白,拖拽到底是一个什么东西,你也知道,我也知道,但是我还是想来描述一下:

拖拽就是一个容器,你用鼠标可以在页面上拖着到处跑,废话,精确的描述应该是,鼠标移到容器上,然后鼠标按下去,注意要按着不放,然后拖动鼠标,容器能跟着鼠标跑,松开鼠标,容器就停在那里不动了,现实中的例子就是桌子上有一个盒子,我用手放在盒子上,然后移动盒子,手停盒子停,手拿开,盒子不动了,嘻嘻,都懂了哈!

别以为上面说了一堆的废话,我们可以从中得到很多的信息,总结如下就是:

拖拽 = 鼠标按下 + 鼠标移动 + 鼠标弹上

这样就完成了一个拖拽任务,好了,原来这就是拖拽的原理,想实现拖拽,自然实现上面的3个动作,便可以模拟拖拽效果,好,对应JavaScript中的语法就是需要实现这3个动作:

onmousedown , onmousemove , onmouseup

 实现的代码就应该是:

obj.onmousedown = function(ev){
   obj.onmousemove = function(ev){
 
   } ;
   obj.onmouseup = function(ev){
   
   };
   
}

为什么后面2个动作要写的里面,好好回味一下,好了,第一步的大概思路就有了,下一步就需要考虑怎么让物体跟着鼠标一起移动,思路大概是这样的:

首先物体是需要决定定位的,因为我们需要操作它的left和top值,才能让它移动,然后就是要考虑鼠标了,鼠标位移,本身就会有一个距离,如果我们知道鼠标移动了多远,然后把这个距离给物体,那物体是不是也和鼠标一样,移动了相同的距离,这不就实现拖拽了吗?哈哈,思路一点点有,感觉萌萌哒~ 现在的问题就是怎么获取鼠标的距离,如果需要深入了解,请复习一下盒子模型,这里我就不说了,很多大神也有相关的博客,我用一张图表示一下:

javascript实现PC网页里的拖拽效果

说明:蓝色框为屏幕宽高,黑色粗框为浏览器可视区宽高(浏览器缩小效果),黑色细框为鼠标要拖拽的对象,如图可知,获取鼠标的坐标,可以用event.clientX,event.clientY来获取,哦了;

计算的大致原理可以参照下图:

javascript实现PC网页里的拖拽效果

说明:左边为初始位置,右边为目标位置,原点为鼠标位置,大黑框为浏览器可视宽度,小黑框为拖拽对象,看拖拽对象到目标位置的状态,获取鼠标的最终位置,再减去鼠标距离对象的差值,再赋值给对象的top,left值,也可以获取鼠标的位置差值,再用初始的top,left值加上差值,我们采用第一种,第二种也可以,自己去试一下:

obj.onmousedown = function(ev){
  var ev = ev || event;
  var disX = ev.clientX - this.offsetLeft,disY = ev.clientY - this.offsetTop;
 
  document.onmousemove = function(ev){
    var ev = ev || event;
    obj.style.left = ev.clientX - disX + 'px';
    obj.style.top = ev.clientY - disY + 'px';
  };
  document.onmouseup = function(ev){
    var ev = ev || event;
    document.onmousemove = document.onmouseup = null;
  };
}

这里说明一下:onmousemove和onmouseup之所以用document对象而不用obj对象,是因为如果用obj对象,鼠标在obj内部还好,如果在obj外面的话,拖拽会很怪异,你也可以改成obj体会一下,最后我们在鼠标弹起的时候将事件都清空;

上面的基本拖拽就算完成了,但是细心的同学一定会问,如果页面上有文字的话,拖拽物体会将文字选中,这效果岂不是怪怪的,没错,这是因为拖拽的时候触发了浏览器的默认选择事件,所以,在拖拽的时候,我们要清除这个默认事件,那怎么清除呢?

下面给一个兼容性写法:

if(ev.stopPropagation){
   ev.stopPropagation();
}else{
  ev.cancelBubble = true; //兼容IE
}
//简写成
ev.stopPropagation ? ev.stopPropagation() : ev.cancelBubble = true;

将上面的代码放在onmousedown下,鼠标按下就清除浏览器默认事件,文字就不会被选中了,好了,一个简单的拖拽效果就完成了,当然你现在是看不到效果,之所以不给demo链接是为了让你自己试着写一写,这样印象更深刻,

好了,那问题又来了,到这里就这样完了吗?。。。。。。按本人的风格,当然没有,干货还在后面!

如果我想实现这样一个效果,就是这一个大的容器里面(可以是box,也可以是document),怎么样能让我们的拖拽对象不跑出去呢,换句话说,拖到边缘就拖不动了,耶,是不是很多人想要实现的效果,哈哈,我们看看实现的原理是什么:

现实生活中,一个物体在一个盒子里跑不出去,是因为有堵墙,那我们只要能模拟出这堵墙,就可以把物体框起来,那这堵墙要怎么做呢?我们可以换个思路,当拖拽对象拖到边缘的时候,比如说拖到右边,我们将它的left固定住,是不是就不能再往右了,因为left值不能再加了,那么拖到底部,同理我们将top值固定住,就不能再往下拖了,理解吗?

最终的结果就是如下:

//左侧
if(obj.offsetLeft <=0){
  obj.style.left = 0;
};
//右侧
if(obj.offsetLeft >= pWidth - oWidth){
  obj.style.left = pWidth - oWidth + 'px'; 
};
//上面
if(obj.offsetTop <= 0){
  obj.style.top = 0; 
};
//下面
if(obj.offsetTop >= pHeight - oHeight){
  obj.style.top = pHeight - oHeight + 'px'; 
};

说明:pWidth,pHeight 表示父级元素的宽高(这里是表示相对于父级的宽高限制),oWidth,oHeigt表示拖拽元素的宽高

最后,我将整个拖拽代码整理了一下:

/*
      参数说明:
      元素绝对定位,父级相对定位,如果父级为window,则可以不用
      传一个参数,表示父级为window,物体相对于window范围拖动
      传2个参数,则父级为第二个参数,物体相对于父级范围拖动
      参数为id值
    */
    function drag(obj,parentNode){
      var obj = document.getElementById(obj);
      if(arguments.length == 1){
        var parentNode = window.self; 
        var pWidth = parentNode.innerWidth,pHeight = parentNode.innerHeight;  
      }else{
        var parentNode = document.getElementById(parentNode);
        var pWidth = parentNode.offsetWidth,pHeight = parentNode.offsetHeight;
      }
      obj.onmousedown = function(ev){
        var ev = ev || event;
        var disX = ev.clientX - this.offsetLeft,disY = ev.clientY - this.offsetTop;
        var oWidth = obj.offsetWidth,oHeight = obj.offsetHeight;
         
        //阻止冒泡时间
        ev.stopPropagation ? ev.stopPropagation() : ev.cancelBubble = true;
       
         
        document.onmousemove = function(ev){
          var ev = ev || event;
          obj.style.left = ev.clientX - disX + 'px';
          obj.style.top = ev.clientY - disY + 'px';
           
          //左侧
          if(obj.offsetLeft <=0){
            obj.style.left = 0;
          };
          //右侧
          if(obj.offsetLeft >= pWidth - oWidth){
            obj.style.left = pWidth - oWidth + 'px'; 
          };
          //上面
          if(obj.offsetTop <= 0){
            obj.style.top = 0; 
          };
          //下面
          if(obj.offsetTop >= pHeight - oHeight){
            obj.style.top = pHeight - oHeight + 'px'; 
          };
        };
        document.onmouseup = function(ev){
          var ev = ev || event;
          document.onmousemove = document.onmouseup = null;
        };
      }
         
    }

说明:我这里处理的效果是,如果传一个参数,表示相对的对象是window对象,如果传2个参数,第一个是拖拽对象,第二个为相对父级

开篇就说了,搜房网的那个图片拖拽效果是我的一个心结,我写了一个类似的效果,供大家参考,因为自己没有买服务器,所以效果我就不展示了,直接把代码贴出来,供大家参考:

css:

<style>
.box{
  width:600px;
  height:400px;
  margin:50px auto;
  position:relative;
  overflow:hidden;
}
#box{
  width:1000px;
  height:800px;
  position:absolute;
  left:50%;
  top:50%;
  margin:-400px 0 0 -500px;
}
#pic{ width:800px; height:600px; background:url(images/pic1.jpg) no-repeat; position:absolute; left:100px; top:100px; }
#pic:hover{
  cursor:move;
}
</style>

html:

<div class="box">
    <div id="box">
      <div id="pic"></div>
    </div>
  </div>

javascript:

window.onload = function(){
     
    drag("pic","box");
    function drag(obj,parentNode){
      var obj = document.getElementById(obj);
      if(arguments.length == 1){
        var parentNode = window.self; 
        var pWidth = parentNode.innerWidth,pHeight = parentNode.innerHeight;  
      }else{
        var parentNode = document.getElementById(parentNode);
        var pWidth = parentNode.offsetWidth,pHeight = parentNode.offsetHeight;
      }
      obj.onmousedown = function(ev){
        var ev = ev || event;
        var disX = ev.clientX - this.offsetLeft,disY = ev.clientY - this.offsetTop;
        var oWidth = obj.offsetWidth,oHeight = obj.offsetHeight;
         
        //阻止冒泡时间
        ev.stopPropagation ? ev.stopPropagation() : ev.cancelBubble = true;
       
         
        document.onmousemove = function(ev){
          var ev = ev || event;
          obj.style.left = ev.clientX - disX + 'px';
          obj.style.top = ev.clientY - disY + 'px';
           
          //左侧
          if(obj.offsetLeft <=0){
            obj.style.left = 0;
          };
          //右侧
          if(obj.offsetLeft >= pWidth - oWidth){
            obj.style.left = pWidth - oWidth + 'px'; 
          };
          //上面
          if(obj.offsetTop <= 0){
            obj.style.top = 0; 
          };
          //下面
          if(obj.offsetTop >= pHeight - oHeight){
            obj.style.top = pHeight - oHeight + 'px'; 
          };
        };
        document.onmouseup = function(ev){
          var ev = ev || event;
          document.onmousemove = document.onmouseup = null;
        };
      }
         
    }
     
     
  }

效果完全是用的那个封装代码块,引用起来也挺方便,有人会问了,你这用的id获取DOM元素,一个页面只能用一次啊,如果页面多次使用呢,有道理,解决方案之一,那就命名不同的id呗,又不犯法,方案二,获取id的地方改成获取class,但是要注意的是,getElementsByClassName是获取的class集合,需要改写一下,这里我就不写了,有兴趣的同学自行改写一下,好了,到这里真的结束了!

Javascript 相关文章推荐
Jquery Ajax学习实例 向页面发出请求,返回XML格式数据
Mar 14 Javascript
jQuery-Tools-overlay 使用介绍
Jul 14 Javascript
javascript删除字符串最后一个字符
Jan 14 Javascript
jQuery获取iframe的document对象的方法
Oct 10 Javascript
JS简单实现城市二级联动选择插件的方法
Aug 19 Javascript
基于JavaScript实现图片点击弹出窗口而不是保存
Feb 06 Javascript
详解Matlab中 sort 函数用法
Mar 20 Javascript
一个简易的js图片轮播效果
Jul 22 Javascript
小程序实现按下录音松开识别语音
Nov 22 Javascript
Vue computed 计算属性代码实例
Apr 22 Javascript
浅谈vue获得后台数据无法显示到table上面的坑
Aug 13 Javascript
基于javascript实现放大镜特效
Dec 03 Javascript
Jquery实现简单的轮播效果(代码管用)
Mar 14 #Javascript
node模块机制与异步处理详解
Mar 13 #Javascript
JS中创建函数的三种方式及区别
Mar 13 #Javascript
使用jQuery操作HTML的table表格的实例解析
Mar 13 #Javascript
Javascript数组Array方法解读
Mar 13 #Javascript
GitHub上一些实用的JavaScript的文件压缩解压缩库推荐
Mar 13 #Javascript
Javascript数组Array基础介绍
Mar 13 #Javascript
You might like
星际争霸中的热键
2020/03/04 星际争霸
PHP创建/删除/复制文件夹、文件
2016/05/03 PHP
php实现与python进行socket通信的方法示例
2017/08/30 PHP
jquery简单体验
2007/01/10 Javascript
奇妙的js
2007/09/24 Javascript
网页中的图片的处理方法与代码
2009/11/26 Javascript
20款效果非常棒的 jQuery 插件小结分享
2011/11/18 Javascript
canvas绘制爱心的几种方法总结(推荐)
2017/10/31 Javascript
实例分析JS与Node.js中的事件循环
2017/12/12 Javascript
浅谈Vue2.0父子组件间事件派发机制
2018/01/08 Javascript
vue如何判断dom的class
2018/04/26 Javascript
Vue中$refs的用法详解
2018/06/24 Javascript
Node.js npm命令运行node.js脚本的方法
2018/10/10 Javascript
Vue使用预渲染代替SSR的方法
2020/07/02 Javascript
[03:37]2015国际邀请赛第四日现场精彩集锦
2015/08/08 DOTA
python单链表实现代码实例
2013/11/21 Python
使用C语言扩展Python程序的简单入门指引
2015/04/14 Python
python实现redis三种cas事务操作
2017/12/19 Python
python实现桌面托盘气泡提示
2019/07/29 Python
使用selenium和pyquery爬取京东商品列表过程解析
2019/08/15 Python
Django框架反向解析操作详解
2019/11/28 Python
Python几种常见算法汇总
2020/06/02 Python
HTML5 中新的全局属性(整理)
2013/07/31 HTML / CSS
使用HTML5拍照示例代码
2013/08/06 HTML / CSS
阿姆斯特丹城市卡:Amsterdam Pass
2019/12/01 全球购物
药剂专业学生求职信范文
2013/12/28 职场文书
共产党员公开承诺书
2014/03/25 职场文书
报告会主持词
2014/04/02 职场文书
跳蚤市场口号
2014/06/13 职场文书
2014年技术员工作总结
2014/11/18 职场文书
学校2015年纠风工作总结
2015/05/15 职场文书
地雷战观后感
2015/06/09 职场文书
历史名人教你十五个读书方法,赶快Get起来!
2019/07/18 职场文书
快消品行业营销模式与盈利模式分享
2019/09/27 职场文书
Axios代理配置及封装响应拦截处理方式
2022/04/07 Vue.js
SQL中去除重复数据的几种方法汇总(窗口函数对数据去重)
2023/05/08 MySQL