vue自定義指令實(shí)現(xiàn)點(diǎn)擊空白區(qū)域收起下拉窗口

(一)場(chǎng)景:
例如百度的搜索框煎谍,當(dāng)我們輸入搜索內(nèi)容的時(shí)候會(huì)彈出搜索建議提示窗,然后當(dāng)我們點(diǎn)擊其他區(qū)域的時(shí)候這個(gè)彈窗就會(huì)收起。上一張圖看一下:


demo.png

(二)先要知道的基礎(chǔ)點(diǎn):

  1. vm.$isServer

當(dāng)前 Vue 實(shí)例是否運(yùn)行于服務(wù)器。

該屬性不是直接定義在實(shí)例對(duì)象 vm 上,而是定義在原型對(duì)象上 => Vue.prototype.$isServer
該屬性根據(jù)當(dāng)前運(yùn)行的環(huán)境以及 process.ven.VUE_ENV 自動(dòng)設(shè)置。

  1. node.contains( otherNode )
    如果 otherNode 是 node 的后代節(jié)點(diǎn)或是 node 節(jié)點(diǎn)本身.則返回true , 否則返回 false.

  2. vue的自定義指令 官方文檔

示例寫法:

import Vue from 'vue'
Vue.directive('demo', {
  // 當(dāng)被綁定的元素插入到dom時(shí)……
  inserted (el) {},
  // 只調(diào)用一次,指令第一次綁定到元素時(shí)調(diào)用。在這里可以進(jìn)行一次性的初始化設(shè)置
  bind (el, binding, vnode) {},
  // 所在組件的 VNode 更新時(shí)調(diào)用
  update (el, binding, vnode) {},
  // 只調(diào)用一次尊残,指令與元素解綁時(shí)調(diào)用
  unbind (el, binding, vnode) {}
})

使用:

<div v-demo>

bind,update,unbind等等這些都是鉤子函數(shù),它們會(huì)被傳入一些參數(shù)(下面說明的都是要用到的犀农,了解更多請(qǐng)移步官方文檔):

  • el:指令所綁定的元素孟害,可以用來直接操作 DOM
  • binding
    • name:指令名
    • value:綁定的值
    • expression:指令表達(dá)式
  • vnode:虛擬節(jié)點(diǎn)

(三)實(shí)現(xiàn):
以下代碼參考自elementui的源碼,會(huì)有解讀

import Vue from 'vue'

const ctx = 'clickoutside'
const nodeList = []
const isServer = Vue.prototype.$isServer
let seed = 0
let startClick

// 用來綁定事件的方法,它是一個(gè)自執(zhí)行的函數(shù),會(huì)根據(jù)是否運(yùn)行于服務(wù)器和是否支持addEventListener來返回一個(gè)function
const on = (function () {
  if (!isServer && document.addEventListener) {
    return function (element, event, handler) {
      if (element && event && handler) {
        element.addEventListener(event, handler, false)
      }
    }
  } else {
    return function (element, event, handler) {
      if (element && event && handler) {
        element.attachEvent('on' + event, handler)
      }
    }
  }
})()
// 返回一個(gè)方法用來在點(diǎn)擊的時(shí)候觸發(fā)函數(shù)(觸發(fā)之前會(huì)判斷該元素是不是el,是不是focusElment以及他們的后代元素,如果是則不會(huì)執(zhí)行函數(shù))
function createDocumentHandler (el, binding, vnode) {
  return function (mouseup = {}, mousedown = {}) {
    if (
      !vnode ||
      !vnode.context ||
      !mouseup.target ||
      !mousedown.target ||
      el.contains(mouseup.target) ||
      el.contains(mousedown.target) ||
      el === mouseup.target || (vnode.context.focusElment &&
        (vnode.context.focusElment.contains(mouseup.target) ||
        vnode.context.focusElment.contains(mousedown.target)))
    ) {
      return
    }
    if (binding.expression &&
      el[ctx].methodName &&
      vnode.context[el[ctx].methodName]) {
      vnode.context[el[ctx].methodName]()
    } else {
      el[ctx].bindingFn && el[ctx].bindingFn()
    }
  }
}

if (!isServer) {
  on(document, 'mousedown', e => (startClick = e))
  on(document, 'mouseup', e => {
    // 循環(huán)所有的綁定節(jié)點(diǎn)揽乱,把它們的documentHandler屬性所綁定的函數(shù)執(zhí)行一次(這個(gè)時(shí)候得到的剛好是上面的那個(gè)判斷執(zhí)行的函數(shù))
    nodeList.forEach(node => node[ctx].documentHandler(e, startClick))
  })
}

