超實用:Vue 自定義指令合集

作者:Huup_We

juejin.cn/post/6963840401899782175

在 Vue2.0 中,代碼複用和抽象的主要形式是組件。然而,有的情況下,你仍然需要對普通 DOM 元素進行底層操作,這時候就會用到自定義指令。

你可以將一些 css 樣式抽象到指令中,也可以將一些 js 操作放到指令中去執行。就使用上來說,指令不用像組件一樣需要引入和註冊,註冊後使用非常簡潔方便。

對於在項目中常用到的指令,在此做了一個合集介紹,附源碼可以直接在項目中使用。

元素點擊範圍擴展指令 v-expandClick

使用該指令可以隱式的擴展元素的點擊範圍,由於借用僞元素實現,故不會影響元素在頁面上的排列布局。

可傳入的參數爲:上右下左擴展的範圍,單位 px,默認向外擴展 10px。指令的代碼如下:

export default function (el, binding) {
    const s = document.styleSheets[document.styleSheets.length - 1]
    const DEFAULT = -10 // 默認向外擴展10px
    const ruleStr = `content:"";position:absolute;top:-${top || DEFAULT}px;bottom:-${bottom || DEFAULT}px;right:-${right || DEFAULT}px;left:-${left || DEFAULT}px;`
    const [top, right, bottom, left] = binding.expression && binding.expression.split(',') || []
    const classNameList = el.className.split(' ')
    el.className = classNameList.includes('expand_click_range') ? classNameList.join(' ') : [...classNameList, 'expand_click_range'].join(' ')
    el.style.position = el.style.position || "relative"
    if (s.insertRule) {
        s.insertRule('.expand_click_range::before' + '{' + ruleStr + '}', s.cssRules.length)
    } else { /* IE */
        s.addRule('.expand_click_range::before', ruleStr, -1)
    }
}

參數 Attributes:

JXi0hS

然後你可以在模板中任何元素上使用新的 v-expandClick property,如下:

<div v-expandClick="20,30,40,50" @click="glabClickoutside"> 點擊範圍擴大</div>

文本內容複製指令 v-copy

使用該指令可以複製元素的文本內容(指令支持單擊複製 v-copy、雙擊複製 v-copy.dblclick、點擊 icon 複製 v-copy.icon 三種模式),不傳參數時,默認使用單擊複製。

指令的代碼如下:

export default {
  bind (el, binding) {
    // 雙擊觸發複製
    if (binding.modifiers.dblclick) {
      el.addEventListener('dblclick', () => handleClick(el.innerText))
      el.style.cursor = 'copy'
    }
    // 點擊icon觸發複製
    else if (binding.modifiers.icon) {
      if (el.hasIcon) return
      const iconElement = document.createElement('i')
      iconElement.setAttribute('class', 'el-icon-document-copy')
      iconElement.setAttribute('style', 'margin-left:5px')
      el.appendChild(iconElement)
      el.hasIcon = true
      iconElement.addEventListener('click', () => handleClick(el.innerText))
      iconElement.style.cursor = 'copy'
    }
    // 單擊觸發複製
    else {
      el.addEventListener('click', () => handleClick(el.innerText))
      el.style.cursor = 'copy'
    }
  }
}

function handleClick (text) {
  // 創建元素
  if (!document.getElementById('copyTarget')) {
    const copyTarget = document.createElement('input')
    copyTarget.setAttribute('style', 'position:fixed;top:0;left:0;opacity:0;z-index:-1000;')
    copyTarget.setAttribute('id', 'copyTarget')
    document.body.appendChild(copyTarget)
  }

  // 複製內容
  const input = document.getElementById('copyTarget')
  input.value = text
  input.select()
  document.execCommand('copy')
  // alert('複製成功')
}

參數 Attributes:

qhRW2X

然後你可以在模板中任何元素上使用新的 v-copy property,如下:

