
滑到盒子上,按住盒子;
鼠标走,盒子拖着走;
结束了,松开鼠标,即抬起;
mousedown 按下
mousemove 跟着走
mouseup 抬起


核心思想:
按下的时候记录起始位置,移动过程中计算偏移+盒子起始位置,设置盒子位置。
但是这是最low最low的版本
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>drag</title>
- <style>
- *{
- margin: 0;
- padding:0;
- }
- .box{
- position: absolute;
- left: 100px;
- top: 100px;
- width: 100px;
- height: 100px;
- background: red;
- }
- </style>
- </head>
- <body>
- <div class="box" id="box"></div>
- <script>
- let drag=false;
- // id可以直接当做dom用
- box.onmousedown=function (ev) {
- console.log('onmousedown===');
- drag=true;
- this.mouseStartX=ev.pageX;
- this.mouseStartY=ev.pageY;
- this.startX=box.offsetLeft;
- this.startY=box.offsetTop;
- }
- box.onmousemove=function (ev) {
- console.log('onmousemove===');
- if(!drag) return false;
- let curLeft=ev.pageX-this.mouseStartX+this.startX;
- let curTop=ev.pageY-this.mouseStartY+this.startY;
- this.style.left=`${curLeft}px`;
- this.style.top=`${curTop}px`;
- }
- box.onmouseup=function (ev) {
- console.log('onmouseup===');
- drag=false;
- }
- // box.ondragend=function(ev){
- // console.log('ondragend======');
- // console.log(ev)
- // console.log(ev.offsetX);
- // console.log(ev.offsetY);
- //
- // }
- // box.ondragstart=function(ev){
- // console.log('ondragstart======');
- // console.log(ev)
- // console.log(ev.offsetX);
- // console.log(ev.offsetY);
- //
- // }
- </script>
- </body>
- </html>
-
不是一进来就绑方法,按下去的时候才绑。按下去的时候做什么才有作用。
按下才表示拖拽要开始。mousemove mouseup事件绑定要在mousedown触发之后;
同理up时移除掉。

出现问题原因:丢掉焦点出现问题的原因就是,鼠标移动过快,盒子跟不上。松起来是在盒子外面松起来,盒子事件没取消掉。


- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>drag</title>
- <style>
- *{
- margin: 0;
- padding:0;
- }
- html,body{
- height: 100%;
- }
- .box{
- position: absolute;
- left: 100px;
- top: 100px;
- width: 100px;
- height: 100px;
- background: red;
- }
- </style>
- </head>
- <body>
- <div class="box" id="box"></div>
- <script>
-
- function mousedown(ev){
- this.mouseStartX=ev.pageX;
- this.mouseStartY=ev.pageY;
- this.startX=box.offsetLeft;
- this.startY=box.offsetTop;
- document.onmousemove=mousemove.bind(box);
- document.onmouseup=mouseup;
- }
-
- function mousemove(ev){
- let curLeft=ev.pageX-this.mouseStartX+this.startX;
- let curTop=ev.pageY-this.mouseStartY+this.startY;
- let minLeft=0,maxLeft=document.body.clientWidth-this.offsetWidth,minTop=0,maxTop=document.body.clientHeight-this.offsetHeight;
- curLeft=curLeft<minLeft?0:curLeft>maxLeft?maxLeft:curLeft;
- curTop=curTop<minTop?0:curTop>maxTop?maxTop:curTop;
- this.style.left=`${curLeft}px`;
- this.style.top=`${curTop}px`;
- }
- function mouseup(ev){
- this.onmousemove=null;
- this.onmouseup=null;
- }
- // id可以直接当做dom用
- box.onmousedown=mousedown;
- </script>
- </body>
- </html>
-
绑定的时候好绑,我以后想移除该怎么移除。所以2级dom事件绑定的时候一般都用实名函数,不用匿名函数。
move另一个方法要拿到,那么做成自定义属性。

把上述内容梳理了一遍
手指按下代表拖拽开始,只要鼠标在盒子上移动盒子就跟着动。
抬起表示拖拽结束,鼠标再移动就要没效果。
拖拽的整个流程规划:
啥时候开始拖拽,啥时候鼠标移动有效果,啥时候没效果。整体流程规划。
鼠标按下绑定move事件,move事件计算,up事件移除。
鼠标移动多远盒子也移动多远。
鼠标当前-鼠标开始+盒子开始=盒子当前位置
一些涉及到的知识点:
开始信息存储:
全局变量容易污染。
盒子存东西用自定义属性
鼠标移动过快焦点丢失问题:
外边鼠标抬起了,不是在盒子上,所以盒子的mouseup没有被触发。
鼠标脱离盒子了,鼠标的事情跟盒子没关系了。

第一步:搭结构。什么时候绑定事件方法,什么时候移除。
第二步:拖拽步骤计算位置
第三步:过快焦点丢失。绑document->this问题;自定义属性存储move函数。
拖到一个容器中

