zoukankan      html  css  js  c++  java
  • javascript动画系列第一篇——模拟拖拽

    前面的话

      从本文开始,介绍javascript动画系列。javascript本身是具有原生拖放功能的,但是由于兼容性问题,以及功能实现的方式,用的不是很广泛。javascript动画广泛使用的还是模拟拖拽。本文将详细介绍该内容

    原理介绍

      模拟拖拽最终效果和在桌面上移动文件夹的效果类似

      鼠标按下时,拖拽开始。鼠标移动时,被拖拽元素跟着鼠标一起移动。鼠标抬起时,拖拽结束

      所以,拖拽的重点是确定被拖拽元素是如何移动的

      假设,鼠标按下时,鼠标对象的clientX和clientY分别为x1和y1。元素距离视口左上角x轴和y轴分别为x0和y0

      鼠标移动的某一时刻,clientX和clientY分别为x2和y2

      所以,元素移动的x轴和y轴距离分别为x2-x1和y2-y1

      元素移动后,元素距离视口左上角x轴和y轴的位置分别为

        X = x0 + (x2-x1)
        Y = y0 + (y2-y1)

    代码实现

      将上面的原理用代码实现如下

      鼠标按下时,初始态的x0和y0分别用offsetLeft和offsetTop表示

      鼠标移动时,瞬时态的x和y分别赋值为定位后元素的left和top

    <div id="test" style="height:100px;100px;background:pink;position:absolute;top:0;left:0;"></div>
    <script>
    test.onmousedown = function(e){
        e = e || event;
        //获取元素距离定位父级的x轴及y轴距离
        var x0 = this.offsetLeft;
        var y0 = this.offsetTop;
        //获取此时鼠标距离视口左上角的x轴及y轴距离
        var x1 = e.clientX;
        var y1 = e.clientY;
    
        test.onmousemove = function(e){
            e = e || event;
            //获取此时鼠标距离视口左上角的x轴及y轴距离
            x2 = e.clientX;
            y2 = e.clientY;    
            //计算此时元素应该距离视口左上角的x轴及y轴距离
            var X = x0 + (x2 - x1);
            var Y = y0 + (y2 - y1);
            //将X和Y的值赋给left和top,使元素移动到相应位置
            test.style.left = X + 'px';
            test.style.top = Y + 'px';
        }
    
        test.onmouseup = function(e){
            //当鼠标抬起时,拖拽结束,则将onmousemove赋值为null即可
            test.onmousemove = null;
        }
    }
    </script>

    【另一种实现】

      由于使用上面的DOM0级事件处理程序时,将只能绑定一个函数,将不利于扩展。所以,将其改写为DOM2级事件处理程序IE事件处理程序的兼容写法

    <div id="test" style="height:100px;100px;background:pink;position:absolute;top:0;left:0;"></div>
    <script>
    function addEvent(target,type,handler){
        if(target.addEventListener){
            target.addEventListener(type,handler,false);
        }else{
            target.attachEvent('on'+type,function(event){
                return handler.call(target,event);
            });
        }
    }
    (function(){
        var x0,y0,x1,y1,isMoving;
        var ele = document.getElementById('test');
    
    var mousedownHandler = function(e){
        e = e || event;
        //获取元素距离定位父级的x轴及y轴距离
        x0 = this.offsetLeft;
        y0 = this.offsetTop;
        //获取此时鼠标距离视口左上角的x轴及y轴距离
        x1 = e.clientX;
        y1 = e.clientY;
        //按下鼠标时,表示正在运动
        isMoving = true;
    }
    var mousemoveHandler = function(e){
        //如果没有触发down事件,而直接触发move事件,则函数直接返回
        if(!isMoving){
            return;
        }
        e = e || event;
        //获取此时鼠标距离视口左上角的x轴及y轴距离
        var x2 = e.clientX;
        var y2 = e.clientY;    
        //计算此时元素应该距离视口左上角的x轴及y轴距离
        var X = x0 + (x2 - x1);
        var Y = y0 + (y2 - y1);
        //将X和Y的值赋给left和top,使元素移动到相应位置
        ele.style.left = X + 'px';
        ele.style.top = Y + 'px';
    }
    var mouseupHandler = function(e){
        //鼠标抬起时,表示停止运动
        isMoving = false;
    }
    
    addEvent(ele,'mousedown',mousedownHandler);
    addEvent(ele,'mousemove',mousemoveHandler)
    addEvent(ele,'mouseup',mouseupHandler)
    
    })();
    </script>

    代码优化

      使用上面的代码时,会出现一个问题。当鼠标拖动的太快,比mousemove事件的触发间隔还要快时,鼠标就会从元素上离开。这样就停止了元素的拖拽过程

      此时,如果把mousemove和mouseup事件都加在document上时,即可解决

    <div id="test" style="height:100px;100px;background:pink;position:absolute;top:0;left:0;"></div>
    <script>
    function addEvent(target,type,handler){
        if(target.addEventListener){
            target.addEventListener(type,handler,false);
        }else{
            target.attachEvent('on'+type,function(event){
                return handler.call(target,event);
            });
        }
    }
    (function(){
        var x0,y0,x1,y1,isMoving;
        var ele = document.getElementById('test');
    
    var mousedownHandler = function(e){
        e = e || event;
        //获取元素距离定位父级的x轴及y轴距离
        x0 = this.offsetLeft;
        y0 = this.offsetTop;
        //获取此时鼠标距离视口左上角的x轴及y轴距离
        x1 = e.clientX;
        y1 = e.clientY;
        //按下鼠标时,表示正在运动
        isMoving = true;
    }
    var mousemoveHandler = function(e){
        //如果没有触发down事件,而直接触发move事件,则函数直接返回
        if(!isMoving){
            return;
        }
        e = e || event;
        //获取此时鼠标距离视口左上角的x轴及y轴距离
        var x2 = e.clientX;
        var y2 = e.clientY;    
        //计算此时元素应该距离视口左上角的x轴及y轴距离
        var X = x0 + (x2 - x1);
        var Y = y0 + (y2 - y1);
        //将X和Y的值赋给left和top,使元素移动到相应位置
        ele.style.left = X + 'px';
        ele.style.top = Y + 'px';
    }
    var mouseupHandler = function(e){
        //鼠标抬起时,表示停止运动
        isMoving = false;
    }
    
    addEvent(ele,'mousedown',mousedownHandler);
    addEvent(ele,'mousemove',mousemoveHandler)
    addEvent(ele,'mouseup',mouseupHandler)
    
    })();
    </script>

    拖拽冲突

      由于文字和图片默认支持原生拖放,如果将原生拖放和模拟拖拽掺杂在一起,将造成与预想效果不符的情况

      如果拖放的元素内容存在文字,且文字被选中会触发文字的原生拖放效果

      在文字上面双击鼠标,即可选中文字,再移动鼠标时,会触发文字的原生拖放效果,如下所示

      只要在mousedown事件阻止浏览器的默认行为即可

    <div id="test" style="height:100px;100px;background:pink;position:absolute;top:0;left:0;">测试文字</div>
    <script>
    function addEvent(target,type,handler){
        if(target.addEventListener){
            target.addEventListener(type,handler,false);
        }else{
            target.attachEvent('on'+type,function(event){
                return handler.call(target,event);
            });
        }
    }
    (function(){
        var x0,y0,x1,y1,isMoving;
        var ele = document.getElementById('test');
    
    var mousedownHandler = function(e){
        e = e || event;
        //获取元素距离定位父级的x轴及y轴距离
        x0 = this.offsetLeft;
        y0 = this.offsetTop;
        //获取此时鼠标距离视口左上角的x轴及y轴距离
        x1 = e.clientX;
        y1 = e.clientY;
        //按下鼠标时,表示正在运动
        isMoving = true;
    }
    var mousemoveHandler = function(e){
        //如果没有触发down事件,而直接触发move事件,则函数直接返回
        if(!isMoving){
            return;
        }
        e = e || event;
        //获取此时鼠标距离视口左上角的x轴及y轴距离
        var x2 = e.clientX;
        var y2 = e.clientY;    
        //计算此时元素应该距离视口左上角的x轴及y轴距离
        var X = x0 + (x2 - x1);
        var Y = y0 + (y2 - y1);
        //将X和Y的值赋给left和top,使元素移动到相应位置
        ele.style.left = X + 'px';
        ele.style.top = Y + 'px';
    }
    var mouseupHandler = function(e){
        //鼠标抬起时,表示停止运动
        isMoving = false;
    }
    var preventDefaultHandler = function(e){
        e = e || event;
        if(e.preventDefault){
            e.preventDefault();
        }else{
            e.returnValue = false;
        }
    }
    addEvent(ele,'mousedown',mousedownHandler);
    addEvent(ele,'mousedown',preventDefaultHandler);
    addEvent(document,'mousemove',mousemoveHandler)
    addEvent(document,'mouseup',mouseupHandler)
    
    })();
    </script>

    IE兼容

      以上代码在IE8-浏览器中仍然无法阻止默认行为。此时,为了实现IE兼容,需要使用全局捕获setCapture()和释放捕获releaseCapture()

      首先,先看一下全局捕获的效果

      下面代码中,开启全局捕获之后,页面中的所有点击效果,都相当于针对按钮一的点击效果。释放捕获后,效果消失

      [注意]IE浏览器完全支持全局捕获;chrome不支持,使用全局捕获会报错;firefox不报错,但静默失败

    <button id="btn1">按钮一</button>
    <button id="btn2">开启按钮一的全局捕获</button>
    <script>
    btn1.onclick = function(){
        alert(1);
    }
    btn2.onclick = function(){
        if(btn1.setCapture){
            if(btn2.innerHTML.charAt(0) == ''){
                btn1.setCapture();
                btn2.innerHTML = '关闭按钮一的全局捕获';
            }else{
                btn1.releaseCapture();
                btn2.innerHTML = '开启按钮一的全局捕获';    
            }
        }
    }
    </script>

      通过在IE浏览器设置全局捕获来达到取消文字原生拖放的默认行为

    <div id="test" style="height: 100px; 100px;background:pink;position:absolute;top:0;left:0;">测试文字</div>
    <script>
    function addEvent(target,type,handler){
        if(target.addEventListener){
            target.addEventListener(type,handler,false);
        }else{
            target.attachEvent('on'+type,function(event){
                return handler.call(target,event);
            });
        }
    }
    (function(){
        var x0,y0,x1,y1,isMoving;
        var ele = document.getElementById('test');
    
    var mousedownHandler = function(e){
        e = e || event;
        //获取元素距离定位父级的x轴及y轴距离
        x0 = this.offsetLeft;
        y0 = this.offsetTop;
        //获取此时鼠标距离视口左上角的x轴及y轴距离
        x1 = e.clientX;
        y1 = e.clientY;
        //按下鼠标时,表示正在运动
        isMoving = true;
    }
    var mousemoveHandler = function(e){
        //如果没有触发down事件,而直接触发move事件,则函数直接返回
        if(!isMoving){
            return;
        }
        e = e || event;
        //获取此时鼠标距离视口左上角的x轴及y轴距离
        var x2 = e.clientX;
        var y2 = e.clientY;    
        //计算此时元素应该距离视口左上角的x轴及y轴距离
        var X = x0 + (x2 - x1);
        var Y = y0 + (y2 - y1);
        //将X和Y的值赋给left和top,使元素移动到相应位置
        ele.style.left = X + 'px';
        ele.style.top = Y + 'px';
    }
    var mouseupHandler = function(e){
        //鼠标抬起时,表示停止运动
        isMoving = false;
        //释放全局捕获
        if(ele.releaseCapture){
            ele.releaseCapture();
        }    
    }
    var preventDefaultHandler = function(e){
        e = e || event;
        if(e.preventDefault){
            e.preventDefault();
        }else{
            e.returnValue = false;
        }
        //IE8-浏览器阻止默认行为
        if(ele.setCapture){
            ele.setCapture();
        }
    }
    addEvent(ele,'mousedown',mousedownHandler);
    addEvent(ele,'mousedown',preventDefaultHandler);
    addEvent(document,'mousemove',mousemoveHandler)
    addEvent(document,'mouseup',mouseupHandler)
    
    })();
    </script>

    源码查看

  • 相关阅读:
    《软件方法》读书笔记2
    《代码阅读方法与实践》读书笔记3
    课堂讨论记录
    《代码阅读方法与实践》读书笔记2
    [洛谷] P1948 [USACO08JAN]Telephone Lines S(二分+SPFA)
    2020 CCPC秦皇岛 正式赛题解
    [洛谷] P3146 [USACO16OPEN]248 G (区间DP)
    [进阶指南] 最大子序和
    [训练] 图的K步移动最大收获
    [计蒜客] 受力平衡(组合数学 + 乘法逆元)
  • 原文地址:https://www.cnblogs.com/xiaohuochai/p/5897780.html
Copyright © 2011-2022 走看看