您好,登錄后才能下訂單哦!
這期內(nèi)容當中小編將會給大家?guī)碛嘘P(guān)如何自定義類似于jQueryUISelectable的Vue指令v-selectable,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
話不多說,先看效果。
其實就是一個可以按住鼠標進行一個區(qū)域內(nèi)條目選擇的功能,相信用過Jquery UI 的都知道這是selectable的功能,然而我們?nèi)绻肰ue開發(fā)的話沒有類似的插件,當然你仍然可以把jquery的拿過來直接用,但是我又不想引入jquery 和 jquery UI在我的項目中,于是我就自己嘗試著實現(xiàn)類似的功能。
要實現(xiàn)這個功能分兩步。第一步是實現(xiàn)鼠標選擇區(qū)域的功能,第步部是把這個區(qū)域內(nèi)被選擇的item添加一個active的類。
先看如何實現(xiàn)按住鼠標畫虛線框,思路是先把容器元素的定位改為relative 然后判斷當鼠標按下(mousedown)的時候,進行記住這個點擊點的位置(e.layerX , e.layerY),然后鼠標移動(mousemove)的時候,實時的監(jiān)測鼠標的位置(e.layerX , e.layerY),有了這兩個位置就可以動態(tài)的創(chuàng)建一個div,它的定位為absolute,然后把它添加的容器框里,并且每次清空前一個框就可以了。為什么是用e.layerX e.layerY呢,
layerX layerY
如果元素的position樣式不是默認的static,我們說這個元素具有定位屬性。
在當前觸發(fā)鼠標事件的元素和它的祖先元素中找到最近的具有定位屬性的元素,計算鼠標與其的偏移值,以找到元素的border的左上角的外交點作為相對點。如果找不到具有定位屬性的元素,那么就相對于當前頁面計算偏移,此時等同于pageY。按照這個思路完成以下代碼:
export default (Vue, options = {}) =>{ const listener = (ele, binding) =>{ let reactArea = { startX: 0, startY: 0, endX: 0, endY: 0 } //是否一直按下鼠標 let isMouseDown = false let areaSelect = {} //將元素定位改為relative ele.style.position = 'relative' ele.addEventListener('mousedown', function(e) { reactArea.startX = e.layerX; reactArea.startY = e.layerY; isMouseDown = true }) ele.addEventListener('mousemove', function(e) { if(isMouseDown){ let preArea = ele.getElementsByClassName('v-selected-area') if(preArea.length){ ele.removeChild(preArea[0]) } reactArea.endX = e.layerX reactArea.endY = e.layerY let leftValue = 0 let topValue = 0 let widthValue = Math.abs(reactArea.startX - reactArea.endX) let heightValue = Math.abs(reactArea.startY - reactArea.endY) if(reactArea.startX >= reactArea.endX){ leftValue = reactArea.endX }else{ leftValue = reactArea.startX } if(reactArea.startY > reactArea.endY ){ topValue = reactArea.endY }else{ topValue = reactArea.startY } //判斷同時有寬高才開始畫虛線框 if(reactArea.startX != reactArea.endX && reactArea.startY !=reactArea.endY){ areaSelect = document.createElement('div') areaSelect.classList.add("v-selected-area") areaSelect.style.position = "absolute"; areaSelect.style.left = leftValue + 'px' areaSelect.style.top = topValue + 'px' areaSelect.style.width = widthValue + 'px' areaSelect.style.height = heightValue + 'px' areaSelect.style.border = "1px dashed grey" ele.append(areaSelect) } } }) ele.addEventListener('mouseup', function(e) { isMouseDown = false //每次鼠標點擊完了areaSelect if(areaSelect && areaSelect.childNodes && ele.contains(areaSelect)){ ele.removeChild(areaSelect) } areaSelect = null }) } Vue.directive('selectable',{ inserted:listener, updated:listener }) }
這個時就可以實現(xiàn)畫虛線框的效果
下一步是如何把每個item置為選中狀態(tài)。思路是遍歷這個容器ul 的所有子元素li ,然后判斷每個li是否在選中的框內(nèi)部。然后看每個元素的offsetLeft 和 offsetTop 計算元素相對于父元素的位置,然后通過getBoundingClientRect().height 和 getBoundingClientRect().width 確定子元素的寬高。這些就可以計算出元素的位置和大小了,然后如何判斷這個元素是否在選擇區(qū)域內(nèi)呢?我的規(guī)則是這個元素的四個角位置有任何一個在選擇區(qū)域內(nèi)或者選擇區(qū)域就在這個區(qū)域的內(nèi)部,就算是這個元素被選中了(這個判斷方式感覺不是很完美)。按照這個思路,繼續(xù)完成我們的代碼:
export default (Vue, options = {}) =>{ const listener = (ele, binding) =>{ let reactArea = { startX: 0, startY: 0, endX: 0, endY: 0 } //是否一直按下鼠標 let isMouseDown = false let areaSelect = {} //將元素定位改為relative ele.style.position = 'relative' ele.addEventListener('mousedown', function(e) { reactArea.startX = e.layerX; reactArea.startY = e.layerY; isMouseDown = true }) ele.addEventListener('mousemove', function(e) { if(isMouseDown){ let preArea = ele.getElementsByClassName('v-selected-area') if(preArea.length){ ele.removeChild(preArea[0]) } reactArea.endX = e.layerX reactArea.endY = e.layerY let leftValue = 0 let topValue = 0 let widthValue = Math.abs(reactArea.startX - reactArea.endX) let heightValue = Math.abs(reactArea.startY - reactArea.endY) if(reactArea.startX >= reactArea.endX){ leftValue = reactArea.endX }else{ leftValue = reactArea.startX } if(reactArea.startY > reactArea.endY ){ topValue = reactArea.endY }else{ topValue = reactArea.startY } //判斷同時有寬高才開始畫虛線框 if(reactArea.startX != reactArea.endX && reactArea.startY !=reactArea.endY){ areaSelect = document.createElement('div') areaSelect.classList.add("v-selected-area") areaSelect.style.position = "absolute"; areaSelect.style.left = leftValue + 'px' areaSelect.style.top = topValue + 'px' areaSelect.style.width = widthValue + 'px' areaSelect.style.height = heightValue + 'px' areaSelect.style.border = "1px dashed grey" ele.append(areaSelect) } let children = ele.getElementsByTagName('li') for(let i =0 ; i < children.length ; i ++ ){ let childrenHeight = children[i].getBoundingClientRect().height let childrenWidth = children[i].getBoundingClientRect().width //每個li元素的位置 let offsetLeft = children[i].offsetLeft let offsetTop = children[i].offsetTop //每個li元素的寬高 let endPositionH = childrenHeight + offsetTop let endPositionW = childrenWidth + offsetLeft //五個條件滿足一個就可以判斷被選擇 //一是右下角在選擇區(qū)域內(nèi) let require1 = endPositionH > topValue && endPositionW > leftValue && endPositionH < topValue + heightValue && endPositionW < leftValue + widthValue //二是左上角在選擇區(qū)域內(nèi) let require2 = offsetTop > topValue && offsetLeft > leftValue && offsetTop < topValue + heightValue && offsetLeft < leftValue + widthValue //三是右上角在選擇區(qū)域內(nèi) let require3 = offsetTop > topValue && offsetLeft + childrenWidth > leftValue && offsetTop < topValue + heightValue && offsetLeft + childrenWidth< leftValue + widthValue //四是左下角在選擇區(qū)域內(nèi) let require4 = offsetTop + childrenHeight > topValue && offsetLeft > leftValue && offsetTop + childrenHeight < topValue + heightValue && offsetLeft < leftValue + widthValue //五選擇區(qū)域在元素體內(nèi) let require5 = offsetTop < topValue && offsetLeft < leftValue && offsetTop + childrenHeight > topValue + heightValue && offsetLeft + childrenWidth > leftValue + widthValue if(require1 || require2 || require3 || require4 || require5){ children[i].classList.add('active') }else{ children[i].classList.remove('active') } } } }) ele.addEventListener('mouseup', function(e) { isMouseDown = false if(areaSelect && areaSelect.childNodes && ele.contains(areaSelect)){ ele.removeChild(areaSelect) } areaSelect = null }) } Vue.directive('selectable',{ inserted:listener, updated:listener }) }
完成之后再看看如何使用,html 結(jié)構(gòu):
<ul v-selectable > <li class="square"> item1 </li> <li class="oval"> item2 </li> <li class="triangle"> item3 </li> <li class="triangle-topleft"> item4 </li> <li class="curvedarrow"> item5 </li> <li class="triangle-topleft"> item6 </li> </ul>
注意ul的這個v-selectable就是我們自定義的指令,但是使用之前必須 Vue.use
import Vue from 'vue' import Selectable from '@/components/vue-selectable/vue-selectable.js' //這個修改為你的js路徑 Vue.use(Selectable);
再給我們的ul li 加點樣式,注意我們的被選擇項會被添加一個active的class,通過這個來改變選中項樣式
<style scoped> ul{ margin: 40px 40px 40px 40px; border: 1px solid red; width: 300px; padding-bottom: 20px; } ul li { width: 200px; height: 30px; list-style: none; border: 1px solid black; margin-left: 10px; margin-top: 30px; text-align: center; line-height: 30px; user-select:none; } ul li.active{ background-color: red; } </style>
這樣就可以達到開頭的效果了。實際上代碼運行過程中還是有許多小bug的,本文只是提供了一個簡單的思路和代碼,更多功能可以自己修改代碼進行添加。如果不明白這個自定義指令為什么是這樣的寫法,可以參考我的另一篇文章自定義懶加載圖片插件v-lazyload。
上述就是小編為大家分享的如何自定義類似于jQueryUISelectable的Vue指令v-selectable了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。