使用Vue自定义指令完结Select组件

话十分少说,先看作用。

本篇文章教我们写贰个特别轻巧的Select组件,想必很四人都写过Select,究竟它太常用了,然则本篇文章的言传身教使用到了Vue的自定义指令,假如您对Vue自定义指令不怎么熟练的话,本篇小说或然会令你抱有收获!

图片 1

做到的机能图如下:

  其实正是3个可以按住鼠标实行2个区域内条款选用的功效,相信用过Jquery
UI
的都清楚那是selectable的功力,然则大家假诺用Vue开荒的话未有类似的插件,当然你依然可以把jquery的拿过来直接用,不过作者又不想引入jquery
和 jquery UI在本人的类型中,于是笔者就融洽尝尝着完结类似的法力。

图片 2 

  要落到实处这些效果分两步。第三步是促成鼠标选用区域的职能,第步部是把那几个区域内被挑选的item增添八个active的类。

壹、首先,大家简要布局一下:

  先看怎么完结按住鼠标画虚线框,思路是先把容器元素的永世改为relative
然后决断当鼠标按下(mousedown)的时候,进行记住那一个点击点的地方(e.layerX
,
e.layerY),然后鼠标移动(mousemove)的时候,实时的监测鼠标的岗位(e.layerX
,
e.layerY),有了那五个职责就足以动态的创制二个div,它的固化为absolute,然后把它助长的容器框里,并且每趟清空前三个框就能够了。为啥是用e.layerX
e.layerY呢,

<template>
 <div class="select">
  <div class="inner">
   <div class="inputWrapper">
    <input type="text" readonly placeholder="请选择菜品">

   </div>
   <ul class="options">
    <li v-for="(item, index) in options" :key="index">{{item.value}}</li>
   </ul>
  </div>
 </div>
</template> 
......
data() {
  return {
    options: [
      {
       value: '西红柿鸡蛋'
      },
      {
       value: '青椒抱鸡蛋'
      },
      {
       value: '回锅肉'
      },
      {
       value: '宫保鸡丁'
      },
      {
       value: '地三鲜'
      }
    ],
  }
}

layerX layerY

功能是如此:

        
借使成分的position样式不是暗中认可的static,大家说那几个因素具有一定属性。

图片 3 

        
在当下触发鼠标事件的要素和它的祖辈元素中找到近来的富有原则性属性的因素,总计鼠标与其的偏移值,以找到成分的border的左上角的外交点作为相对点。假如找不到具有一定属性的因素,那么就相对于当下页面总计偏移,此时同1pageY。根据那几个思路完结以下代码:

上面可供选用的options用的是绝对定位;同一时间input设置了readonly,使input变的不得输入,全部布局很轻巧。

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

二、起先加多效果

  这一个时就足以实现画虚线框的功效

接下去,大家要增添五个作用:

图片 4

  • 点击上边的input框,能够切换展现下边包车型大巴options
  • 慎选options里的某些选项后让它显得在input里,同有时间让选项部分未有

  下一步是怎么把每一个item置为当选状态。思路是遍历这几个容器ul
的有所子成分li
,然后判定每种li是或不是在当选的框内部。然后看每种成分的offsetLeft 和
offsetTop
计算成分相对于父成分的职责,然后经过getBoundingClientRect().height 和
getBoundingClientRect().width
分明子成分的宽高。这几个就足以测算出成分的位置和尺寸了,然后如何判断这么些因素是还是不是在选择区域内啊?笔者的规则是那个因素的八个角地方有别的1个在挑选区域内依然选用区域就在这一个区域的里边,就终于这一个因素被入选了(这一个论断方式认为不是很周详)。根据这么些思路,继续实现大家的代码:

那两品种效益都挺轻巧,先来实现第贰个,点击input框切换呈现options,借助v-show就好。

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
 })
}
<div class="inputWrapper" @click="showOptions = !showOptions">
  <input type="text" readonly placeholder="请选择菜品">

</div>
<ul class="options" v-show="showOptions" v-show="showOptions"> //添加v-show
  <li v-for="(item, index) in options" :key="index">{{item.value}}</li>
</ul>
......
data() {
  showOptions: false
}

成功之后再看看如何利用,html 结构:

如上所示,在选拔里加多 v-show="showOptions" 并将 showOptions 早先化为
false 。相同的时间,在卷入 input 的 div 上增添 click 事件来回切换 showOptions
的布尔值。

<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

图片 5 

import Vue from 'vue'
import Selectable from '@/components/vue-selectable/vue-selectable.js' //这个修改为你的js路径
Vue.use(Selectable); 

其次个,点击下边包车型地铁选项,将被挑选的显示到input里,同不寻常候让options消失,也易于。

  再给我们的ul li
加点样式,注意大家的被采用项会被增加一个active的class,通过那几个来退换选中项样式

<div class="inputWrapper" @click="showOptions = !showOptions">
  <input type="text" readonly placeholder="请选择菜品" :value="selected"> //这里用value绑定一个data值selected

</div>
<ul class="options" v-show="showOptions">
  <li v-for="(item, index) in options" :key="index" @click="choose(item.value)">{{item.value}}</li>
</ul>
......
data() {
  return {
    ......
    showOptions: false
    selected: ''
  }
},
methods: {
  choose(value) {
    this.showOptions = false
    if (value !== this.selected) {
      this.selected = value
    }
  }
}
<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>

逻辑很简短,在input里用value绑定1个data值,点击选用有些选项后,将选拔的内容赋给这几个data值就可以,同有时间,隐藏壹切选项内容。

  那样就能够完成早先的功力了。实际上代码运营进度中如故有众多小bug的,本文只是提供了2个简易的思路和代码,越来越多效益能够团结修改代码举办增多。假诺不明了那个自定义指令为啥是那般的写法,能够参见作者的另一篇文章自定义懒加载图片插件v-lazyload。

功效如下:

//www.jb51.net/article/112355.htm

图片 6 

总结

从地点的功用图中得以看来,已经得以健康采纳了,可是有3个难题,就是它选用内容展现的时候,大家希望点击任何空白的地点也得以让接纳内容隐藏,可是上边的代码并从未缓慢解决那些主题材料,接下去大家来用三种方法来消除它。

以上所述是作者给我们介绍的自定义类似于jQuery UI Selectable
的Vue指令v-selectable,希望对我们有着辅助,即便咱们有任何疑问请给本身留言,作者会及时苏醒大家的。在此也非常感激咱们对台本之家网址的支撑!

3、常规的DOM操作 VS Vue自定义指令

您恐怕感兴趣的稿子:

事实上,完结那么些作用并简单,只是要想消除它就必要操作DOM

<div class="inputWrapper" @click.stop="showOptions = !showOptions"> //注意这里的stop修饰器
  <input type="text" readonly placeholder="请选择菜品" :value="selected">

</div>
<ul class="options" v-show="showOptions">
  <li v-for="(item, index) in options" :key="index" @click.stop="choose(item.value)">{{item.value}}</li> //还有这里的stop修饰器
</ul>
...
data() {
  return {
    ......
    showOptions: false
  }
}
mounted() {
  let that = this
  document.addEventListener('click', function() {
    that.showOptions = false
  })
}

上面的代码有两点:1个是在mounted前面给任何document增加了点击事件,这样在点击时候就能够将options隐藏,可是,我们在点击输入框部分和选择内容时,大家不希望它触发,而是让它走大家前边写好的逻辑,所以给八个click 事件都增添了 stop
修饰器来阻拦冒泡,那样,点击到它们的时候就不会冒泡到 document
上面了。效果如下:

图片 7 

到此处基本效率都写完了,能够通过抬高 $emit 和 props
来进行数据传递,让它进一步通用些。不过末了关于点击任啥地方方让选项部分未有的机能,大家还足以再完美下,能够设想使用Vue指令的格局完毕。

至于Vue指令,官方文书档案里有比较清楚的说明,借使不是专程驾驭能够点击这里先看看!

有关Vue自定义指令,在这几个例子中必要领悟以下为主知识点:

它是用来操作DOM的,所以具有Vue指令都会挂在 template 里的某部成分上

它有4个钩函数,一是 bind
,它在命令第1回绑定到成分上调用而且只调用一回,这些钩子很要紧,大家在这么些事例里会用到;第四个是
inserted ,它在要素插入到父成分的时候调用,官方文书档案里给了多个 v-focus
的例证就用到了它;第十个和第多个分级是 update 和 componentUpdated
,前者是在 vNode 更新时调用,后者是在创新完结后调用;最终是 unbind
,在命令和因素解绑时调用。

