js拖拽技能

你猜我叫啥發表於2018-05-25

今天來研究下跟拖拽有關的問題,以備以後可以實現更多友好的互動形式。

1. 相關原生方法:

  • ondragstart:拖拽開始
  • ondragend:拖拽結束
  • ondragenter:拖拽元素進入目標元素頭上的時候
  • ondrop:拖拽元素進入目標元素頭上,同時滑鼠鬆開的時候
  • ondragover:拖拽元素在目標元素頭上移動的時候

有點暈?按下面的例子分別都操作下就明白啦。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style type= text/css>
        .container {
            width: 400px;
            height: 400px;
            background: #ff6632;
        }
    </style>
</head>
<body>
    <div id='drag1' draggable="true">拖拽我,come on!</div>
    <div id='drag2'>拽不動我,come on!</div>
    <div class='container' id='container'>

    </div>
</body>
<script>
    document.getElementById('drag1').ondragstart = function(){
        console.log('開始拖拽')
    }
    document.getElementById('drag1').ondragend = function(){
        console.log('結束拖拽')
    }
    document.getElementById('container').ondrop = function(){
        console.log('已掉入規定範圍')
    } 
    document.getElementById('container').ondragenter = function(){
        console.log('拖拽元素進入目標元素頭上的時候')
    } 
    document.getElementById('container').ondragover = function(ev) {
        //防止ondrop不生效,ev.preventDefault()
        ev.preventDefault();
        console.log('拖拽元素在目標元素頭上移動的時候')
    };
    
</script>
</html>
複製程式碼

特別注意:

  1. 想對一個元素進行一系列的拖拽操作,要註明屬性 draggable="true" 來啟用它的可拖拽性。
  2. 在使用ondrop時,發現沒有生效,在ondragover中需要阻止預設事件的觸發。

到這裡我們已經可以拖拽元素並且監聽一系列的拖拽事件了,然而本文遠遠沒有結束,我們接著往下看:

人家網頁上的拖拽都是拖著元素可以隨便動,我這寫的什麼玩意!元素根本都不能動,不好使啊!!!!

彆著急,現在我們來讓元素跟著拖拽動起來!!!

  • 首先我們要搞清楚一些概念:
  1. screenX,screenY/offsetX,offsetY/clientX,clientY. 1. 1. --學習連結
  2. getComputedStyle -- 學習連結
  • 清楚了概念,我們看下具體實現。

html部分:

<style type='text/css'>
  #box {
        position: absolute;
        left: 100px;
        top: 100px;
        padding: 5px;
        background: #f0f3f9;
        width: 100px;
        height: 100px;
    }
</style>
...
<div id='box'></div>
複製程式碼

js部分:

    window.onload = function () {
        var oBox = document.getElementById("box");
        startDrag(oBox, oBox);
    };

    var params = {
        left: 0,
        top: 0,
        currentX: 0,
        currentY: 0,
        flag: false
    };
    //獲取相關CSS屬性
    var getCss = function (o, key) {
        return o.currentStyle ? o.currentStyle[key] : document.defaultView.getComputedStyle(o, null)[key];
    };

    //拖拽的實現
    var startDrag = function (bar, target, callback) {
        if (getCss(target, "left") !== "auto") {
            params.left = getCss(target, "left");
        }
        if (getCss(target, "top") !== "auto") {
            params.top = getCss(target, "top");
        }
        //o是移動物件
        bar.onmousedown = function (event) {
            params.flag = true;
            var e = event;
            params.currentX = e.clientX;
            params.currentY = e.clientY;
        };
        document.onmouseup = function () {
            console.log('test')
            params.flag = false;
            if (getCss(target, "left") !== "auto") {
                params.left = getCss(target, "left");
            }
            if (getCss(target, "top") !== "auto") {
                params.top = getCss(target, "top");
            }
        };
        document.onmousemove = function (event) {
            var e = event ? event : window.event;
            if (params.flag) {
                var nowX = e.clientX, nowY = e.clientY;
                var disX = nowX - params.currentX, disY = nowY - params.currentY;
                target.style.left = parseInt(params.left) + disX + "px";
                target.style.top = parseInt(params.top) + disY + "px";
            }
        }
    };
複製程式碼

元素跟著動起來啦!

可以嘗試把這裡的 DEMO 和 drag 的方法合併使用,這裡就不贅述啦。

上述為PC的拖動,我們把chrome的模擬器切到手機模式,oh myGod!!炫酷的效果全都失效了!!!!桑心~~~~