Vue.directive('clickoutside', {
  // 當(dāng)被綁定的元素插入到dom時(shí)……
  inserted (el) {
    console.log(el)
  },
  // 只調(diào)用一次撒犀,指令第一次綁定到元素時(shí)調(diào)用。在這里可以進(jìn)行一次性的初始化設(shè)置
  // 把綁定的元素扔到nodeList里面,并給綁定元素設(shè)置屬性
  // documentHandler屬性在nodeList.forEach的時(shí)候執(zhí)行并得到一個(gè)function
  // bindingFn 是綁定的那個(gè)值,用來執(zhí)行的
  bind (el, binding, vnode) {
    nodeList.push(el)
    const id = seed++
    el[ctx] = {
      id,
      documentHandler: createDocumentHandler(el, binding, vnode),
      methodName: binding.expression,
      bindingFn: binding.value
    }
  },
  // 所在組件的 VNode 更新時(shí)調(diào)用
  update (el, binding, vnode) {
    el[ctx].documentHandler = createDocumentHandler(el, binding, vnode)
    el[ctx].methodName = binding.expression
    el[ctx].bindingFn = binding.value
  },
  // 只調(diào)用一次蚌斩,指令與元素解綁時(shí)調(diào)用
  unbind (el, binding, vnode) {
    const len = nodeList.length
    for (let i = 0; i < len; i++) {
      if (nodeList[i][ctx].id === el[ctx].id) {
        nodeList.splice(i, 1)
        break
      }
    }
    delete el[ctx]
  }
})

使用:

<template lang='pug'>
  .test-wrap
    input.input(
      v-model='text'
      v-clickoutside='handleClose'
      @focus='visible = true'
      placeholder='請(qǐng)?zhí)顚懰阉鲀?nèi)容'
    )
    ul.list-wrap(v-show='visible' ref='listWrap')
      li(v-for='item in list' @click='setValue(item.text)') {{item.text}}
</template>
<script>
export default {
  data () {
    return {
      text: '',
      visible: false,
      list: [{
        text: '1111',
        id: 1
      }, {
        text: 'aaaaa',
        id: 2
      }]
    }
  },
  computed: {
    focusElment () {
      return this.$refs.listWrap
    }
  },
  methods: {
    handleClose () {
      this.visible = false
    },
    setValue (val) {
      this.text = val
    }
  }
}
</script>

調(diào)用解析:

  1. template里面設(shè)置v-clickoutside='handleClose'調(diào)用這個(gè)自定義指令
  2. list-wrap用visible來控制顯示
  3. 把list-wrap設(shè)置為focusElment,目的是在點(diǎn)擊這個(gè)區(qū)域的時(shí)候不去觸發(fā)bindingFn,源碼中(vnode.context.focusElment &&
    (vnode.context.focusElment.contains(mouseup.target) ||
    vnode.context.focusElment.contains(mousedown.target))這段代碼解決這個(gè)問題
  4. handleClose函數(shù)設(shè)置visible的值為false,以隱藏下拉窗口

這個(gè)實(shí)現(xiàn)就分享這些幸缕,歡迎拍磚~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末滑蚯,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子缰猴,更是在濱河造成了極大的恐慌疑故,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件佛点,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門工猜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了觅廓?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵讨阻,是天一觀的道長(zhǎng)奇瘦。 經(jīng)常有香客問我邑跪,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任嘁傀,我火速辦了婚禮,結(jié)果婚禮上茴肥,老公的妹妹穿的比我還像新娘嗓节。我一直安慰自己鸵隧,他們只是感情好靡羡,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布杭煎。 她就那樣靜靜地躺著也切,像睡著了一般。 火紅的嫁衣襯著肌膚如雪两残。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天吟榴,我揣著相機(jī)與錄音狭瞎,去河邊找鬼。 笑死锌妻,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的贝室。 我是一名探鬼主播峡迷,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼娄帖!你這毒婦竟也來了数焊?” 一聲冷哼從身側(cè)響起干厚,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤闲先,失蹤者是張志新(化名)和其女友劉穎累驮,沒想到半個(gè)月后毒租,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年鲫忍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片做鹰。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡献宫,死狀恐怖捷兰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤柬焕,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布赎懦,位于F島的核電站傅瞻,受9級(jí)特大地震影響溺森,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一捞烟、第九天 我趴在偏房一處隱蔽的房頂上張望苍息。 院中可真熱鬧,春花似錦押逼、人聲如沸漂彤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肛循。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工只怎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓票渠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子廓脆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容