那五个钩函数能够 都至少可以传三个参数 ,第三是 el
就是被绑定指令的要素,第四个 binding , 它是个指标 ,而且
它的部分性质极度有用 ,它的质量包罗 name , expression 和 value
等,当然不只那多少个,但是大家那么些事例要用。比如:
要是笔者写八个自定义指令 v-example=”test” ,而这一个 test 是自己在 methods
里写的2个情势,那么就足以经过 binding.name 获得 example
字符串,能够透过 binding.value 获得 test
函数本人还要实践。假如这里不清楚不要紧接下去大家会聊到。

假若条分缕析察看,它们极其像 Vue
本身的生命周期钩子函数,只是它们是成效在指令上与成分的上的。从 bind
最开端绑定到结尾 unbind 解绑完成了贰个完全的周期。

好了,我们把前边 mounted
写的DOM操作相关的东西都删掉,初步入手写三个自定义指令。

<ul class="options" v-show="showOptions" v-clickOut="test"> //这里使用了下面的自定义指令,并将一个test方法传递进去了
  <li v-for="(item, index) in options" :key="index" @click.stop="choose(item.value)">{{item.value}}</li>
</ul>
...
methods: {
  ......
  test() {       //test函数,它作为参数传递给了指令
    console.log('这是一个测试函数')
  }
}, 
directives: {       //这里是自定义指令
  clickOut: {       // 这里是自定义的v-clickOut指令
    bind: function(el, binding) {    // bind钩子函数,当它与元素绑定的时候就会执行
      console.log('el===>', el)
      console.log('binding.name===>', binding.name)
      console.log('binding.expression===>', binding.expression)
      console.log('binding.value===>', binding.value)
    }
  }
}

上边的代码都有精通的笺注表明,我们自定义了二个 clickOut
的授命,并且把它挂到了二个要素上,而且给它传了三个 test 方法,我们来看望
console.log 出的事物都以些什么。

图片 8 

从地点的图形能够看看当指令和因素绑定的时候即 bind
的时候,它会实行bind函数获得广大管用的事物,上面大家讲了 bind
函数里有几个重大的参数,从打字与印刷出的结果里大家丰富清楚地见到,el便是命令绑定的元素本身,binding是二个对象,它获得了广大实惠的东西,包含传递进入的函数。

略知壹2了它的基本结构,大家就来一而再周到那些命令。

<ul class="options" v-show="showOptions" v-clickOut="test">
  <li v-for="(item, index) in options" :key="index" @click.stop="choose(item.value)">{{item.value}}</li>
</ul>
...
methods: {
  test() {
    this.showOptions = false  
  }
},
directives: {
  clickOut: {
   bind: function(el, binding) {
    document.addEventListener('click', function(e) {
     if (el.contains(e.target)) return false
     if (binding.expression) {
      binding.value()
     }
    })
   }
  }

看下下面改写过的代码做了些啥? 说下逻辑:当我们自定的 v-clickOut
与选用部分的ul成分绑定的时候,大家监听document的click事件,假设点击的要素是被命令绑定的成分的子成分或是被绑定成分本身,那就如何都不做;如若不是,这就举办传递进入的test函数。而test函数试行的结果就是把选用部分隐藏。

逻辑很明亮。

理之当然大家得以一连完善它。大家给 document.addEventListener了,也得以在
合适的时候 removeEventListener,那一个合适的时候就算 unbind 钩子函数。

故此我们可以全面下:

......
directives : {
  clickOut: {
    bind: function(el, binding) {
      function handler(e) {
       if (el.contains(el.target)) return false
       if (binding.expression) {
        binding.value()
       }
      }
      el.handler = handler
      document.addEventListener('click', el.handler)
    },
    unbind: function(el) {
      document.removeEventListener('click', el.handler)
    }    
  }
}

代码如上,效果如下:

图片 9 

大致总计一下:那是三个极其轻巧的小例子,因为需求操作DOM,所以大家选拔接纳自定义来产生,当然大家也足以运用别的方法。只是,在大家用Vue的时候,若是遭受须求操作DOM的时候,那么可以想想好还是不佳透过自定义指令来兑现吗!

你可能感兴趣的篇章:

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图