自定義相似於Jquery UI Selectable 的Vue指令v-selectable

  話很少說,先看效果。javascript

  

  其實就是一個能夠按住鼠標進行一個區域內條目選擇的功能,相信用過Jquery UI 的都知道這是selectable的功能,然而咱們若是用Vue開發的話沒有相似的插件,固然你仍然能夠把jquery的拿過來直接用,可是我又不想引入jquery 和 jquery UI在個人項目中,因而我就本身嘗試着實現相似的功能。css

  要實現這個功能分兩步。第一步是實現鼠標選擇區域的功能,第步部是把這個區域內被選擇的item添加一個active的類。html

  先看如何實現按住鼠標畫虛線框,思路是先把容器元素的定位改成relative 而後判斷當鼠標按下(mousedown)的時候,進行記住這個點擊點的位置(e.layerX , e.layerY),而後鼠標移動(mousemove)的時候,實時的監測鼠標的位置(e.layerX , e.layerY),有了這兩個位置就能夠動態的建立一個div,它的定位爲absolute,而後把它添加的容器框裏,而且每次清空前一個框就能夠了。爲何是用e.layerX e.layerY呢,vue

layerX layerYjava

         若是元素的position樣式不是默認的static,咱們說這個元素具備定位屬性。react

         在當前觸發鼠標事件的元素和它的祖先元素中找到最近的具備定位屬性的元素,計算鼠標與其的偏移值,以找到元素的border的左上角的外交點做爲相對點。若是找不到具備定位屬性的元素,那麼就相對於當前頁面計算偏移,此時等同於pageY。按照這個思路完成如下代碼:jquery

  

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
    })
}

  這個時就能夠實現畫虛線框的效果app

 

  下一步是如何把每一個item置爲選中狀態。思路是遍歷這個容器ul 的全部子元素li ,而後判斷每一個li是否在選中的框內部。而後看每一個元素的offsetLeft 和 offsetTop 計算元素相對於父元素的位置,而後經過getBoundingClientRect().height 和 getBoundingClientRect().width 肯定子元素的寬高。這些就能夠計算出元素的位置和大小了,而後如何判斷這個元素是否在選擇區域內呢?個人規則是這個元素的四個角位置有任何一個在選擇區域內或者選擇區域就在這個區域的內部,就算是這個元素被選中了(這個判斷方式感受不是很完美)。按照這個思路,繼續完成咱們的代碼:ui

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
                    //五個條件知足一個就能夠判斷被選擇
                    //一是右下角在選擇區域內
                    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 結構:spa

<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

  http://www.cnblogs.com/mdengcc/p/6773672.html.

  本文結束,歡迎你們在留言區指出不正確的地方,喜歡的話能夠點個推薦。

  

 

 

 

 

注:本文出自博客園 https://home.cnblogs.com/u/mdengcc/ ,轉載請註明出處。

相關文章
相關標籤/搜索