把一个元素放到指定区域里面
draggable=true 可拖拽元素
dragstart dataTransfer存储数据,把其他方法中要用到的数据事先存储起来

- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>drag</title>
- <style>
- *{
- margin: 0;
- padding:0;
- }
- html,body{
- height: 100%;
- }
- .box{
- cursor: move;
- position: absolute;
- width: 100px;
- height: 100px;
- background: red;
- }
- .container{
- position: relative;
- width: 600px;
- height: 400px;
- background: #9fcdff;
- top: 50px;
- left: 500px;
- }
- </style>
- </head>
- <body>
- <div class="box" id="box" draggable="true"></div>
- <div class="container" id="container"></div>
- <script>
- box.ondragstart=function (ev) {
- ev.dataTransfer.setData('text/plain','box');
- }
- container.ondragover=function (ev) {
- ev.preventDefault()
- }
- container.ondrop=function (ev) {
-
- let id=ev.dataTransfer.getData('text/plain')
- let box=document.getElementById(id)
- // ev.preventDefault()
- container.appendChild(box);
- }
-
- </script>
- </body>
- </html>
-
dataTransfer中setData的东西只能是在ondrop事件中通过getData拿到
mouse事件要判断范围,是否在象限内
但是会有一些问题:
dragover要ev.preventDefault()
drag事件是拖拽了盒子的阴影,不是盒子

项目中拖动效果其实还是用mouse用得多
只是某些业务场景,比如拖到一个容器里可能用drag会更加简单些
扩展:操作自定义属性

原生this和jquery $this各自的好处:
原生this:原生的永远都是一个,就是这一个堆
$this:能用jQuery方法, 操作起来更加方便
实现居中的方式:
top left 50%:
【css】盒子水平垂直居中的实现_儒rs的博客-CSDN博客
这里要用js实现
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Title</title>
- <link rel="stylesheet" href="../css/bootstrap.min.css">
- <style>
- html,body{
- height: 100%;
- }
- .modal{
- width: 500px;
- height: 264px;
- }
- .modal .modal-dialog{
- margin: 0;
- }
- .modal-header{
- cursor: move;
- }
- </style>
- </head>
- <body>
- <!-- Button trigger modal -->
- <button type="button" class="btn btn-primary" id="loginButton">
- 百度登录
- </button>
-
- <!-- Modal -->
- <div class="modal fade show" id="loginModal" draggable="false">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title">百度登录</h5>
- <button type="button" class="close" id="closeButton">
- <span>×</span>
- </button>
- </div>
- <div class="modal-body">
- 夫君子之行,静以修身,俭以养德。非淡泊无以明志,非宁静无以致远。夫学须静也,才须学也,非学无以广才,非志无以成学。淫慢则不能励精,险躁则不能治性。年与时驰,意与日去,遂成枯落,多不接世,悲守穷庐,将复何及!
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-primary">提交</button>
- </div>
- </div>
- </div>
- </div>
- <script src="../js/jquery-1.11.3.min.js"></script>
- <script>
- let $loginButton=$('#loginButton'),$loginModal=$('#loginModal'),$closeButton=$('#closeButton');
- $loginButton.click(function () {
- // 用js实现居中
- let top=($(window).outerHeight()-$loginModal.outerHeight())/2;
- let left=($(window).outerWidth()-$loginModal.outerWidth())/2;
- $loginModal.css({
- display:'block',
- top,
- left,
- })
- })
- $closeButton.click(function () {
- $loginModal.css({
- display:'none'
- })
- })
-
- const modalHeader=document.querySelector('.modal .modal-header'),modal=document.querySelector('.modal')
- // 拖拽,同之前一样
- modalHeader.addEventListener('mousedown',mousedown.bind(modal))
- modalHeader.ondrag=function (ev) {
- ev.preventDefault();
- }
- document.body.ondragover=function (ev) {
- ev.preventDefault()
- ev.stopPropagation()
- }
- function mousedown(ev){
- if(ev.target.className!=='modal-header' || (ev.offsetX<0 && ev.offsetY<0)) return;
- this.mouseStartX=ev.pageX;
- this.mouseStartY=ev.pageY;
- this.startX=this.offsetLeft;
- this.startY=this.offsetTop;
- this.move=mousemove.bind(this);
- this.up=mouseup.bind(this)
- document.onmousemove=this.move;
- document.onmouseup=this.up;
- // document.addEventListener('mousemove',this.move)
- // document.addEventListener('mouseup',this.up)
- }
-
- function mousemove(ev){
- let curLeft=ev.pageX-this.mouseStartX+this.startX;
- let curTop=ev.pageY-this.mouseStartY+this.startY;
- let minLeft=0,maxLeft=document.body.clientWidth-this.offsetWidth,minTop=0,maxTop=document.body.clientHeight-this.offsetHeight;
- curLeft=curLeft<minLeft?0:curLeft>maxLeft?maxLeft:curLeft;
- curTop=curTop<minTop?0:curTop>maxTop?maxTop:curTop;
- this.style.left=`${curLeft}px`;
- this.style.top=`${curTop}px`;
- }
- function mouseup(ev){
- // document.removeEventListener('mousemove',this.move)
- // document.removeEventListener('mouseup',this.up)
- document.onmousemove=null;
- document.onmouseup=null;
-
- }
-
- </script>
- </body>
- </html>
-
尽可能保证每个方法中的this都是当前类的实例
全部挂载到实例上

