Vue自定義指令實現(xiàn)彈窗拖拽四邊拉伸及對角線拉伸效果
引言
近期公司vue前端項目需求:實現(xiàn)彈窗的拖拽,四邊拉伸及對角線拉伸,以及彈窗邊界處理。本人使用vue的自定義指令編寫了drag.js文件分享給大家一起學(xué)習(xí),以下代碼是本人提取出來的示意demo,僅供參考。這是本人前端小白的第一篇技術(shù)分享,如有錯誤的地方,請大家批評指正!
頁面布局
<template> <div class="parameter" v-dialogDrag > <div class="title">標(biāo)題 <div class="close"> <img src="../assets/close.png" alt="" > </div> </div> <div class="content">內(nèi)容區(qū)</div> </div> </template>
<style lang="less"> .parameter { height: 569px; width: 960px; position: absolute; left: 50%; top: 50%; margin-left: calc(-960px / 2); margin-top: calc(-569px / 2); z-index: 999; background: #fff; box-sizing: border-box; box-shadow: 0px 12px 32px 0px rgba(0, 0, 0, 0.08); border-radius: 2px; .title { display: flex; font-size: 16px; height: 48px; line-height: 48px; background: #f5f5f5; box-sizing: border-box; box-shadow: inset 0px -1px 0px rgba(0, 0, 0, 0.12); border-radius: 2px 2px 0px 0px; padding: 0 20px; z-index: 99; font-size: 16px; font-weight: 500; color: rgba(0, 0, 0, 0.85); .close { img { width: 10px; } margin-left: auto; // 右對齊 } } .content { display: flex; justify-content: center; align-items: center; height: calc(100% - 48px); box-sizing: border-box; background: #fff; overflow: auto; } } </style>
頁面布局實際效果如下:
drag.js文件
可以在main.js全局引入drag.js文件,也可以單獨在彈窗組件內(nèi)部組件引入,看是否還有其他使用場景。
項目目錄截圖
main.js全局引入drag.js
import Vue from 'vue' import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; import App from './App.vue' import '../drag.js' Vue.config.productionTip = false Vue.use(ElementUI); new Vue({ render: h => h(App), }).$mount('#app')
彈窗拖拽實現(xiàn)及邊界限制
import Vue from 'vue' // v-dialogDrag: 彈窗拖拽+水平方向伸縮+對角線拉伸 Vue.directive('dialogDrag', { bind(el) { // dialogHeaderEl為標(biāo)題欄,綁定mousedown事件進行拖拽 const dialogHeaderEl = el.querySelector('.title') // dragDom為指令綁定dom元素,定義變量便于區(qū)分 const dragDom = el // 獲取css所有屬性兼容性寫法 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null); const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null) // 定義鼠標(biāo)按下事件 const moveDown = e => { // e.clientX,Y:鼠標(biāo)相對于瀏覽器可視窗口的X,Y坐標(biāo) // offsetTop,offsetLeft:當(dāng)前元素相對于其offsetParent元素的頂部,左邊的距離,這里title無定位偏移,故為0 const disX = e.clientX - dialogHeaderEl.offsetLeft // 元素相對位置 const disY = e.clientY - dialogHeaderEl.offsetTop // 元素相對位置 const screenWidth = document.documentElement.clientWidth || document.body.clientWidth // 頁面可視區(qū)寬度,兼容寫法 const screenHeight = document.documentElement.clientHeight || document.body.clientHeight // 頁面可視區(qū)高度,兼容寫法 const dragDomWidth = dragDom.offsetWidth // 對話框?qū)挾? const dragDomheight = dragDom.offsetHeight // 對話框高度 const minDragDomLeft = dragDom.offsetLeft // 對話框邊界最小left值 const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth // 對話框邊界最大left值 const minDragDomTop = dragDom.offsetTop // 對話框邊界最小Top值 const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight // 對話框邊界最大Top值 // 獲取到的值帶px 正則匹配替換 let styL = sty.left // 為兼容ie if (styL === 'auto') styL = '0px' let styT = sty.top // 注意在ie中 第一次獲取到的值為組件自帶50% 移動之后賦值為px if (sty.left.includes('%')) { styL = +document.body.clientWidth * (+styL.replace(/%/g, '') / 100) styT = +document.body.clientHeight * (+styT.replace(/%/g, '') / 100) } else { styL = +styL.replace(/\px/g, '') styT = +styT.replace(/\px/g, '') } document.onmousemove = function (e) { // 通過事件委托,計算移動的距離 let left = e.clientX - disX let top = e.clientY - disY // 邊界處理 if (-(left) > minDragDomLeft) { left = -(minDragDomLeft) } else if (left > maxDragDomLeft) { left = maxDragDomLeft } if (-(top) > minDragDomTop) { top = -(minDragDomTop) } else if (top > maxDragDomTop) { top = maxDragDomTop } // 移動當(dāng)前元素 dragDom.style.left = `${left + styL}px` dragDom.style.top = `${top + styT}px` // 鼠標(biāo)抬起停止彈窗移動 document.onmouseup = function () { document.onmousemove = null document.onmouseup = null } } dialogHeaderEl.onmousedown = moveDown } })
鼠標(biāo)指針懸停樣式
彈窗并沒有設(shè)置cursor:move懸停樣式,因為參考的是瀏覽器拖拽實際效果,如果想設(shè)置move,需要增加邊界判斷條件。
判斷鼠標(biāo)懸浮指針類型中x > left + width - 5
,其中5為自己設(shè)置的可拉伸區(qū)域,因為需求中彈窗不可設(shè)置邊框和padding,所以無實際可拖拽元素,故手動設(shè)置5px(可根據(jù)需求自行更改)。
// 定義鼠標(biāo)懸停樣式 const CURSORTYPE = { top: 'n-resize', bottom: 's-resize', left: 'w-resize', right: 'e-resize', // right_top寫法是便于后面代碼數(shù)據(jù)處理 right_top: 'ne-resize', left_top: 'nw-resize', left_bottom: 'sw-resize', right_bottom: 'se-resize', default: 'default', }; // 判斷鼠標(biāo)懸浮指針類型 const checkType = obj => { const { x, y, left, top, width, height } = obj let type if (x > left + width - 5 && el.scrollTop + y <= top + height - 5 && top + 5 <= y) { type = 'right' } else if (left + 5 > x && el.scrollTop + y <= top + height - 5 && top + 5 <= y) { type = 'left' } else if (el.scrollTop + y > top + height - 5 && x <= left + width - 5 && left + 5 <= x) { type = 'bottom' } else if (top + 5 > y && x <= left + width - 5 && left + 5 <= x) { type = 'top' } else if (x > left + width - 5 && el.scrollTop + y > top + height - 5) { type = 'right_bottom' } else if (left + 5 > x && el.scrollTop + y > top + height - 5) { type = 'left_bottom' } else if (top + 5 > y && x > left + width - 5) { type = 'right_top' } else if (top + 5 > y && left + 5 > x) { type = 'left_top' } return type || 'default' }
四邊拉伸及對角線拉伸
在做對角線拉伸過程中思路出現(xiàn)一點偏差,我發(fā)現(xiàn)瀏覽器窗口對角線拉伸可以X軸方向拉伸,Y方向拉伸,斜邊拉伸,故分三種情況判斷,可是這樣做出來實際彈窗效果只能拉伸一點點,不滿足拉伸需求。經(jīng)過思考后發(fā)現(xiàn),實際對角線拉伸為X,Y軸疊加和,參考矢量疊加。
因為對角線拉伸為X軸和Y軸的疊加,故考慮將四邊拉伸封裝函數(shù),對角線拉伸直接調(diào)用相應(yīng)的X,Y軸,減少代碼量。傳遞數(shù)據(jù)的時候因為對角線拉伸需要傳遞兩個值,而四邊拉伸只需要傳遞一個值,所以需要對數(shù)據(jù)進行包裝。例如:右側(cè)拉伸傳遞數(shù)據(jù)['right', null]
,而右下角傳遞數(shù)據(jù)['right', 'bottom']
// 判斷邊界條件 const boundaryLimit = obj => { const { left, top, width, height, diffX, diffY, screenHeight, screenWidth, arr } = obj if (arr[0] == 'right' || arr[1] == 'right') { if (width + diffX > screenWidth - left) { dragDom.style.width = screenWidth - left + 'px' } else { dragDom.style.width = width + diffX + 'px' } } if (arr[0] == 'left' || arr[1] == 'left') { if (width - diffX > width + left) { dragDom.style.width = width + left + 'px' dragDom.style.left = - parseInt(sty.marginLeft) + 'px' } else { dragDom.style.width = width - diffX + 'px' // left實際 = left + marginLeft 計算時需要將marginLeft減掉 dragDom.style.left = left + diffX - parseInt(sty.marginLeft) + 'px' } } if (arr[0] == 'top' || arr[1] == 'top') { if (height - diffY > height + top) { dragDom.style.height = height + top + 'px' dragDom.style.top = - parseInt(sty.marginTop) + 'px' } else { dragDom.style.height = height - diffY + 'px' // top實際 = top + marginTop 計算時需要將marginTop減掉 dragDom.style.top = top + diffY - parseInt(sty.marginTop) + 'px' } } if (arr[0] == 'bottom' || arr[1] == 'bottom') { if (height + diffY > screenHeight - top) { dragDom.style.height = screenHeight - top } else { dragDom.style.height = height + diffY + 'px' } } } dragDom.onmousedown = e => { const x = e.clientX const y = e.clientY const width = dragDom.clientWidth const height = dragDom.clientHeight const left = dragDom.offsetLeft const top = dragDom.offsetTop const screenWidth = document.documentElement.clientWidth || document.body.clientWidth const screenHeight = document.documentElement.clientHeight || document.body.clientHeight // dragDom.style.userSelect = 'none' let type = checkType({ x, y, left, top, width, height }) // 判斷是否為彈窗頭部 if (x > left && x < left + width && y > top + 5 && y < top + dialogHeaderEl.clientHeight) { // dialogHeaderEl.onmousedown = moveDown } else { document.onmousemove = function (e) { // 移動時禁用默認(rèn)事件 e.preventDefault() let endX = e.clientX let endY = e.clientY let diffX = endX - x let diffY = endY - y let arr // 將type轉(zhuǎn)換為數(shù)組格式,簡化代碼判斷調(diào)用即可 if (type.indexOf('_') == -1) { arr = [type, ''] } else { arr = type.split('_') } boundaryLimit({ left, top, width, height, diffX, diffY, screenHeight, screenWidth, arr }) } // 拉伸結(jié)束 document.onmouseup = function () { document.onmousemove = null document.onmouseup = null } } }
拉伸干涉
因為彈窗設(shè)置了overflow: auto
,故拉伸過程勢必會產(chǎn)生右側(cè),底部滾動條,在實際拉伸的時候滾動條會和拉伸區(qū)域干涉。解決方案為:在彈窗右側(cè)和底部外部增加空div條,實際拉伸區(qū)域為空div即可解決。(空div條寬高為5px,與之前設(shè)置的拉伸區(qū)域一致)
<template> <div class="parameter" v-dialogDrag > <div class="title">標(biāo)題 <div class="close"> <img src="../assets/close.png" alt="" > </div> </div> <div class="content">內(nèi)容區(qū)</div> <div class="rightBlank">123</div> <div class="bottomBlank">456</div> </div> </template>
更改后頁面效果為
到此這篇關(guān)于Vue自定義指令實現(xiàn)彈窗拖拽,四邊拉伸及對角線拉伸的文章就介紹到這了,更多相關(guān)vue自定義指令彈窗拖拽內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
從Vue轉(zhuǎn)換看Webpack與Vite 代碼轉(zhuǎn)換機制差異詳解
這篇文章主要為大家介紹了從Vue轉(zhuǎn)換看Webpack與Vite代碼轉(zhuǎn)換機制差異詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-10-10解決vue項目運行出現(xiàn)warnings?potentially?fixable?with?the?`--fix
這篇文章主要介紹了解決vue項目運行出現(xiàn)warnings?potentially?fixable?with?the?`--fix`?option的報錯問題,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2021-11-11Vue使用sign-canvas實現(xiàn)在線手寫簽名的實例
sign-canvas?一個基于?canvas?開發(fā),封裝于?Vue?組件的通用手寫簽名板(電子簽名板),支持?pc?端和移動端,本文給大家分享Vue使用sign-canvas實現(xiàn)在線手寫簽名,感興趣的朋友一起看看吧2022-05-05解決vue-cli項目sourcemap因為文件重名導(dǎo)致的文件定位映射錯誤問題
這篇文章主要介紹了解決vue-cli項目sourcemap因為文件重名導(dǎo)致的文件定位映射錯誤問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06vue中利用mqtt服務(wù)端實現(xiàn)即時通訊的步驟記錄
前些日子了解到mqtt這樣一個協(xié)議,可以在web上達到即時通訊的效果,所以下面這篇文章主要給大家介紹了關(guān)于vue中如何利用mqtt服務(wù)端實現(xiàn)即時通訊的相關(guān)資料,需要的朋友可以參考下2021-07-07如何用vue3+Element?plus實現(xiàn)一個完整登錄功能
要實現(xiàn)用戶的登錄功能,可以使用Vue3和Element?Plus,下面這篇文章主要給大家介紹了關(guān)于如何基于Vue3和Element?Plus組件庫實現(xiàn)一個完整的登錄功能,文中提供了詳細(xì)的代碼示例,需要的朋友可以參考下2023-10-10vue-admin-template?動態(tài)路由的實現(xiàn)示例
本文主要介紹了ue-admin-template動態(tài)路由的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12解決創(chuàng)建vue項目后沒有vue.config.js文件的問題
這篇文章給大家主要介紹如何解決創(chuàng)建vue項目后沒有webpack.config.js(vue.config.js)文件,文中有詳細(xì)的解決方法,感興趣的朋友可以參考閱讀下2023-07-07