成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

前端基礎(chǔ)進(jìn)階(十):面向?qū)ο髮?shí)戰(zhàn)之封裝拖拽對(duì)象

Eidesen / 757人閱讀

摘要:前面幾篇文章,我跟大家分享了的一些基礎(chǔ)知識(shí),這篇文章,將會(huì)進(jìn)入第一個(gè)實(shí)戰(zhàn)環(huán)節(jié)利用前面幾章的所涉及到的知識(shí),封裝一個(gè)拖拽對(duì)象。不封裝對(duì)象直接實(shí)現(xiàn)利用原生封裝拖拽對(duì)象通過擴(kuò)展來實(shí)現(xiàn)拖拽對(duì)象。

前面幾篇文章,我跟大家分享了JavaScript的一些基礎(chǔ)知識(shí),這篇文章,將會(huì)進(jìn)入第一個(gè)實(shí)戰(zhàn)環(huán)節(jié):利用前面幾章的所涉及到的知識(shí),封裝一個(gè)拖拽對(duì)象。為了能夠幫助大家了解更多的方式與進(jìn)行對(duì)比,我會(huì)使用三種不同的方式來實(shí)現(xiàn)拖拽。

不封裝對(duì)象直接實(shí)現(xiàn);

利用原生JavaScript封裝拖拽對(duì)象;

通過擴(kuò)展jQuery來實(shí)現(xiàn)拖拽對(duì)象。

本文的例子會(huì)放置于codepen.io中,供大家在閱讀時(shí)直接查看。如果對(duì)于codepen不了解的同學(xué),可以花點(diǎn)時(shí)間稍微了解一下。

拖拽的實(shí)現(xiàn)過程會(huì)涉及到非常多的實(shí)用小知識(shí),因此為了鞏固我自己的知識(shí)積累,也為了大家能夠?qū)W到更多的知識(shí),我會(huì)盡量詳細(xì)的將一些細(xì)節(jié)分享出來,相信大家認(rèn)真閱讀之后,一定能學(xué)到一些東西。

1、如何讓一個(gè)DOM元素動(dòng)起來

我們常常會(huì)通過修改元素的top,left,translate來其的位置發(fā)生改變。在下面的例子中,每點(diǎn)擊一次按鈕,對(duì)應(yīng)的元素就會(huì)移動(dòng)5px。大家可點(diǎn)擊查看。

點(diǎn)擊查看一個(gè)讓元素動(dòng)起來的小例子

由于修改一個(gè)元素top/left值會(huì)引起頁面重繪,而translate不會(huì),因此從性能優(yōu)化上來判斷,我們會(huì)優(yōu)先使用translate屬性。
2、如何獲取當(dāng)前瀏覽器支持的transform兼容寫法

transform是css3的屬性,當(dāng)我們使用它時(shí)就不得不面對(duì)兼容性的問題。不同版本瀏覽器的兼容寫法大致有如下幾種:

["transform", "webkitTransform", "MozTransform", "msTransform", "OTransform"]

因此我們需要判斷當(dāng)前瀏覽器環(huán)境支持的transform屬性是哪一種,方法如下:

// 獲取當(dāng)前瀏覽器支持的transform兼容寫法
function getTransform() {
    var transform = "",
        divStyle = document.createElement("div").style,
        // 可能涉及到的幾種兼容性寫法,通過循環(huán)找出瀏覽器識(shí)別的那一個(gè)
        transformArr = ["transform", "webkitTransform", "MozTransform", "msTransform", "OTransform"],

        i = 0,
        len = transformArr.length;

    for(; i < len; i++)  {
        if(transformArr[i] in divStyle) {
            // 找到之后立即返回,結(jié)束函數(shù)
            return transform = transformArr[i];
        }
    }

    // 如果沒有找到,就直接返回空字符串
    return transform;
}

該方法用于獲取瀏覽器支持的transform屬性。如果返回的為空字符串,則表示當(dāng)前瀏覽器并不支持transform,這個(gè)時(shí)候我們就需要使用left,top值來改變?cè)氐奈恢谩H绻С?,就改變transform的值。

3、 如何獲取元素的初始位置