传的替换,不传的使用默认值

assign的局限性:
只做一层的拷贝

涉及到知识点:
数据类型的检测
回调函数的使用
对象、数组的循环
数组&类数组的特点
支持返回值
……

- (function () {
- class Drag{
- constructor(selector,options){
- this.init(selector,options)
- }
- init(selector,options){
- this._selector=document.querySelector(selector);
- const defaultOptions={
- element:this._selector,
- boundary:true,
- dragstart:null,
- dragmove:null,
- dragend:null
- }
- options=Object.assign(defaultOptions,options)
- Drag.each(options,(value,key)=>{
- this[`_${key}`]=value;
- })
- console.log(this)
- }
- static each(iterator,callback){
- const type=Drag.typeof(iterator);
- if(type==='Array'){
- for(let i=0;i<iterator.length;i++){
- callback(iterator[i],i)
- }
- }else if(type==='Object'){
- for(const key in iterator){
- if(iterator.hasOwnProperty(key)){
- callback(iterator[key],key)
- }
- }
- }
- }
- static typeof(obj){
- return Object.prototype.toString.call(obj).replace(/\[object |\]/g,'')
- }
- }
- window.Drag=Drag;
- })()
-
实现拖拽效果
drag事件:dragover默认行为阻止掉
call & apply & bind:
三者区别:
call&apply立即执行,bind预先改变this,没有立即执行。
apply传参是数组
源码
应用场景:
dom事件绑定的时候,改变this;
定时器执行:默认是window。使用bind改变this。之后才做,做的时候this改成我想要的。
call&apply把函数执行了,改了this。
在钩子函数中,把一些信息通过参数传递过去

- (function () {
- class Drag{
- constructor(selector,options){
- this.init(selector,options)
- this._selector.addEventListener('mousedown',this.down.bind(this))
- }
- init(selector,options){
- this._selector=document.querySelector(selector);
- const defaultOptions={
- element:this._selector,
- boundary:true,
- dragstart:null,
- dragmove:null,
- dragend:null
- }
- options=Object.assign(defaultOptions,options)
- Drag.each(options,(value,key)=>{
- this[`_${key}`]=value;
- })
- console.log(this)
- }
- down(ev){
- let {_element}=this;
- this.mouseStartX=ev.pageX;
- this.mouseStartY=ev.pageY;
- this.startX=Number.parseFloat(Drag.queryCss(_element,'left'));
- this.startY=Number.parseFloat(Drag.queryCss(_element,'top'));
- this._move=this.move.bind(this);
- this._up=this.up.bind(this)
- document.addEventListener('mousemove',this._move)
- document.addEventListener('mouseup',this._up)
- this.dragstart && this.dragstart(this,ev);
- }
- move(ev){
- let {mouseStartX,mouseStartY,startX,startY,_element,_boundary}=this;
- let curLeft=ev.pageX-mouseStartX+startX;
- let curTop=ev.pageY-mouseStartY+startY;
- if(_boundary){
- // 已约定要相对于父盒子定位
- let parent=_element.parentNode;
- let minLeft=0,maxLeft=parent.offsetWidth-_element.offsetWidth,minTop=0,maxTop=parent.offsetHeight-_element.offsetHeight;
- curLeft=curLeft<minLeft?0:curLeft>maxLeft?maxLeft:curLeft;
- curTop=curTop<minTop?0:curTop>maxTop?maxTop:curTop;
- }
-
- _element.style.left=`${curLeft}px`;
- _element.style.top=`${curTop}px`;
- this.dragmove && this.dragmove(this,curLeft,curTop,ev)
- }
- up(ev){
- document.removeEventListener('mousemove',this._move)
- document.removeEventListener('mouseup',this._up)
- this.dragend && this.dragend(this,ev)
- }
- static each(iterator,callback){
- const type=Drag.typeof(iterator);
- if(type==='Array'){
- for(let i=0;i<iterator.length;i++){
- callback(iterator[i],i)
- }
- }else if(type==='Object'){
- for(const key in iterator){
- if(iterator.hasOwnProperty(key)){
- callback(iterator[key],key)
- }
- }
- }
- }
- static typeof(obj){
- return Object.prototype.toString.call(obj).replace(/\[object |\]/g,'')
- }
- static queryCss(elem,attr){
- return window.getComputedStyle(elem)[attr];
- }
- }
- window.Drag=Drag;
- })()
-
插件封装:
class constructor
分析配置哪些参数