前端學習筆記の拖拽(一)原生基礎(chǔ)篇

前端學習筆記の拖拽(一)#

源碼地址:https://github.com/WZOnePiece/study-draggable
引言:9月開始的半個月陶衅,由于項目和一些朋友的詢問,我接觸了關(guān)于前端拖拽的一些問題膨俐。不過本人由于只是一個前端菜鳥焚刺,接觸前端時間滿打滿算门烂,從0到現(xiàn)在也就一年左右,對于一些知識了解的還不是很全面和深刻蔓姚。所以赂乐,我就打算好好學習下前端拖拽得一些相關(guān)知識咖气。這就是這篇“史詩級的錦繡文章”的誕生記O(∩_∩)O挖滤。
借鑒:http://www.cnblogs.com/lrzw32/p/4696655.html

js拖拽插件的原理##

常見的拖拽操作是什么樣的呢斩松?整過過程大概有下面幾個步驟:

1觉既、用鼠標點擊被拖拽的元素

2、按住鼠標不放钧椰,移動鼠標

3嫡霞、拖拽元素到一定位置,放開鼠標

這里的過程涉及到三個dom事件:onmousedown,onmousemove,onmouseup诊沪。所以拖拽的基本思路就是:

1曾撤、用鼠標點擊被拖拽的元素觸發(fā)onmousedown

(1)設(shè)置當前元素的可拖拽為true挤悉,表示可以拖拽

(2)記錄當前鼠標的坐標x,y

(3)記錄當前元素的坐標x,y

2、移動鼠標觸發(fā)onmousemove

(1)判斷元素是否可拖拽昏鹃,如果是則進入步驟2,否則直接返回

(2)如果元素可拖拽衅斩,則設(shè)置元素的坐標

元素的x坐標 = 鼠標移動的橫向距離+元素本來的x坐標 = 鼠標現(xiàn)在的x坐標 - 鼠標之前的x坐標 + 元素本來的x坐標

元素的y坐標 = 鼠標移動的橫向距離+元素本來的y坐標 = 鼠標現(xiàn)在的y坐標 - 鼠標之前的y坐標 + 元素本來的y坐標

3盆顾、放開鼠標觸發(fā)onmouseup

(1)將鼠標的可拖拽狀態(tài)設(shè)置成false

基本效果##

在實現(xiàn)基本的效果之前怠褐,有幾點需要說明的:

1畏梆、元素想要被拖動,它的postion屬性一定要是relative或absolute

2奈懒、通過event.clientX和event.clientY獲取鼠標的坐標

3奠涌、onmousemove是綁定在document元素上而不是拖拽元素本身,這樣能解決快速拖動造成的延遲或停止移動的問題

代碼如下:

var dragObj = document.getElementById("test");
dragObj.style.left = "0px";
dragObj.style.top = "0px";

var mouseX, mouseY, objX, objY;
var dragging = false;

dragObj.onmousedown = function (event) {
    event = event || window.event;

    dragging = true;
    dragObj.style.position = "relative";

    mouseX = event.clientX;
    mouseY = event.clientY;
    objX = parseInt(dragObj.style.left);
    objY = parseInt(dragObj.style.top);
}

document.onmousemove = function (event) {
    event = event || window.event;
    if (dragging) {

        dragObj.style.left = parseInt(event.clientX - mouseX + objX) + "px";
        dragObj.style.top = parseInt(event.clientY - mouseY + objY) + "px";
    }

}

document.onmouseup = function () {
    dragging = false;
}

代碼抽象和優(yōu)化##

上面的代碼要做成插件磷杏,要將其抽象出來溜畅,基本思路如下:

; (function (window, undefined) {            

        function Drag(ele) {}

        window.Drag = Drag;
    })(window, undefined);

用自執(zhí)行匿名函數(shù)將代碼包起來,內(nèi)部定義Drag方法并暴露到全局中慈格,直接調(diào)用Drag蒜田,傳入被拖拽的元素冲粤。

前面加入梯捕; 避免一些不必要錯誤傀顾。

首先對一些常用的方法進行簡單的封裝:

; (function (window, undefined) {
        var dom = {
            //綁定事件
            on: function (node, eventName, handler) {
                if (node.addEventListener) {
                    node.addEventListener(eventName, handler);
                }
                else {
                    node.attachEvent("on" + eventName, handler);
                }
            },
            //獲取元素的樣式
            getStyle: function (node, styleName) {
                var realStyle = null;
                if (window.getComputedStyle) {
                    realStyle = window.getComputedStyle(node, null)[styleName];
                }
                else if (node.currentStyle) {
                    realStyle = node.currentStyle[styleName];
                }
                return realStyle;
            },
            //獲取設(shè)置元素的樣式
            setCss: function (node, css) {
                for (var key in css) {
                    node.style[key] = css[key];
                }
            }
        };
      
        window.Drag = Drag;
    })(window, undefined);

在一個拖拽操作中,存在著兩個對象:被拖拽的對象和鼠標對象错英,我們定義了下面的兩個對象以及它們對應(yīng)的操作:

首先的拖拽對象椭岩,它包含一個元素節(jié)點和拖拽之前的坐標x和y:

function DragElement(node) {
    this.node = node;//被拖拽的元素節(jié)點
    this.x = 0;//拖拽之前的x坐標
    this.y = 0;//拖拽之前的y坐標
}
DragElement.prototype = {
    constructor: DragElement,
    init: function () {                    
        this.setEleCss({
            "left": dom.getStyle(node, "left"),
            "top": dom.getStyle(node, "top")
        })
        .setXY(node.style.left, node.style.top);
    },
    //設(shè)置當前的坐標
    setXY: function (x, y) {
        this.x = parseInt(x) || 0;
        this.y = parseInt(y) || 0;
        return this;
    },
    //設(shè)置元素節(jié)點的樣式
    setEleCss: function (css) {
        dom.setCss(this.node, css);
        return this;
    }
}

還有一個對象是鼠標,它主要包含x坐標和y坐標:

function Mouse() {
        this.x = 0;
        this.y = 0;
    }
Mouse.prototype.setXY = function (x, y) {
    this.x = parseInt(x);
    this.y = parseInt(y);
}

這是在拖拽操作中定義的兩個對象塌计。

如果一個頁面可以有多個拖拽元素,那應(yīng)該注意什么:

1热芹、每個元素對應(yīng)一個拖拽對象實例

2伊脓、每個頁面只能有一個正在拖拽中的元素

為此报腔,我們定義了唯一一個對象用來保存相關(guān)的配置:

 var draggableConfig = {
     zIndex: 1,
   draggingObj: null,
     mouse: new Mouse()
};

這個對象中有三個屬性:

(1)zIndex:用來賦值給拖拽對象的zIndex屬性邪狞,有多個拖拽對象時帆卓,當兩個拖拽對象重疊時剑令,會造成當前拖拽對象有可能被擋住吁津,通過設(shè)置zIndex使其顯示在最頂層

(2)draggingObj:用來保存正在拖拽的對象碍脏,在這里去掉了前面的用來判斷是否可拖拽的變量,通過draggingObj來判斷當前是否可以拖拽以及獲取相應(yīng)的拖拽對象

(3)mouse:唯一的鼠標對象糊探,用來保存當前鼠標的坐標等信息

最后是綁定onmousedown褥紫,onmouseover髓考,onmouseout事件氨菇,整合上面的代碼如下:

/*
  author: NARUTOne,wz;
  creatTime: 2016/9/20;
  developing: 可以擴展:添加父元素限制门驾;多個同級元素拖拽順序排列等。
*/