我們首先需要獲取到目標(biāo)元素的初始位置,因此這里我們需要一個(gè)專門用來獲取元素樣式的功能函數(shù)。

但是獲取元素樣式在IE瀏覽器與其他瀏覽器有一些不同,因此我們需要一個(gè)兼容性的寫法。

function getStyle(elem, property) {
    // ie通過currentStyle來獲取元素的樣式,其他瀏覽器通過getComputedStyle來獲取
    return document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(elem, false)[property] : elem.currentStyle[property];
}

有了這個(gè)方法之后,就可以開始動(dòng)手寫獲取目標(biāo)元素初始位置的方法了。

function getTargetPos(elem) {
    var pos = {x: 0, y: 0};
    var transform = getTransform();
    if(transform) {
        var transformValue = getStyle(elem, transform);
        if(transformValue == "none") {
            elem.style[transform] = "translate(0, 0)";
            return pos;
        } else {
            var temp = transformValue.match(/-?d+/g);
            return pos = {
                x: parseInt(temp[4].trim()),
                y: parseInt(temp[5].trim())
            }
        }
    } else {
        if(getStyle(elem, "position") == "static") {
            elem.style.position = "relative";
            return pos;
        } else {
            var x = parseInt(getStyle(elem, "left") ? getStyle(elem, "left") : 0);
            var y = parseInt(getStyle(elem, "top") ? getStyle(elem, "top") : 0);
            return pos = {
                x: x,
                y: y
            }
        }
    }
}

在拖拽過程中,我們需要不停的設(shè)置目標(biāo)元素的新位置,這樣它才會(huì)移動(dòng)起來,因此我們需要一個(gè)設(shè)置目標(biāo)元素位置的方法。

// pos = { x: 200, y: 100 }
function setTargetPos(elem, pos) {
    var transform = getTransform();
    if(transform) {
        elem.style[transform] = "translate("+ pos.x +"px, "+ pos.y +"px)";
    } else {
        elem.style.left = pos.x + "px";
        elem.style.top = pos.y + "px";
    }
    return elem;
}
5、我們需要用到哪些事件?

在pc上的瀏覽器中,結(jié)合mousedown、mousemove、mouseup這三個(gè)事件可以幫助我們實(shí)現(xiàn)拖拽。

mousedown 鼠標(biāo)按下時(shí)觸發(fā)

mousemove 鼠標(biāo)按下后拖動(dòng)時(shí)觸發(fā)

mouseup 鼠標(biāo)松開時(shí)觸發(fā)

而在移動(dòng)端,分別與之對(duì)應(yīng)的則是touchstart、touchmove、touchend

當(dāng)我們將元素綁定這些事件時(shí),有一個(gè)事件對(duì)象將會(huì)作為參數(shù)傳遞給回調(diào)函數(shù),通過事件對(duì)象,我們可以獲取到當(dāng)前鼠標(biāo)的精確位置,鼠標(biāo)位置信息是實(shí)現(xiàn)拖拽的關(guān)鍵。

事件對(duì)象十分重要,其中包含了非常多的有用的信息,這里我就不擴(kuò)展了,大家可以在函數(shù)中將事件對(duì)象打印出來查看其中的具體屬性,這個(gè)方法對(duì)于記不清事件對(duì)象重要屬性的童鞋非常有用。
6、拖拽的原理

當(dāng)事件觸發(fā)時(shí),我們可以通過事件對(duì)象獲取到鼠標(biāo)的精切位置。這是實(shí)現(xiàn)拖拽的關(guān)鍵。當(dāng)鼠標(biāo)按下(mousedown觸發(fā))時(shí),我們需要記住鼠標(biāo)的初始位置與目標(biāo)元素的初始位置,我們的目標(biāo)就是實(shí)現(xiàn)當(dāng)鼠標(biāo)移動(dòng)時(shí),目標(biāo)元素也跟著移動(dòng),根據(jù)常理我們可以得出如下關(guān)系:

移動(dòng)后的鼠標(biāo)位置 - 鼠標(biāo)初始位置 = 移動(dòng)后的目標(biāo)元素位置 - 目標(biāo)元素的初始位置