<div v-copy> 單擊複製 </div>
<div v-copy.dblclick> 雙擊複製 </div>
<div v-copy.icon> icon複製 </div>

元素全屏指令 v-screenfull

全屏指令,點擊元素進行全屏 / 退出全屏的操作。支持元素後面是否插入 element-ui 的全屏圖標 el-icon-full-screen

指令的代碼如下:

import screenfull from 'screenfull'

export default {
  bind (el, binding) {
    if (binding.modifiers.icon) {
      if (el.hasIcon) return
      // 創建全屏圖標
      const iconElement = document.createElement('i')
      iconElement.setAttribute('class', 'el-icon-full-screen')
      iconElement.setAttribute('style', 'margin-left:5px')
      el.appendChild(iconElement)
      el.hasIcon = true
  }
    el.style.cursor = el.style.cursor || 'pointer'
    // 監聽點擊全屏事件
    el.addEventListener('click', () => handleClick())
  }
}

function handleClick () {
  if (!screenfull.isEnabled) {
    alert('瀏覽器不支持全屏')
    return
  }
  screenfull.toggle()
}

參數 Attributes:

Z3bS4Y

然後你可以在模板中任何元素上使用新的 v-screenfull property,如下:

<div v-screenfull.icon> 全屏 </div>

元素說明指令 v-tooltip

爲元素添加說明,如同 element-ui 的 el-tooltip(問號 icon 在鼠標覆蓋後,展示說明文字)。

 指令的代碼如下:

import Vue from 'vue'
export default function (el, binding) {
    if (el.hasIcon) return
    const iconElement = structureIcon(binding.arg, binding.value)
    el.appendChild(iconElement)
    el.hasIcon = true
}

function structureIcon (content, attrs) {
    // 拼接綁定屬性
    let attrStr = ''
    for (let key in attrs) {
        attrStr += `${key}=${attrs[key]} `
    }
    const a = `<el-tooltip content=${content} ${attrStr}><i class="el-icon-question" style="margin:0 10px"></i></el-tooltip>`
    // 創建構造器
    const tooltip = Vue.extend({
        template: a
    })
    // 創建一個 tooltip 實例並返回 dom 節點
    const component = new tooltip().$mount()
    return component.$el
}

參數 Attributes:

GQ5WJM

然後你可以在模板中任何元素上使用新的 v-tooltip property,如下:

<div v-tooltip:content='tootipParams'> 提示 </div>

舉例:

<div v-tooltip:提示內容爲XXX1> 提示1</div>
<div v-tooltip:提示內容爲XXX='tootipParams'> 提示2 </div>

爲指令傳入 element-ui 支持的參數:

data() {
    return {
        tootipParams: {
            placement: 'top',
            effect: 'light',
        }
    }
}

文字超出省略指令 v-ellipsis

使用該指令當文字內容超出寬度(默認 100 px)時自動變爲省略形式。等同於使用 css

width: 100px;
whiteSpace: nowrap
overflow: hidden;
textOverflow: ellipsis;

使用指令效果:

指令的代碼如下:

export default function (el, binding) {
    el.style.width = binding.arg || 100 + 'px'
    el.style.whiteSpace = 'nowrap'
    el.style.overflow = 'hidden';
    el.style.textOverflow = 'ellipsis';
}

參數 Attributes:

Lee4Sm

然後你可以在模板中任何元素上使用新的 v-ellipsis property,如下:

<div v-ellipsis:100> 需要省略的文字是阿薩的副本阿薩的副本阿薩的副本阿薩的副本</div>

回到頂部指令 v-backtop

使用該指令可以讓頁面或指定元素回到頂部。

可選指定元素,如果不指定則全局頁面回到頂部。可選在元素偏移多少 px 後顯示 backtop 元素,例如在滾動 400px 後顯示回到頂部按鈕。

指令的代碼如下:

