前言
這二天在整理一個系統前端時, 發現需要一個可拉動位置的彈出視窗. 最初一直卡在拉動時移動量非常大. 網路上貼的例子雖然都可以正常動作, 可是都沒有明確的說明重點, 套到自己的程式時就是不正常. 最後經過大半天的測試修正, 總算找到問題點, 搞定! 故特記下一點心得.
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() 等等...) 已經找到的元素.
- 再來就是變數名稱改了一下而已.
程式的架構也很簡單:
- 先在控制移動的元件上附加上按下滑鼠右鍵事件 (mousedown) 的事件處理函數 (_evMsDown).
- 當按下滑鼠右鍵時則改為: 記下滑鼠目前位置 (稍後計算移動量用的), 並附加上
放開滑鼠右鍵事件 (mouseup) 的事件處理函數 (_evMsUp)
以及拉動滑鼠事件 (mousedrag) 的事件處理函數 (_evMsDrag). - 拉動滑鼠時要處理的就是計算滑鼠的移動量, 並經由修改 CSS 更新整個被拉動部份的位置.
- 放開滑鼠右鍵時, 則把第二項附加上去的二個事件處理函數拿掉.
不過會發生問題的點並不是 JS 程式, 而是 CSS.
dragElement("#myDiv", "#myDiv .Header");
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 要正常有以下幾個重點:
- 被拉動的元件: (第 3 行) 可以有 padding, 但是 margin 必需為 0 (尤其是 margin-top 和 margin-left). 拉動時是否平順正確就靠它了.
- 預設位置: (第 4,5 行) 用於將被拉動的元件的初始位置居中.
- 拉動的游標: (第 7 行) 一般我們只要加上 cursor 設定就好, 不過這裡要加上 z-index 把它拉到最上層, 移動的游標才會出現. (因為 #myDiv 是包含在 #pop 內, 並設定 z-index:10 把它向上拉. 因此 .header 必需比它再高一層才會顯現出 cursor:move 的效果.)
- 由於拉動的 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; background-color:lightblue; }
#myDiv .Footer { border-radius:0 0 5px 5px; background-color:lightblue; }
#myDiv .Form_Content { line-height: 1.5em; padding: 0.8em 0; }
.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;
}
追加功能: 調整視窗大小
調整視窗大小的功能很簡單: 主要是二行 CSS:
resize:both;
overflow:hedden;
這裡 overflow 不可以設為 visible, 其他設定值 (auto, scroll, hidden) 應該都沒有問題. 而加了上述 resize 設定的 <div> tag, 其右下角就會出現一個調整大小的 icon. 不過應該把它附加在哪一個 div 呢? 有下面幾種選擇:
- 放在最裡層的 div.Form_Content: 這是最簡單的. 這種作法調整大小的 icon 會出現在 div.Form_Content 的右下角, 而不是整個可拉動的 div#myDiv. 不過受限於我原本寫的 CSS, 它無法調整寬度, 只能啟用垂直單向調整.
#myDiv .Form_Content { resize:vertical; overflow:hedden; }
- 同樣是放在最裡層的 div.Form_Content: 想要擺脫只能啟用垂直單向調整的限制, 則我們需要先把整個可拉動部份 div#myDiv 的寬度設為
width:fit-content
(即讓它依其下層內容自動調整大小), 再將 div.Form_Content 的寬度改為某一固定大小 (這裡不能用 % 因為上一層的寬度是width:fit-content
).#myDiv { width:fit-content; } #myDiv .Form_Content { width: 25em; resize:both; overflow:hedden; }
- 放在 div.action 裡: 這組設定幾乎和上一組一樣, 差異只是作用在不同的 <div> tag 上.
#myDiv { width:fit-content; } #myDiv .action { width: 25em; resize:both; overflow:hedden; }
#myDiv .action { display: flex; flex-flow: column; } #myDiv .Form_Content { flex-grow: 1; }
#myDiv { width:fit-content; } #myDiv .action { width: 25em; resize:both; overflow:hedden; display: flex; flex-flow: column; } #myDiv .Form_Content { flex-grow: 1; }
- 放在可移動部份的最外層的 div#myDiv: 有了上一項的改用 Flex, 要把 resize 設定套最外層 div#myDiv 也是很簡單的事情: div#myDiv 不用更動寬度, div.action 需要多設高度佔滿整個 div#myDiv.
#myDiv { resize:both; overflow:hedden; } #myDiv .action { height:100%; display: flex; flex-flow: column; } #myDiv .Form_Content { flex-grow: 1; }
匯整
最後, 我把上面拉動及調整大小的功能匯整放在 上, 大家可以連過去試一下. 或者, 直接在下面試試拉動及調整大小的效果.
See the Pen Something Dragable by Jack Ting (@magicjack) on CodePen.
文章標籤
全站熱搜
留言列表