如果鼠標(biāo)位置的差值我們用dis來表示,那么目標(biāo)元素的位置就等于:

移動(dòng)后目標(biāo)元素的位置 = dis + 目標(biāo)元素的初始位置

通過事件對(duì)象,我們可以精確的知道鼠標(biāo)的當(dāng)前位置,因此當(dāng)鼠標(biāo)拖動(dòng)(mousemove)時(shí),我們可以不停的計(jì)算出鼠標(biāo)移動(dòng)的差值,以此來求出目標(biāo)元素的當(dāng)前位置。這個(gè)過程,就實(shí)現(xiàn)了拖拽。

而在鼠標(biāo)松開(mouseup)結(jié)束拖拽時(shí),我們需要處理一些收尾工作。詳情見代碼。

7、 我又來推薦思維導(dǎo)圖輔助寫代碼了

常常有新人朋友跑來問我,如果邏輯思維能力不強(qiáng),能不能寫代碼做前端。我的答案是:能。因?yàn)榻柚季S導(dǎo)圖,可以很輕松的彌補(bǔ)邏輯的短板。而且比在自己頭腦中腦補(bǔ)邏輯更加清晰明了,不易出錯(cuò)。

上面第六點(diǎn)我介紹了原理,因此如何做就顯得不是那么難了,而具體的步驟,則在下面的思維導(dǎo)圖中明確給出,我們只需要按照這個(gè)步驟來寫代碼即可,試試看,一定很輕松。

8、代碼實(shí)現(xiàn)

part1、準(zhǔn)備工作

// 獲取目標(biāo)元素對(duì)象
var oElem = document.getElementById("target");

// 聲明2個(gè)變量用來保存鼠標(biāo)初始位置的x,y坐標(biāo)
var startX = 0;
var startY = 0;

// 聲明2個(gè)變量用來保存目標(biāo)元素初始位置的x,y坐標(biāo)
var sourceX = 0;
var sourceY = 0;

part2、功能函數(shù)

因?yàn)橹耙呀?jīng)貼過代碼,就不再重復(fù)

// 獲取當(dāng)前瀏覽器支持的transform兼容寫法
function getTransform() {}

// 獲取元素屬性
function getStyle(elem, property) {}

// 獲取元素的初始位置
function getTargetPos(elem) {}

// 設(shè)置元素的初始位置
function setTargetPos(elem, potions) {}

part3、聲明三個(gè)事件的回調(diào)函數(shù)

這三個(gè)方法就是實(shí)現(xiàn)拖拽的核心所在,我將嚴(yán)格按照上面思維導(dǎo)圖中的步驟來完成我們的代碼。

// 綁定在mousedown上的回調(diào),event為傳入的事件對(duì)象
function start(event) {
    // 獲取鼠標(biāo)初始位置
    startX = event.pageX;
    startY = event.pageY;

    // 獲取元素初始位置
    var pos = getTargetPos(oElem);

    sourceX = pos.x;
    sourceY = pos.y;

    // 綁定
    document.addEventListener("mousemove", move, false);
    document.addEventListener("mouseup", end, false);
}

function move(event) {
    // 獲取鼠標(biāo)當(dāng)前位置
    var currentX = event.pageX;
    var currentY = event.pageY;

    // 計(jì)算差值
    var distanceX = currentX - startX;
    var distanceY = currentY - startY;

    // 計(jì)算并設(shè)置元素當(dāng)前位置
    setTargetPos(oElem, {
        x: (sourceX + distanceX).toFixed(),
        y: (sourceY + distanceY).toFixed()
    })
}

function end(event) {
    document.removeEventListener("mousemove", move);
    document.removeEventListener("mouseup", end);
    // do other things
}

OK,一個(gè)簡(jiǎn)單的拖拽,就這樣愉快的實(shí)現(xiàn)了。點(diǎn)擊下面的鏈接,可以在線查看該例子的demo。

使用原生js實(shí)現(xiàn)拖拽

9、封裝拖拽對(duì)象

