前言


這二天在整理一個系統前端時, 發現需要一個可拉動位置的彈出視窗. 最初一直卡在拉動時移動量非常大. 網路上貼的例子雖然都可以正常動作, 可是都沒有明確的說明重點, 套到自己的程式時就是不正常. 最後經過大半天的測試修正, 總算找到問題點, 搞定! 故特記下一點心得.

HTML 文件


這是前端系統的 HTML 框架. 只寫到 myDiv 這一層, 前端 JS 程式執行了之後 myDiv 裡會再填入一個輸入用的表格 (form). 完成之後, 我希望可拉動的部份就是整個輸入表格 (含 myDiv).

<div id='pop' class="msgDiv ui-overlay">
  <div id='myDiv'></div>
</div>

填入輸入用的表格後的 DOM 結構如下:

<div id='pop' class="msgDiv ui-overlay">
  <div id='myDiv'>
    <div class='action'>
      <div class='Header'>...</div>
      <div class='Form_Content'>...</div>
      <div class='Footer'>...</div>
    </div>
  </div>
</div>

程式碼


這段程式的基礎來自 W3School, 我稍微做了一些改變:

  • 需要輸入二個參數: 一是可以被拉動的部份, 一是控制可拉動的部份.
    可被拉動的部份一般是一個 div tag;
    而控制的部份則通常是這個容器的表頭部份.
  • 輸入的參數可以是字串 (用於 querySelector()), 或者是 (querySelector()getElementById() 等等...) 已經找到的元素.
  • 再來就是變數名稱改了一下而已.

程式的架構也很簡單:

  1. 先在控制移動的元件上附加上按下滑鼠右鍵事件 (mousedown) 的事件處理函數 (_evMsDown).
  2. 當按下滑鼠右鍵時則改為: 記下滑鼠目前位置 (稍後計算移動量用的), 並附加上
    放開滑鼠右鍵事件 (mouseup) 的事件處理函數 (_evMsUp)
    以及拉動滑鼠事件 (mousedrag) 的事件處理函數 (_evMsDrag).
  3. 拉動滑鼠時要處理的就是計算滑鼠的移動量, 並經由修改 CSS 更新整個被拉動部份的位置.
  4. 放開滑鼠右鍵時, 則把第二項附加上去的二個事件處理函數拿掉.

不過會發生問題的點並不是 JS 程式, 而是 CSS.

function dragElement(emCntr, emHdr) {
    var dltaX = 0, dltaY = 0, oPosX = 0, oPosY = 0;
    if (typeof emCntr === 'string')
        emCntr = document.querySelector(emCntr);
    if (typeof emHdr === 'string')
        emHdr = document.querySelector(emHdr);
    emHdr.addEventListener("mousedown", _evMsDown);

    function _evMsDown(ev) {
        ev = ev || window.event;
        ev.preventDefault();
        // 保存滑鼠位置
        oPosX = ev.clientX;
        oPosY = ev.clientY;
        // 附加事件監聽器
        document.addEventListener("mouseup", _evMsUp);
        document.addEventListener("mousemove", _evMsDrag);
    }

    function _evMsDrag(ev) {
        ev = ev || window.event;
        ev.preventDefault();
        // 計算移動量
        dltaX = oPosX - ev.clientX;
        dltaY = oPosY - ev.clientY;
        // 保存滑鼠位置
        oPosX = ev.clientX;
        oPosY = ev.clientY;
        // 更新被拉動元件的位置
        emCntr.style.top  = (emCntr.offsetTop  - dltaY) + "px";
        emCntr.style.left = (emCntr.offsetLeft - dltaX) + "px";
    }

    function _evMsUp() {
        // 移除事件監聽器
        document.removeEventListener("mouseup", _evMsUp, false);
        document.removeEventListener("mousemove", _evMsDrag, false);
    }
}

CSS


前面的 JS 要正常有以下幾個重點:

  1. 被拉動的元件: (第 3 行) 可以有 padding, 但是 margin 必需為 0 (尤其是 margin-topmargin-left). 拉動時是否平順正確就靠它了.
  2. 預設位置: (第 4,5 行) 用於將被拉動的元件的初始位置居中.
  3. 拉動的游標: (第 7 行) 一般我們只要加上 cursor 設定就好, 不過這裡要加上 z-index 把它拉到最上層, 移動的游標才會出現. (因為 #myDiv 是包含在 #pop 內, 並設定 z-index:10 把它向上拉. 因此 .header 必需比它再高一層才會顯現出 cursor:move 的效果.)
  4. 由於拉動的 JS 程式透過是更動 top, left 來改變可被拉動元件的位置 (JS 程式第 30,31 行), 因此它的 position 不可以是不能用 top, left 改變位置的 static (CSS .msgDiv > div 的最後一行).
#pop { position: absolute; }
#myDiv{
    width:50%; margin:0; padding:5px; z-index: 10;
    left:50%; top: 50%;
    transform: translate(-50%, -60%);
}
#myDiv .Header { border-radius:5px 5px 0 0; cursor: move; z-index: 11; }
#myDiv .Footer { border-radius:0 0 5px 5px }
#myDiv .Form_Content { line-height: 2.5em; }
.ui-overlay {
    position: fixed;
    inset: 0px;
    background: rgba(64, 64, 64, 0.7);
    transition: opacity 500ms ease 0s;
    overflow-y: auto;
    margin: 0px !important;
}
.msgDiv {
    width: 100%;
    overflow: hidden;
}
.msgDiv > div {
    background-color: white;
    border: 5px solid rgb(179, 213, 255);
    border-radius: 10px;
    margin: 2em 1em;
    padding: 1.5em 1em 0.2em;
    position: relative;
}

arrow
arrow

    MagicJackTing 發表在 痞客邦 留言(0) 人氣()