export default {
  bind (el, binding, vnode) {
    // 響應點擊後滾動到元素頂部
    el.addEventListener('click', () => {
    const target = binding.arg ? document.getElementById(binding.arg) : window
    target.scrollTo({
      top: 0,
      behavior: 'smooth'
      })
    })
  },
  update (el, binding, vnode) {
    // 滾動到達參數值纔出現綁定指令的元素
    const target = binding.arg ? document.getElementById(binding.arg) : window
    if (binding.value) {
      target.addEventListener('scroll', (e) => {
        if (e.srcElement.scrollTop > binding.value) {
          el.style.visibility = 'unset'
        } else {
          el.style.visibility = 'hidden'
        }
      })
    }
    // 判斷初始化狀態
    if (target.scrollTop < binding.value) {
      el.style.visibility = 'hidden'
    }
  },
  unbind (el) {
    const target = binding.arg ? document.getElementById(binding.arg) : window
    target.removeEventListener('scroll')
    el.removeEventListener('click')
  }
}

參數 Attributes:

kA07xa

然後你可以在模板中任何元素上使用新的 v-backtop property,如下表示在 id 爲 app 的元素滾動 400px 後顯示綁定指令的元素:

<div  v-backtop:app="400"> 回到頂部 </div>

也可以這樣使用,表示爲一直顯示綁定指令的元素,並且是全局頁面回到頂部:

<div  v-backtop> 回到頂部 </div>

空狀態指令 v-empty

使用該指令可以顯示缺省的空狀態。可以傳入默認圖片(可選,默認無圖片)、默認文字內容(可選,默認爲暫無數據)、以及標示是否顯示空狀態(必選)。

指令的代碼如下:

import Vue from "vue";
export default {
  update (el, binding, vnode) {
    el.style.position = el.style.position || 'relative'
    const { offsetHeight, offsetWidth } = el
    const { visible, content, img } = binding.value
    const image = img ? `<img src="${img}" height="30%" width="30%"></img>` : ''
    const defaultStyle = "position:absolute;top:0;left:0;z-index:9999;background:#fff;display:flex;justify-content: center;align-items: center;"
    const empty = Vue.extend({
    template: `<div style="height:${offsetHeight}px;width:${offsetWidth}px;${defaultStyle}">
      <div style="text-align:center">
        <div>${image}</div>
        <div>${content || '暫無數據'}</div>
      </div>
    </div>`
    })
    const component = new empty().$mount().$el
    if (visible) {
      el.appendChild(component)
    } else {
      el.removeChild(el.lastChild)
    }
  },
}

參數 Attributes:

yug0xb

然後你可以在模板中任何元素上使用新的 v-empty property,如下傳入對象 emptyValue

<div style="height:500px;width:500px" v-empty="emptyValue"> 原本內容

需要傳入一個參數對象,例如顯示文字爲:暫無列表,圖片路徑爲 ../../assets/images/blue_big.png,控制標示 visible

emptyValue = {
  content: '暫無列表',
  img: require('../../assets/images/blue_big.png'),
  visible: true,
},

徽標指令 v-badge

使用該指令在元素右上角顯示徽標。

支持配置徽標的背景顏色、徽標形狀;支持傳入徽標上顯示的數字。

 

指令的代碼如下:

import Vue from 'vue'