在前面一章我給大家分享了面向?qū)ο笕绾螌?shí)現(xiàn),基于那些基礎(chǔ)知識(shí),我們來將上面實(shí)現(xiàn)的拖拽封裝為一個(gè)拖拽對(duì)象。我們的目標(biāo)是,只要我們聲明一個(gè)拖拽實(shí)例,那么傳入的目標(biāo)元素將自動(dòng)具備可以被拖拽的功能。

在實(shí)際開發(fā)中,一個(gè)對(duì)象我們常常會(huì)多帶帶放在一個(gè)js文件中,這個(gè)js文件將多帶帶作為一個(gè)模塊,利用各種模塊的方式組織起來使用。當(dāng)然這里沒有復(fù)雜的模塊交互,因?yàn)檫@個(gè)例子,我們只需要一個(gè)模塊即可。

為了避免變量污染,我們需要將模塊放置于一個(gè)函數(shù)自執(zhí)行方式模擬的塊級(jí)作用域中。

;
(function() {
    ...
})();
在普通的模塊組織中,我們只是單純的將許多js文件壓縮成為一個(gè)js文件,因此此處的第一個(gè)分號(hào)則是為了防止上一個(gè)模塊的結(jié)尾不用分號(hào)導(dǎo)致報(bào)錯(cuò)。必不可少。當(dāng)然在通過require或者ES6模塊等方式就不會(huì)出現(xiàn)這樣的情況。

我們知道,在封裝一個(gè)對(duì)象的時(shí)候,我們可以將屬性與方法放置于構(gòu)造函數(shù)或者原型中,而在增加了自執(zhí)行函數(shù)之后,我們又可以將屬性和方法防止與模塊的內(nèi)部作用域。這是閉包的知識(shí)。

那么我們面臨的挑戰(zhàn)就在于,如何合理的處理屬性與方法的位置。

當(dāng)然,每一個(gè)對(duì)象的情況都不一樣,不能一概而論,我們需要清晰的知道這三種位置的特性才能做出最適合的決定。

構(gòu)造函數(shù)中: 屬性與方法為當(dāng)前實(shí)例多帶帶擁有,只能被當(dāng)前實(shí)例訪問,并且每聲明一個(gè)實(shí)例,其中的方法都會(huì)被重新創(chuàng)建一次。

原型中: 屬性與方法為所有實(shí)例共同擁有,可以被所有實(shí)例訪問,新聲明實(shí)例不會(huì)重復(fù)創(chuàng)建方法。

模塊作用域中:屬性和方法不能被任何實(shí)例訪問,但是能被內(nèi)部方法訪問,新聲明的實(shí)例,不會(huì)重復(fù)創(chuàng)建相同的方法。

對(duì)于方法的判斷比較簡(jiǎn)單。

因?yàn)樵跇?gòu)造函數(shù)中的方法總會(huì)在聲明一個(gè)新的實(shí)例時(shí)被重復(fù)創(chuàng)建,因此我們聲明的方法都盡量避免出現(xiàn)在構(gòu)造函數(shù)中。

而如果你的方法中需要用到構(gòu)造函數(shù)中的變量,或者想要公開,那就需要放在原型中。

如果方法需要私有不被外界訪問,那么就放置在模塊作用域中。

對(duì)于屬性放置于什么位置有的時(shí)候很難做出正確的判斷,因此我很難給出一個(gè)準(zhǔn)確的定義告訴你什么屬性一定要放在什么位置,這需要在實(shí)際開發(fā)中不斷的總結(jié)經(jīng)驗(yàn)。但是總的來說,仍然要結(jié)合這三個(gè)位置的特性來做出最合適的判斷。

如果屬性值只能被實(shí)例多帶帶擁有,比如person對(duì)象的name,只能屬于某一個(gè)person實(shí)例,又比如這里拖拽對(duì)象中,某一個(gè)元素的初始位置,也僅僅只是這個(gè)元素的當(dāng)前位置,這個(gè)屬性,則適合放在構(gòu)造函數(shù)中。

而如果一個(gè)屬性僅僅供內(nèi)部方法訪問,這個(gè)屬性就適合放在模塊作用域中。

