话不多说,先看效果。

  

  其实就是一个可以按住鼠标进行一个区域内条目选择的功能,相信用过Jquery UI 的都知道这是selectable的功能,然而我们如果用Vue开发的话没有类似的插件,当然你仍然可以把jquery的拿过来直接用,但是我又不想引入jquery 和 jquery UI在我的项目中,于是我就自己尝试着实现类似的功能。

  要实现这个功能分两步。第一步是实现鼠标选择区域的功能,第步部是把这个区域内被选择的item添加一个active的类。

  先看如何实现按住鼠标画虚线框,思路是先把容器元素的定位改为relative 然后判断当鼠标按下(mousedown)的时候,进行记住这个点击点的位置(e.layerX , e.layerY),然后鼠标移动(mousemove)的时候,实时的监测鼠标的位置(e.layerX , e.layerY),有了这两个位置就可以动态的创建一个div,它的定位为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('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
})
}

  这个时就可以实现画虚线框的效果

  下一步是如何把每个item置为选中状态。思路是遍历这个容器ul 的所有子元素li ,然后判断每个li是否在选中的框内部。然后看每个元素的offsetLeft 和 offsetTop 计算元素相对于父元素的位置,然后通过getBoundingClientRect().height 和 getBoundingClientRect().width 确定子元素的宽高。这些就可以计算出元素的位置和大小了,然后如何判断这个元素是否在选择区域内呢?我的规则是这个元素的四个角位置有任何一个在选择区域内或者选择区域就在这个区域的内部,就算是这个元素被选中了(这个判断方式感觉不是很完美)。按照这个思路,继续完成我们的代码:

export default (Vue, options = {}) =>{
const listener = (ele, binding) =>{
let reactArea = {
startX: ,
startY: ,
endX: ,
endY:
}
//是否一直按下鼠标
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[])
}
reactArea.endX = e.layerX
reactArea.endY = e.layerY
let leftValue =
let topValue =
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 = ; 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

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

  本文结束,欢迎大家在留言区指出不正确的地方,喜欢的话可以点个推荐。

  

注:本文出自博客园 https://home.cnblogs.com/u/mdengcc/ ,转载请注明出处。

自定义类似于Jquery UI Selectable 的Vue指令v-selectable的更多相关文章

  1. jQuery基础(常用插件 表单验证,图片放大镜,自定义对象级,jQuery UI,面板折叠)

    1.表单验证插件——validate   该插件自带包含必填.数字.URL在内容的验证规则,即时显示异常信息,此外,还允许自定义验证规则,插件调用方法如下:   $(form).validate({o ...

  2. JQuery UI - selectable

    ·概述 Selectable插件允许用户对指定的元素进行选中的动作.此外还支持按住Ctrl键单击或拖拽选择多个元素. 官方示例地址:http://jqueryui.com/demos/selectab ...

  3. [转]JQuery ui 实现类似于confirm的功能

    本文转自:http://www.cnblogs.com/JerryWang1991/archive/2011/08/04/2127503.html 今天在改进参加一个全国比赛的项目作品时,发现使用了大 ...

  4. Vue指令及自定义指令的使用

    导航列表: 一.vue指令 二.自定义指令 一.vue指令 回到顶部    1. v-text v-text主要用来更新textContent,可以等同于JS的text属性,不会解析标签,会把标签解析 ...

  5. MVC自定义编辑视图,DateTime类型属性显示jQuery ui的datapicker

    实现的效果为:在编辑视图中,对DateTime类型的属性,显示jQuery UI的datepicker.效果如下: Student.cs public class Student    {       ...

  6. vue指令详解

    一.vue简绍 1. Vue.js是什么    Vue.js也称为Vue,读音/vju:/,类似view,错误读音v-u-e. 版本分为v1.0 和 v2.0 2.Vue.js的特点 1. 是一个构建 ...

  7. 这 5 个前端组件库,可以让你放弃 jQuery UI

    欢迎大家持续关注葡萄城控件技术团队博客,更多更好的原创文章尽在这里~~ 在建立Web应用时,通常都需要用到一些有用的UI组件.无论应用中需要的是日历,滑块,图形或其它用于提升或简化用户交互的组件,那么 ...

  8. 一个能拖动,能调整大小,能更新bind值的vue指令-vuedragx

    一. 背景说明 开发一个可自定义组件化门户配置页面,期间采用了vue框架作为前端视图引擎,作为一个刚入手vue的萌新,开发第一个功能就遇到了拦路虎.需要一个拖动并且可改变大小的容器盒子.当时查看vue ...

  9. 第三篇:Vue指令

    Vue指令 1.文本指令相关 v-*是Vue指令,会被vue解析,v-text="num"中的num是变量(指令是有限的,不可以自定义) v-text是原样输出渲染内容,渲染控制的 ...

随机推荐

  1. jquery中append与appendTo方法区别

    1. append(content)方法 方法作用:向每个匹配的元素内部追加内容. 参数介绍:content (<Content>): 要追加到目标中的内容. 用法示例: HTML代码为& ...

  2. 微信公众平台——token验证php版

    这几天开始接触微信公众号的开发,注册这些就不说了,我是先弄了个测试号用着.进入正题 所谓token验证,其实就是微信服务器向自己要用到的服务器url发送一段数据,其中有一个参数$_GET['echho ...

  3. A start job is running for xxx to stop

    CentOS7开机时,遇到这样的问题已经好多回了,查阅了许多这样的问题,总是没能找到自己想要的答案. 今天本来启动顺利,但是设置mysql.httpd服务开机启动之后,再次开机时又遇到这样的问题. 这 ...

  4. Java 用Freemarker完美导出word文档(带图片)

    Java  用Freemarker完美导出word文档(带图片) 前言 最近在项目中,因客户要求,将页面内容(如合同协议)导出成word,在网上翻了好多,感觉太乱了,不过最后还是较好解决了这个问题. ...

  5. (Android)Wifi-Direct直连

    因项目需要Pad端和手机端交互,采用wifi直连.查阅资料,大概写下一些资料和收获吧.注:大公司的代码带不出来,我也比较懒不想再认真去写一遍了,所以大概这个意思哦. wifi直连也叫做wifi设备点对 ...

  6. 使用express, create-react-app, mongodb搭建react模拟数据开发环境

    提要 最近刚刚完成了一个vue的项目,其中涉及的用户数有6000多个以及其他数据也比较多,为了在前端能够真实的进行数据模拟,所有把全量数据拷贝下来放到了api.json中.这样导致整个api.json ...

  7. 把int型非负数转换为英文

    数字转换为英文 输入为int型非负数,最大值为2^31 - 1 = 2 147 483 647 输出为String英文,最大输出为Two Billion One Hundred Forty Seven ...

  8. mysql浅龟定

    一,尽量不字啊数据库做运算 1,尽量不再数据库做运算, 2,将复杂运算移动到cpu 3,尽可能简单应用mysql 二,控制表数据量 1,纯 int不超过1000w 2,含char不超过500w 3,建 ...

  9. spring mvc3 静态文件放在WEB-INF下无法访问解决和解决@Controller失效问题

    今天整合spring+mybatis+easyui碰到的问题 将easyui放在WEB-INF下后无法访问, 解决:需配置<mvc:resources mapping="/easyui ...

  10. 【SpringMVC】使用SpringMVC进行上传文件!

    写在前面: 之前在上传文件的时候,使用的是commons-file-upload这个插件,非常方便,能控制每个文件的大小,总共大小,缓存,以及支持多个文件的同时上传,但是写一次上传文件的后台代码量太大 ...