下面我們來看下移動端的拖拽操作:

在這之前先對touch事件有一個瞭解:學習連結

有一些不得不說的概念:

  1. screenX: 觸控點相對於螢幕左邊緣的 x 座標。

  2. screenY: 觸控點相對於螢幕上邊緣的 y 座標。

  3. clientX: 觸控點相對於瀏覽器的 viewport左邊緣的 x 座標。不會包括左邊的滾動距離。

  4. clientY: 觸控點相對於瀏覽器的 viewport上邊緣的 y 座標。不會包括上邊的滾動距離。

  5. pageX: 觸控點相對於 document的左邊緣的 x 座標。 與 clientX不同的是,他包括左邊滾動的距離,如果有的話。

  6. pageY: 觸控點相對於 document的左邊緣的 y 座標。 與 clientY不同的是,他包括上邊滾動的距離,如果有的話。

  7. target: 總是表示 手指最開始放在觸控裝置上的觸發點所在位置的 element。即使已經移出了元素甚至移出了document, 他表示的element仍然不變

掌握了上述概念開啟控制檯,讓我來觀察元素的運動吧!!!

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style type="text/css">
        #content {
            width: 200px;
            height: 200px;
            background: #ff6632;
            position: absolute;
            left: 0;
            top: 0;
        }
    </style>
</head>

<body>
    <div id="content">test</div>
</body>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script>
    (function (window) {  //傳入window,提高變數的查詢效率
        function myQuery(selector) {  //這個函式就是對外提供的介面。
            //呼叫這個函式的原型物件上的_init方法,並返回
            return myQuery.prototype._init(selector);
        }
        myQuery.prototype = {
            /*初始化方法,獲取當前query物件的方法*/
            _init: function (selector) {
                if (typeof selector == "string") {
                    //把查詢到的元素存入到這個原型物件上。
                    this.ele = window.document.querySelector(selector);
                    //返回值其實就是原型物件。
                    return this;
                }
            },
            /*單擊事件:
             * 為了規避click的300ms的延遲,自定義一個單擊事件
             * 觸發時間:
             *   當抬起手指的時候觸發
             *   需要判斷手指落下和手指抬起的事件間隔,如果小於500ms表示單擊時間。
             *
             *   如果是大於等於500ms,算是長按時間
             * */
            tap: function (handler) {
                this.ele.addEventListener("touchstart", touchFn);
                this.ele.addEventListener("touchend", touchFn);

                var startTime,
                    endTime;

                function touchFn(e) {
                    e.preventDefault()
                    switch (e.type) {
                        case "touchstart":
                            startTime = new Date().getTime();
                            break;
                        case "touchend":
                            endTime = new Date().getTime();
                            if (endTime - startTime < 500) {
                                handler.call(this, e);
                            }
                            break;
                    }
                }
            },
            /**
             * 長按
             * @param handler
             */
            longTag: function (handler) {
                this.ele.addEventListener("touchstart", touchFn);
                this.ele.addEventListener("touchmove", touchFn);
                this.ele.addEventListener("touchend", touchFn);
                var timerId;

                function touchFn(e) {
                    switch (e.type) {
                        case "touchstart":  //500ms之後執行
                            timerId = setTimeout(function () {
                                handler.call(this, e);
                            }, 500)
                            break;
                        case "touchmove":
                            //如果中間有移動也清除定時器
                            clearTimeout(timerId)
                            break;
                        case "touchend":
                            //如果在500ms之內抬起了手指,則需要定時器
                            clearTimeout(timerId);
                            break;
                    }
                }
            },
            /**
             * 左側滑動。
                記錄手指按下的左邊,在離開的時候計算 deltaX是否滿足左滑的條件
             *
             */
            slide: function (handler) {
                this.ele.addEventListener("touchstart", touchFn);
                this.ele.addEventListener("touchend", touchFn);
                var startX, startY, endX, endY;

                function touchFn(e) {
                    e.preventDefault();
                    var firstTouch = e.changedTouches[0];
                    switch (e.type) {
                        case "touchstart":
                            startX = firstTouch.pageX;
                            startY = firstTouch.pageY;
                            break;
                        case "touchend":
                            endX = firstTouch.pageX;
                            endY = firstTouch.pageY;
                            //x方向移動大於y方向的移動,並且x方向的移動大於25個畫素,表示在向左側滑動
                            if (Math.abs(endX - startX) >= Math.abs(endY - startY) && startX - endX >= 25) {
                                handler.call(this, e, 'left');
                            }else if(Math.abs(endX - startX) >= Math.abs(endY - startY) && startX - endX < -25){
                                handler.call(this, e, 'right');
                            }else if(Math.abs(endX - startX) < Math.abs(endY - startY) && startY - endY >= 25){
                                handler.call(this, e, 'up');
                            }else if(Math.abs(endX - startX) < Math.abs(endY - startY) && startY - endY < -25){
                                handler.call(this, e, 'down');
                            }
                            break;
                    }
                }
            },
            /**
             * 右側滑動。
             *
             */
            slideRight: function (e) {
                
            }
        }
        window.$$ = window.myQuery = myQuery;
    })(window);

    $$("div").tap(function (e) {
        console.log("單擊事件")
    })
    $$("div").slide(function (e, type) {
        let content = ''
        console.log(this);
        type === 'left' && (content = "左側滑動了.....");
        type === 'right' && (content = "右側滑動了...."); 
        type === 'up' && (content = "向上滑動了...."); 
        type === 'down' && (content = "向下滑動了....");
        this.innerHTML = content;
    })
    $$("div").longTag(function () {
        console.log("長按事件");
    })


    var _x_start, _y_start, _x_move, _y_move, _x_end, _y_end, left_start, top_start;
    document.getElementById("content").addEventListener("touchstart", function (e) {

        _x_start = e.touches[0].pageX;
        _y_start = e.touches[0].pageY;
        left_start = $("#content").css("left");
        top_start = $("#content").css("top");

    })
    document.getElementById("content").addEventListener("touchmove", function (e) {
        _x_move = e.touches[0].pageX;
        _y_move = e.touches[0].pageY;
        $("#content").css("left", parseFloat(_x_move) - parseFloat(_x_start) + parseFloat(left_start) + "px");
        $("#content").css("top", parseFloat(_y_move) - parseFloat(_y_start) + parseFloat(top_start) + "px");
    })
    document.getElementById("content").addEventListener("touchend", function (e) {
        // var _x_end=e.changedTouches[0].pageX;
        // var _y_end=e.changedTouches[0].pageY;
        // console.log("end",_x_end)
    })
    //阻止瀏覽器下拉事件
    $('body').on('touchmove', function (event) { event.preventDefault(); });