const SUCCESS = '#72c140'
const ERROR = '#ed5b56'
const WARNING = '#f0af41'
const INFO = '#4091f7'
const HEIGHT = 20
let flag = false
export default {
  update (el, binding, vnode) {
    const { modifiers, value } = binding
    const modifiersKey = Object.keys(modifiers)
    let isDot = modifiersKey.includes('dot')
    let backgroundColor = ''
    if (modifiersKey.includes('success')) {
      backgroundColor = SUCCESS
    } else if (modifiersKey.includes('warning')) {
      backgroundColor = WARNING
    } else if (modifiersKey.includes('info')) {
      backgroundColor = INFO
    } else {
      backgroundColor = ERROR
    }

    const targetTemplate = isDot 
        ? `<div style="position:absolute;top:-5px;right:-5px;height:10px;width:10px;border-radius:50%;background:${backgroundColor}"></div>` 
        : `<div style="background:${backgroundColor};position:absolute;top:-${HEIGHT / 2}px;right:-${HEIGHT / 2}px;height:${HEIGHT}px;min-width:${HEIGHT}px;border-radius:${HEIGHT / 2}px;text-align:center;line-height:${HEIGHT}px;color:#fff;padding:0 5px;">${value}</div>`
        
    el.style.position = el.style.position || 'relative'
    const badge = Vue.extend({
      template: targetTemplate
    })
    const component = new badge().$mount().$el
    if (flag) {
      el.removeChild(el.lastChild)
    }
    el.appendChild(component)
    flag = true
  }
}

參數 Attributes:

CyfRF3

然後你可以在模板中任何元素上使用新的 v-badge property,如下:

<div v-badge.dot.info="badgeCount" style="height:50px;width:50px;background:#999"> </div>

拖拽指令 v-drag

使用該指令可以對元素進行拖拽。

指令的代碼如下:

export default {
  let _el = el
  document.onselectstart = function() {
    return false  //禁止選擇網頁上的文字
  }
  
  _el.onmousedown = e => {
    let disX = e.clientX - _el.offsetLeft //鼠標按下,計算當前元素距離可視區的距離
    let disY = e.clientY - _el.offsetTop
    document.onmousemove = function(e){     
      let l = e.clientX - disX
      let t = e.clientY - disY;
      _el.style.left = l + "px"
      _el.style.top = t + "px"
    }
    document.onmouseup = e => {
      document.onmousemove = document.onmouseup = null
    }
    return false
  }
}

然後你可以在模板中任何元素上使用新的 v-drag property,如下:

<div v-drag> 支持拖拽的元素 </div>

響應縮放指令 v-resize

使用該指令可以響應元素寬高改變時執行的方法。

指令的代碼如下:

export default {
  bind(el, binding) {
    let width = '', height = '';
    function isReize() {
      const style = document.defaultView.getComputedStyle(el);
      if (width !== style.width || height !== style.height) {
        binding.value();  // 執行傳入的方法
      }
      width = style.width;
      height = style.height;
     }
     el.__timer__ = setInterval(isReize, 300); // 週期性監聽元素是否改變
  },
  unbind(el) {
    clearInterval(el.__timer__);
  }
}

參數 Attributes:

a0YpKs

然後你可以在模板中任何元素上使用新的 v-resize property,如下:

// 傳入 resize() 方法
<div v-resize="resize"></div>

如何使用這些指令?

爲了便於管理指令,我們將每個指令都存在於單獨的 js 文件中,你可以像這樣將指令 import 進來後註冊指令:

import Vue from 'vue'
import ellipsis from './ellipsis' // 引入指令
// import other directives

const directives = {
  ellipsis
  // other directives
}

Object.keys(directives).forEach(name => Vue.directive(name, directives[name]))

或者你可以直接使用 Vue.deirect 進行註冊:

// 註冊一個全局自定義指令 `v-focus`
Vue.directive('focus', {
  // 當被綁定的元素插入到 DOM 中時……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})

這樣就可以正常使用這些指令了:

<div v-指令名稱 />

總結

我們常常在引入全局功能時,主要都是寫於 js 文件、組件中。不同於他們在使用時每次需要引用或註冊,在使用上指令更加簡潔。

除了將功能封裝成組件,還可以多多考慮將一些簡潔實用的功能放到 deirect 中。例如:常用的 css 樣式、js 的一些操作、utils 中的一些工具方法、甚至是一個完整的組件都可以放進去(不過需要考慮一下性能和複雜度)。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/XIaJM8GHNYHReg91uRlCCg