關(guān)于面向?qū)ο?,上面的幾點(diǎn)思考我認(rèn)為是這篇文章最值得認(rèn)真思考的精華。如果在封裝時(shí)沒有思考清楚,很可能會(huì)遇到很多你意想不到的bug,所以建議大家結(jié)合自己的開發(fā)經(jīng)驗(yàn),多多思考,總結(jié)出自己的觀點(diǎn)。

根據(jù)這些思考,大家可以自己嘗試封裝一下。然后與我的做一些對(duì)比,看看我們的想法有什么不同,在下面例子的注釋中,我將自己的想法表達(dá)出來。

點(diǎn)擊查看已經(jīng)封裝好的demo

js 源碼

;
(function() {
    // 這是一個(gè)私有屬性,不需要被實(shí)例訪問
    var transform = getTransform();

    function Drag(selector) {
        // 放在構(gòu)造函數(shù)中的屬性,都是屬于每一個(gè)實(shí)例多帶帶擁有
        this.elem = typeof selector == "Object" ? selector : document.getElementById(selector);
        this.startX = 0;
        this.startY = 0;
        this.sourceX = 0;
        this.sourceY = 0;

        this.init();
    }


    // 原型
    Drag.prototype = {
        constructor: Drag,

        init: function() {
            // 初始時(shí)需要做些什么事情
            this.setDrag();
        },

        // 稍作改造,僅用于獲取當(dāng)前元素的屬性,類似于getName
        getStyle: function(property) {
            return document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(this.elem, false)[property] : this.elem.currentStyle[property];
        },

        // 用來獲取當(dāng)前元素的位置信息,注意與之前的不同之處
        getPosition: function() {
            var pos = {x: 0, y: 0};
            if(transform) {
                var transformValue = this.getStyle(transform);
                if(transformValue == "none") {
                    this.elem.style[transform] = "translate(0, 0)";
                } else {
                    var temp = transformValue.match(/-?d+/g);
                    pos = {
                        x: parseInt(temp[4].trim()),
                        y: parseInt(temp[5].trim())
                    }
                }
            } else {
                if(this.getStyle("position") == "static") {
                    this.elem.style.position = "relative";
                } else {
                    pos = {
                        x: parseInt(this.getStyle("left") ? this.getStyle("left") : 0),
                        y: parseInt(this.getStyle("top") ? this.getStyle("top") : 0)
                    }
                }
            }

            return pos;
        },

        // 用來設(shè)置當(dāng)前元素的位置
        setPostion: function(pos) {
            if(transform) {
                this.elem.style[transform] = "translate("+ pos.x +"px, "+ pos.y +"px)";
            } else {
                this.elem.style.left = pos.x + "px";
                this.elem.style.top = pos.y + "px";
            }
        },

        // 該方法用來綁定事件
        setDrag: function() {
            var self = this;
            this.elem.addEventListener("mousedown", start, false);
            function start(event) {
                self.startX = event.pageX;
                self.startY = event.pageY;

                var pos = self.getPosition();

                self.sourceX = pos.x;
                self.sourceY = pos.y;

                document.addEventListener("mousemove", move, false);
                document.addEventListener("mouseup", end, false);
            }

            function move(event) {
                var currentX = event.pageX;
                var currentY = event.pageY;

                var distanceX = currentX - self.startX;
                var distanceY = currentY - self.startY;

                self.setPostion({
                    x: (self.sourceX + distanceX).toFixed(),
                    y: (self.sourceY + distanceY).toFixed()
                })
            }

            function end(event) {
                document.removeEventListener("mousemove", move);
                document.removeEventListener("mouseup", end);
                // do other things
            }
        }
    }

    // 私有方法,僅僅用來獲取transform的兼容寫法
    function getTransform() {
        var transform = "",
            divStyle = document.createElement("div").style,
            transformArr = ["transform", "webkitTransform", "MozTransform", "msTransform", "OTransform"],

            i = 0,
            len = transformArr.length;

        for(; i < len; i++)  {
            if(transformArr[i] in divStyle) {
                return transform = transformArr[i];
            }
        }

        return transform;
    }

    // 一種對(duì)外暴露的方式
    window.Drag = Drag;
})();

// 使用:聲明2個(gè)拖拽實(shí)例
new Drag("target");
new Drag("target2");