</script>

</html>
複製程式碼

是不是覺得可以很自由的操控一個元素了?哈哈哈。。。

接下來我們在看一個進度條的例子:

這裡有一點對range的擴充套件,有興趣的可以瞭解一下,不看也不影響理解。--學習連結

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style type="text/css">
        .scroll {
            top: 100px;
            left: 100px;
            position: relative;
            width: 500px;
            height: 5px;
            background: rgb(180, 180, 180);
        }

        .bar {
            position: absolute;
            top: -3px;
            height: 10px;
            width: 8px;
            background: rgb(255, 0, 0);
        }

        .mask {
            position: absolute;
            height: 5px;
            background: rgb(255, 0, 0);
        }
    </style>
</head>
<body>
    <div class="scroll">
        <div class="bar"></div>
        <div class="mask"></div>
    </div>
    <p></p>
</body>

<script>
    // debugger
    var bar = document.getElementsByClassName('bar')[0];
    var scroll = document.getElementsByClassName('scroll')[0];
    var bar = document.getElementsByClassName('bar')[0];
    var mask = document.getElementsByClassName('mask')[0];
    bar.onmousedown = function(e){
        var leftVal = e.clientX - this.offsetLeft;
        var that = this;
        document.onmousemove = function(e){
            barLeft = e.clientX - leftVal;
            if(barLeft < 0){
                barLeft = 0
            }else if(barLeft > scroll.offsetWidth - bar.offsetWidth){
                barLeft = scroll.offsetWidth - bar.offsetWidth;
            }
            bar.style.left = barLeft + 'px';
            mask.style.width = barLeft + 'px';
            document.getElementsByTagName('p')[0].innerHTML = '已經完成了' + Math.floor((barLeft / (scroll.offsetWidth - bar.offsetWidth))*100) + '%' 
            //防止拖動過快時滑鼠彈起後mousemove還在生效
            window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
        }
    }
    document.onmouseup = function () {
        document.onmousemove = null; //彈起滑鼠不做任何操作
    }
</script>
</html>
複製程式碼

是不是感覺還挺實用的,哈哈哈!

好啦,今天先扯到這裡。

end。。。。。

相關文章