; (function(window, undefined) {
  // 拖拽對象的事件綁定竣灌;樣式獲取初嘹、設(shè)置
  var dom = {
    on: function(node, eventName, handler) {
      if(node.addEventListener) {
        console.log(node);
        node.addEventListener(eventName, handler, false);
      }
      else {//IE
        node.attachEvent("on" + eventName, handler);
      }
    },
    getStyle: function(node, styleName) {
      var realStyle = null;
      if(window.getComputedStyle) {
        realStyle = window.getComputedStyle(node,null)[styleName];
      }
      else if(node.currentStyle) {//IE
        realStyle = node.currentStyle[styleName];
      }
      else if(node.style) {
        realStyle = node.style[styleName];
      }
      return realStyle;
    },
    setCss: function(node, css) {
      for(var key in css) {
        node.style[key] = css[key];
      }
    }
  };

// 拖拽對象定義
  function DragElement(node) {
    this.node = node;
    this.x = 0;
    this.y = 0;
  }

  DragElement.prototype = {
    constructor: DragElement,
    init: function() {
      this.setEleCss({
        "left": dom.getStyle(node, "left"),
        "top": dom.getStyle(node, "top")
      })
      .setXY(node.style.left, node.style.top)
    },
    // 設(shè)置當前坐標
    setXY: function(x, y) {
      this.x = parseInt(x) || 0;
      this.y = parseInt(y) || 0;
      return this;
    },
    // 設(shè)置元素節(jié)點的樣式
    setEleCss: function(css) {
      dom.setCss(this.node, css);
      return this;
    }
  }

  // 鼠標對象
  function Mouse() {
    this.x = 0;
    this.y = 0;
  }
  Mouse.prototype.setXY = function(x, y) {
    this.x = parseInt(x);
    this.y = parseInt(y);
  }

  // 拖拽配置
  var draggableConfig = {
    zIndex: 1,
    draggingObj: null,
    mouse: new Mouse(),
    parent:document,
    dragEle:null
  }

  // 拖拽實現(xiàn)
  function Drag(ele,parent) {
    this.ele = ele;

    function mouseDown(event) {
      var elem = this;

      draggableConfig.mouse.setXY(event.clientX, event.clientY);

      draggableConfig.draggingObj = new DragElement(elem);

      draggableConfig.draggingObj
        .setXY(elem.style.left, elem.style.top)
        .setEleCss({
          "zIndex": draggableConfig.zIndex ++,
          "position": "absolute"
        });
    }
    draggableConfig.parent = parent;
    draggableConfig.dragEle = ele;
    ele.onselectstart = function() {
      // 防止拖拽對象內(nèi)文字被選中
      return false;
    }
    dom.on(ele, "mousedown", mouseDown);
  }

  dom.on(draggableConfig.parent, "mousemove", function(e) {
    if(draggableConfig.draggingObj) {
      var mouse = draggableConfig.mouse,
        draggingObj = draggableConfig.draggingObj;
        // 這里可以添加限制驻龟,拖拽不脫離父元素
      var p_height = draggableConfig.parent.offsetHeight;
      var p_width = draggableConfig.parent.offsetWidth;
      var c_height = draggableConfig.dragEle.offsetHeight;
      var c_width = draggableConfig.dragEle.offsetWidth;
      var left, top = 0;

      if(event.clientX - mouse.x + draggingObj.x < 0) {
        left = 0;
      }
      else if((event.clientX - mouse.x + draggingObj.x) > (p_width - c_width)) {
        left = p_width - c_width;
      }
      else {
        left = event.clientX - mouse.x + draggingObj.x;
      }

      if(event.clientY - mouse.y + draggingObj.y < 0) {
        top = 0;
      }
      else if((event.clientY - mouse.y + draggingObj.y) > (p_height - c_height)) {
        top = p_height - c_height;
      }
      else {
        top = event.clientY - mouse.y + draggingObj.y;
      }

      draggingObj.setEleCss({
        "left": parseInt(left) + "px",
        "top": parseInt(top) + "px"
      })
    }
  })

  dom.on(draggableConfig.parent, "mouseup", function(event) {
    draggableConfig.draggingObj = null;
  })

  window.Drag = Drag;

})(window,undefined);

調(diào)用方法:Drag(document.getElementById("obj"));

注意點:為了防止選中拖拽元素中的文字,通過onselectstart事件處理程序return false來處理這個問題闯冷∩咭或者css3中的user-select:none;
srcElement是IE下的屬性,target是Firefox下的屬性荞怒,Chrome瀏覽器同時有這兩個屬性衰抑。
當然這個也是可以按照需要進行定制化擴展等呛踊。

擴展:有效拖拽##

有時谭网,會遇到一些拖拽需求愉择,只能拖拽需要拖拽模塊的部分區(qū)域衷戈,這時怎樣實現(xiàn)呢殖妇?

思路:首先優(yōu)化拖拽元素對象如下,增加一個目標元素target座每,表示被拖拽對象嫡纠,在上圖的登錄框中除盏,就是整個登錄窗口者蠕。
被記錄和設(shè)置坐標的拖拽元素就是這個目標元素,但是它并不是整個部分都是拖拽的有效部分大磺。我們在html結(jié)構(gòu)中為拖拽的有效區(qū)域添加類draggable表示有效拖拽區(qū)域:

<div id="drag">
    <h3 class='drag-title'>拖拽主體</h3>
    <div class="drag-body">
        內(nèi)容主體
    </div>
</div>

js部分:

function Drag(ele,parent) {
    this.ele = ele;
    var drag = ele.getElementsByClassName('drag-title')[0] || ele
    function mouseDown(event) {
      var elem = this ;
      draggableConfig.mouse.setXY(event.clientX, event.clientY);
      draggableConfig.draggingObj = new DragElement(ele);
      draggableConfig.draggingObj
        .setXY(ele.style.left, ele.style.top)
        .setEleCss({
          "zIndex": draggableConfig.zIndex ++,
          "position": "absolute"
        });
    }
    draggableConfig.parent = parent;
    draggableConfig.dragEle = ele;
    ele.onselectstart = function() {
      // 防止拖拽對象內(nèi)文字被選中
      return false;
    }
    dom.on(drag, "mousedown", mouseDown);
  }

性能優(yōu)化##

由于onmousemove在一直調(diào)用杠愧,會造成一些性能問題锐锣,我們可以通過setTimout來延遲綁定onmousemove事件雕憔,改進move函數(shù)如下:

 setTimeout(function () {
    dom.on(document, "mousemove", function(e){
    
    });
 }, 25);

接下來的拖拽(二)將會去學習下H5中拖拽分瘦,敬請下回分解哈,O(∩_∩)O哈哈~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市拜马,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌扮超,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異坷檩,居然都是意外死亡,警方通過查閱死者的電腦和手機裸删,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來匕荸,“玉大人榛搔,你說我怎么就攤上這事腹泌。” “怎么了专甩?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵钉稍,是天一觀的道長种樱。 經(jīng)常有香客問我,道長俐镐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任茵汰,我火速辦了婚禮枢里,結(jié)果婚禮上蹂午,老公的妹妹穿的比我還像新娘奥洼。我一直安慰自己,他們只是感情好瓷患,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著辨图,像睡著了一般吆豹。 火紅的嫁衣襯著肌膚如雪痘煤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天衷快,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛缔恳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播铃芦,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼刃滓,長吁一口氣:“原來是場噩夢啊……” “哼卓缰!你這毒婦竟也來了茁彭?” 一聲冷哼從身側(cè)響起摄闸,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤树姨,失蹤者是張志新(化名)和其女友劉穎典格,沒想到半個月后防嗡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蚁趁,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡徘熔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年台颠,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漆腌。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡统舀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤跃惫,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布闲勺,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏宪塔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一蜜托、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嗓化,春花似錦刺覆、人聲如沸氢橙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽疲牵。三九已至妆够,卻和暖如春识啦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背神妹。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工袁滥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人灾螃。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓题翻,卻偏偏與公主長得像,于是被迫代替她去往敵國和親腰鬼。 傳聞我的和親對象是個殘疾皇子嵌赠,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容

  • HTML標簽解釋大全 一、HTML標記 標簽:!DOCTYPE 說明:指定了 HTML 文檔遵循的文檔類型定義(D...
    米塔塔閱讀 3,225評論 1 41
  • 轉(zhuǎn)載請聲明 原文鏈接地址 關(guān)注公眾號獲取更多資訊 第一部分 HTML 第一章 職業(yè)規(guī)劃和前景 職業(yè)方向規(guī)劃定位...
    程序員poetry閱讀 16,518評論 32 459
  • 一熄赡、js拖拽插件的原理常見的拖拽操作是什么樣的呢姜挺?整過過程大概有下面幾個步驟: 1、用鼠標點擊被拖拽的元素 2彼硫、按...
    李永州的FE閱讀 706評論 0 0
  • 系列文章自定義表單(一)--拖拽(JS版本)自定義表單(二)--拖拽(HTML版本)自定義表單(完)--(html...
    會打代碼的掃地王大爺閱讀 5,684評論 2 7
  • 前面 還是之前線下讀書會交換過來的書炊豪。關(guān)于習慣,日夜身上固有的特性拧篮,我們又了解一下什么呢词渤?我們能否回憶起自己是怎么...
    青衫磊落_H閱讀 782評論 0 0