话很少说,先看效果。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/ ,转载请注明出处。