自定義類似于jQuery UI Selectable 的Vue指令v-selectable
話不多說,先看效果。

其實(shí)就是一個(gè)可以按住鼠標(biāo)進(jìn)行一個(gè)區(qū)域內(nèi)條目選擇的功能,相信用過Jquery UI 的都知道這是selectable的功能,然而我們?nèi)绻肰ue開發(fā)的話沒有類似的插件,當(dāng)然你仍然可以把jquery的拿過來直接用,但是我又不想引入jquery 和 jquery UI在我的項(xiàng)目中,于是我就自己嘗試著實(shí)現(xiàn)類似的功能。
要實(shí)現(xiàn)這個(gè)功能分兩步。第一步是實(shí)現(xiàn)鼠標(biāo)選擇區(qū)域的功能,第步部是把這個(gè)區(qū)域內(nèi)被選擇的item添加一個(gè)active的類。
先看如何實(shí)現(xiàn)按住鼠標(biāo)畫虛線框,思路是先把容器元素的定位改為relative 然后判斷當(dāng)鼠標(biāo)按下(mousedown)的時(shí)候,進(jìn)行記住這個(gè)點(diǎn)擊點(diǎn)的位置(e.layerX , e.layerY),然后鼠標(biāo)移動(dòng)(mousemove)的時(shí)候,實(shí)時(shí)的監(jiān)測(cè)鼠標(biāo)的位置(e.layerX , e.layerY),有了這兩個(gè)位置就可以動(dòng)態(tài)的創(chuàng)建一個(gè)div,它的定位為absolute,然后把它添加的容器框里,并且每次清空前一個(gè)框就可以了。為什么是用e.layerX e.layerY呢,
layerX layerY
如果元素的position樣式不是默認(rèn)的static,我們說這個(gè)元素具有定位屬性。
在當(dāng)前觸發(fā)鼠標(biāo)事件的元素和它的祖先元素中找到最近的具有定位屬性的元素,計(jì)算鼠標(biāo)與其的偏移值,以找到元素的border的左上角的外交點(diǎn)作為相對(duì)點(diǎn)。如果找不到具有定位屬性的元素,那么就相對(duì)于當(dāng)前頁面計(jì)算偏移,此時(shí)等同于pageY。按照這個(gè)思路完成以下代碼:
export default (Vue, options = {}) =>{
const listener = (ele, binding) =>{
let reactArea = {
startX: 0,
startY: 0,
endX: 0,
endY: 0
}
//是否一直按下鼠標(biāo)
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
}
//判斷同時(shí)有寬高才開始畫虛線框
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
//每次鼠標(biāo)點(diǎn)擊完了areaSelect
if(areaSelect && areaSelect.childNodes && ele.contains(areaSelect)){
ele.removeChild(areaSelect)
}
areaSelect = null
})
}
Vue.directive('selectable',{
inserted:listener,
updated:listener
})
}
這個(gè)時(shí)就可以實(shí)現(xiàn)畫虛線框的效果

下一步是如何把每個(gè)item置為選中狀態(tài)。思路是遍歷這個(gè)容器ul 的所有子元素li ,然后判斷每個(gè)li是否在選中的框內(nèi)部。然后看每個(gè)元素的offsetLeft 和 offsetTop 計(jì)算元素相對(duì)于父元素的位置,然后通過getBoundingClientRect().height 和 getBoundingClientRect().width 確定子元素的寬高。這些就可以計(jì)算出元素的位置和大小了,然后如何判斷這個(gè)元素是否在選擇區(qū)域內(nèi)呢?我的規(guī)則是這個(gè)元素的四個(gè)角位置有任何一個(gè)在選擇區(qū)域內(nèi)或者選擇區(qū)域就在這個(gè)區(qū)域的內(nèi)部,就算是這個(gè)元素被選中了(這個(gè)判斷方式感覺不是很完美)。按照這個(gè)思路,繼續(xù)完成我們的代碼:
export default (Vue, options = {}) =>{
const listener = (ele, binding) =>{
let reactArea = {
startX: 0,
startY: 0,
endX: 0,
endY: 0
}
//是否一直按下鼠標(biāo)
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
}
//判斷同時(shí)有寬高才開始畫虛線框
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
//每個(gè)li元素的位置
let offsetLeft = children[i].offsetLeft
let offsetTop = children[i].offsetTop
//每個(gè)li元素的寬高
let endPositionH = childrenHeight + offsetTop
let endPositionW = childrenWidth + offsetLeft
//五個(gè)條件滿足一個(gè)就可以判斷被選擇
//一是右下角在選擇區(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的這個(gè)v-selectable就是我們自定義的指令,但是使用之前必須 Vue.use
import Vue from 'vue' import Selectable from '@/components/vue-selectable/vue-selectable.js' //這個(gè)修改為你的js路徑 Vue.use(Selectable);
再給我們的ul li 加點(diǎn)樣式,注意我們的被選擇項(xiàng)會(huì)被添加一個(gè)active的class,通過這個(gè)來改變選中項(xiàng)樣式
<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>
這樣就可以達(dá)到開頭的效果了。實(shí)際上代碼運(yùn)行過程中還是有許多小bug的,本文只是提供了一個(gè)簡(jiǎn)單的思路和代碼,更多功能可以自己修改代碼進(jìn)行添加。如果不明白這個(gè)自定義指令為什么是這樣的寫法,可以參考我的另一篇文章自定義懶加載圖片插件v-lazyload。
http://www.fzitv.net/article/112355.htm
總結(jié)
以上所述是小編給大家介紹的自定義類似于jQuery UI Selectable 的Vue指令v-selectable,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
jQuery EasyUI API 中文文檔 - Parser 解析器
jQuery EasyUI API 中文文檔 - Parser 解析器,使用jQuery EasyUI的朋友可以參考下。2011-09-09
基于Jquery的標(biāo)簽智能驗(yàn)證實(shí)現(xiàn)代碼
一直在尋找最快捷方便的信息驗(yàn)證方法,之前自己編過JS版驗(yàn)證但要寫很多輔助代碼,經(jīng)過許多次改進(jìn),還是覺得太麻煩代碼還多,維護(hù)起太費(fèi)盡。2010-12-12
Jquery ajax請(qǐng)求導(dǎo)出Excel表格的實(shí)現(xiàn)代碼
下面小編就為大家?guī)硪黄狫query ajax請(qǐng)求導(dǎo)出Excel表格的實(shí)現(xiàn)代碼。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-06-06
jQuery 選擇表格(table)里的行和列及改變簡(jiǎn)單樣式
本文只是介紹如何用jQuery語句對(duì)表格中行和列進(jìn)行選擇以及一些簡(jiǎn)單樣式改變,希望它可以對(duì)jQuery表格處理的深層學(xué)習(xí)提供一些幫助2012-12-12
jquery封裝插件時(shí)匿名函數(shù)形參和實(shí)參的寫法解釋
本文主要介紹了jquery封裝插件時(shí)匿名函數(shù)形參和實(shí)參的寫法解釋。具有很好的參考價(jià)值,下面跟著小編一起來看下吧2017-02-02
jQuery中removeAttr()方法用法實(shí)例
這篇文章主要介紹了jQuery中removeAttr()方法用法,實(shí)例分析了removeAttr()方法的功能、定義及從匹配元素中移除相應(yīng)屬性的使用技巧,需要的朋友可以參考下2015-01-01

