本文主要介紹了自訂類似jQuery UI Selectable 的Vue指令v-selectable的相關資料,需要的朋友可以參考下,希望能幫到大家。
話不多說,先看效果。
其實就是一個可以按住滑鼠進行一個區域內條目選擇的功能,相信用過Jquery UI 的都知道這是selectable的功能,然而我們如果用Vue開發的話沒有類似的插件,當然你還是可以把jquery的拿過來直接用,但是我又不想引入jquery 和jquery UI在我的專案中,於是我就自己嘗試著實現類似的功能。
要實現這個功能分兩步驟。第一步是實現滑鼠選擇區域的功能,第步部是把這個區域內被選擇的item添加一個active的類別。
先看如何實現按住滑鼠畫虛線框,思路是先把容器元素的定位改為relative 然後判斷當滑鼠按下(mousedown)的時候,進行記住這個點的位置(e .layerX , e.layerY),然後滑鼠移動(mousemove)的時候,即時的監測滑鼠的位置(e.layerX , e.layerY),有了這兩個位置就可以動態的創建一個p,它的定位為absolute,然後把它加的容器框裡,並且每次清空前一個框就可以了。為什麼要用e.layerX e.layerY呢,
layerX layerY
如果元素的position樣式不是預設的static,我們說這個元素具有定位屬性。
在目前觸發滑鼠事件的元素和它的祖先元素中找到最近的具有定位屬性的元素,計算滑鼠與其的偏移值,以找到元素的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('p') 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 }) }
這個時就可以實現畫虛線框的效果
下一步是如何把每個item置為選中狀態。想法是遍歷這個容器ul 的所有子元素li ,然後判斷每個li是否在選取的框內部。然後看每個元素的offsetLeft 和 offsetTop 計算元素相對於父元素的位置,然後透過getBoundingClientRect().height 和 getBoundingClientRect().width 決定子元素的寬高。這些就可以計算出元素的位置和大小了,然後如何判斷這個元素是否在選擇區域內?我的規則是這個元素的四個角落位置有任何一個在選擇區域內或選擇區域就在這個區域的內部,就算是這個元素被選中了(這個判斷方式感覺不是很完美)。依照這個思路,繼續完成我們的程式碼:
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('p') 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 //五个条件满足一个就可以判断被选择 //一是右下角在选择区域内 let require1 = endPositionH > topValue && endPositionW > leftValue && endPositionH < topValue + heightValue && endPositionW < leftValue + widthValue //二是左上角在选择区域内 let require2 = offsetTop > topValue && offsetLeft > leftValue && offsetTop < topValue + heightValue && offsetLeft < leftValue + widthValue //三是右上角在选择区域内 let require3 = offsetTop > topValue && offsetLeft + childrenWidth > leftValue && offsetTop < topValue + heightValue && offsetLeft + childrenWidth< leftValue + widthValue //四是左下角在选择区域内 let require4 = offsetTop + childrenHeight > topValue && offsetLeft > leftValue && offsetTop + childrenHeight < topValue + heightValue && offsetLeft < leftValue + widthValue //五选择区域在元素体内 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 結構:
<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。
相關推薦:
Yii Framework 開發教程Zii元件-Selectable範例_PHP教學
Yii Framework 開發教學課程Zii元件-Selectable範例
#以上是自訂類似jQuery UI Selectable 的Vue指令v-selectable詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!