js中的拖拽

拖拽

基本实现思路(mouse事件替代)

滑到盒子上,按住盒子;

鼠标走,盒子拖着走;

结束了,松开鼠标,即抬起;

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>

问题

优化一:move、up事件绑定

不是一进来就绑方法,按下去的时候才绑。按下去的时候做什么才有作用。

按下才表示拖拽要开始。mousemove mouseup事件绑定要在mousedown触发之后;

同理up时移除掉。

鼠标焦点丢失问题

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

绑document:推荐

setCapture

<!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>

使用dom2级事件绑定

绑定的时候好绑,我以后想移除该怎么移除。所以2级dom事件绑定的时候一般都用实名函数,不用匿名函数。

move另一个方法要拿到,那么做成自定义属性

复习拖拽的步骤

把上述内容梳理了一遍

手指按下代表拖拽开始,只要鼠标在盒子上移动盒子就跟着动。

抬起表示拖拽结束,鼠标再移动就要没效果。

拖拽的整个流程规划:

啥时候开始拖拽,啥时候鼠标移动有效果,啥时候没效果。整体流程规划。

鼠标按下绑定move事件,move事件计算,up事件移除。

鼠标移动多远盒子也移动多远。

鼠标当前-鼠标开始+盒子开始=盒子当前位置

一些涉及到的知识点:

开始信息存储:

全局变量容易污染。

盒子存东西用自定义属性

  • offsetLeft( offset() ) 此案例中父级参照物正好是body
  • 获取left直接通过样式中的left。所有经过浏览器计算的样式属性。

鼠标移动过快焦点丢失问题:

外边鼠标抬起了,不是在盒子上,所以盒子的mouseup没有被触发。

鼠标脱离盒子了,鼠标的事情跟盒子没关系了。

第一步:搭结构。什么时候绑定事件方法,什么时候移除。

第二步:拖拽步骤计算位置

第三步:过快焦点丢失。绑document->this问题;自定义属性存储move函数。

需求升级:Drag事件

拖到一个容器中

drag实现

把一个元素放到指定区域里面

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会更加简单些

案例:百度模态框拖拽

扩展:操作自定义属性

  1. setAttribute:加在html结构中,设置在元素的行内属性
  2. this.xxx: 给堆内存空间直接设置自定义属性。操作堆内存

原生this和jquery $this各自的好处:

原生this:原生的永远都是一个,就是这一个堆

$this:能用jQuery方法, 操作起来更加方便

实现居中的方式:

top left 50%:

  1. margin:负宽高一半
  2. transform:平移
  3. flex布局:center
  4. margin:auto 和 4个方向都是0+position:absolute
  5. 用js实现

【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>&times;</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都是当前类的实例

参数初始化:

全部挂载到实例上

options与defaultOptions合并替换

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

assign的局限性:

只做一层的拷贝

工具方法each:

涉及到知识点:

数据类型的检测

回调函数的使用

对象、数组的循环

数组&类数组的特点

支持返回值

……

(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

分析配置哪些参数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值