這樣一個(gè)拖拽對(duì)象就封裝完畢了。

建議大家根據(jù)我提供的思維方式,多多嘗試封裝一些組件。比如封裝一個(gè)彈窗,封裝一個(gè)循環(huán)輪播等。練得多了,面向?qū)ο缶筒辉偈菃栴}了。這種思維方式,在未來任何時(shí)候都是能夠用到的。

下一章分析jQuery對(duì)象的實(shí)現(xiàn),與如何將我們這里封裝的拖拽對(duì)象擴(kuò)展為jQuery插件。

前端基礎(chǔ)進(jìn)階系列目錄

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/90546.html

相關(guān)文章

  • 前端基礎(chǔ)進(jìn)階目錄

    摘要:不過其實(shí)簡(jiǎn)書文章評(píng)論里有很多大家的問題以及解答,對(duì)于進(jìn)一步理解文中知識(shí)幫助很大的,算是有點(diǎn)可惜吧。不過也希望能夠?qū)φ趯W(xué)習(xí)前端的你有一些小幫助。如果在閱讀中發(fā)現(xiàn)了一些錯(cuò)誤,請(qǐng)?jiān)谠u(píng)論里告訴我,我會(huì)及時(shí)更改。 前端基礎(chǔ)進(jìn)階(一):內(nèi)存空間詳細(xì)圖解 前端基礎(chǔ)進(jìn)階(二):執(zhí)行上下文詳細(xì)圖解 前端基礎(chǔ)進(jìn)階(三):變量對(duì)象詳解 前端基礎(chǔ)進(jìn)階(四):詳細(xì)圖解作用域鏈與閉包 前端基礎(chǔ)進(jìn)階(五):全方位...

    mo0n1andin 評(píng)論0 收藏0
  • 前端2018現(xiàn)在上車還還得及么

    摘要:面向?qū)ο笕筇卣骼^承性多態(tài)性封裝性接口。第五階段封裝一個(gè)屬于自己的框架框架封裝基礎(chǔ)事件流冒泡捕獲事件對(duì)象事件框架選擇框架。核心模塊和對(duì)象全局對(duì)象,,,事件驅(qū)動(dòng),事件發(fā)射器加密解密,路徑操作,序列化和反序列化文件流操作服務(wù)端與客戶端。 第一階段: HTML+CSS:HTML進(jìn)階、CSS進(jìn)階、div+css布局、HTML+css整站開發(fā)、 JavaScript基礎(chǔ):Js基礎(chǔ)教程、js內(nèi)置對(duì)...

    stormgens 評(píng)論0 收藏0
  • 前端2018現(xiàn)在上車還還得及么

    摘要:面向?qū)ο笕筇卣骼^承性多態(tài)性封裝性接口。第五階段封裝一個(gè)屬于自己的框架框架封裝基礎(chǔ)事件流冒泡捕獲事件對(duì)象事件框架選擇框架。核心模塊和對(duì)象全局對(duì)象,,,事件驅(qū)動(dòng),事件發(fā)射器加密解密,路徑操作,序列化和反序列化文件流操作服務(wù)端與客戶端。 第一階段: HTML+CSS:HTML進(jìn)階、CSS進(jìn)階、div+css布局、HTML+css整站開發(fā)、 JavaScript基礎(chǔ):Js基礎(chǔ)教程、js內(nèi)置對(duì)...

    mylxsw 評(píng)論0 收藏0
  • javasscript - 收藏集 - 掘金

    摘要:跨域請(qǐng)求詳解從繁至簡(jiǎn)前端掘金什么是為什么要用是的一種使用模式,可用于解決主流瀏覽器的跨域數(shù)據(jù)訪問的問題。異步編程入門道典型的面試題前端掘金在界中,開發(fā)人員的需求量一直居高不下。 jsonp 跨域請(qǐng)求詳解——從繁至簡(jiǎn) - 前端 - 掘金什么是jsonp?為什么要用jsonp?JSONP(JSON with Padding)是JSON的一種使用模式,可用于解決主流瀏覽器的跨域數(shù)據(jù)訪問的問題...

    Rango 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<