我是下拉框的内容,点击外部区域可以关闭
有的情况下,需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。
自定义指令使用情景:
1.按钮级别权限的控制。
2.按钮的波纹动态效果。
3.一键copy的功能。
4.输入框自动聚焦。
5.下拉菜单,点击下拉菜单以外的地方时隐藏下拉菜单。
6.时间转换,比如朋友圈发布动态后的相对时间,比如刚刚、两分钟前等等。
7.输入框的最小值和最大值限制。
一:自定义指令有全局注册指令和局部注册指令两种方式:
全局注册指令:
Vue.directive('focus',{
bind:function(){},
inserted:function(){},
update:function(){},
componentUpdated:function(){},
unbind:function(){}
});
局部注册指令:在.vue文件中使用directives属性:
directives:{
focus:{
bind:function(){},
inserted:function(){},
update:function(){},
componentUpdated:function(){},
unbind:function(){}
}
}
注册指令成功后,直接在dom元素上使用v-focus。
二:注册指令的使用。
下面以局部注册指令来举例子。
自定义指令有4个钩子函数,v-check-num="{key:'myNum',maxval:1000,minval:100}"
钩子函数需要用到的参数解析:
1.el:指令所绑定的元素。
2.binding:绑定对象。属性包含
name(指令名),不包括v-,此例子中为check-num。
value(计算后的指令所绑定的值),此例子中为{key:'myNum',maxval:1000,minval:100}。
oldValue(指令所绑定的前一个值,仅在update和componentUpdated中可用)。
express(绑定的值的字符串形式),此例子中为'{key:'myNum',maxval:1000,minval:100}'。
arg(传递给指令的参数),v-check-num:a中的arg为a。
modifiers(修饰符对象),v-my-directive.foo.bar中的modifiers为{foo:true,bar:true}。
3.vnode:编译生成的虚拟节点。属性有context为虚拟节点的上下文。
注意:el可读可写,其他参数只读。如果需要在钩子函数之间共享数据,可通过dataset来实现。
4.oldVnode:上一个虚拟节点。
directiveCom.vue:
checkNum指令
在App.vue里面引用上面的指令组件:
效果图:
页面渲染时,触发了bind和inserted函数。
点击按钮,效果图:
此时,改变了directiveCom组件里的dom元素的css样式,触发了update和componentUpdated函数。
将directiveCom中的v-show改为v-if:
checkNum指令
点击按钮,效果如下:
v-if是dom组件的的销毁和创建,指令与元素解绑,此时触发了unbind函数。
三:进阶:使用自定义指令来设置输入框的最小值和最大值规则。规则:如果输入的值<最小值,那么默认为最小值;如果输入的值>最大值,那么默认为最大值;如果输入的是非数字,则清空输入框,默认为空。
directiveCom组件:
checkNum指令
四:用自定义指令来实现几种需求场景:
git链接:https://github.com/xiaoli0510/vue-directive
directive.vue:
1.validBtn指令:按钮级别权限的控制
2.waves指令:按钮的波纹动态效果
3.copy指令:一键copy的功能
4.focus指令:输入框自动聚焦
5.clickoutside指令:下拉菜单,点击下拉菜单以外的地方时隐藏下拉菜单
我是下拉框的内容,点击外部区域可以关闭
7.time指令:时间转换,比如朋友圈发布动态后的相对时间,比如刚刚、两分钟前等等
8.checkNum指令:输入框的最小值和最大值限制
先上效果图:
checkNum、validBtn、focus、clickoutside是局部注册指令。
waves、copy、time是全局注册指定。
time.js:
var Time = {
//获取当前时间戳
getUnix: function () {
var date = new Date();
return date.getTime();
},
//获取今天0点0分0秒的时间戳
getTodayUnix: function () {
var date = new Date();
date.setHours(0);
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
return date.getTime();
},
//获取今年1月1日0点0分0秒的时间戳
getYearUnix: function () {
var date = new Date();
date.setMonth(0);
date.setDate(1);
date.setHours(0);
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
return date.getTime();
},
//获取标准年月日
getLastDate: function (time) {
var date = new Date(time);
var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1;
var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate();
return date.getFullYear() + '-' + month + '-' + day;
},
//转换时间
getFormatTime: function (timestamp) {
var now = this.getUnix(); //当前时间戳
var today = this.getTodayUnix(); //今天0点时间戳
//var year = this.getYearUnix(); //今年0点时间戳
var timer = (now - timestamp) / 1000; //转换为妙级别时间戳
var tip = '';
if (timer <= 0) {
tip = '刚刚';
} else if (Math.floor(timer / 60) <= 0) {
tip = '刚刚';
} else if (timer < 3600) {
tip = Math.floor(timer / 60) + '分钟前';
} else if (timer >= 3600 && (timestamp - today >= 0)) {
tip = Math.floor(timer / 3600) + '小时前';
} else if (timer / 86400 <= 31) {
tip = Math.ceil(timer / 86400) + '天前';
} else {
tip = this.getLastDate(timestamp);
}
return tip;
}
};
export default {
bind: function (el, binding) {
el.innerHTML = Time.getFormatTime(binding.value);
el._timeout_ = setInterval(function () {
el.innerHTML = Time.getFormatTime(binding.value);
}, 1000);
},
unbind:function(el){
clearInterval(el._timeout_);
delete el._timeout_;
}
}
copy.js:
let listenAction
export default {
inserted(el, binding) {
const params = binding.value || {}
const stickyTop = params.stickyTop || 0
const zIndex = params.zIndex || 1000
const elStyle = el.style
elStyle.position = '-webkit-sticky'
elStyle.position = 'sticky'
// if the browser support css sticky(Currently Safari, Firefox and Chrome Canary)
// if (~elStyle.position.indexOf('sticky')) {
// elStyle.top = `${stickyTop}px`;
// elStyle.zIndex = zIndex;
// return
// }
const elHeight = el.getBoundingClientRect().height
const elWidth = el.getBoundingClientRect().width
elStyle.cssText = `top: ${stickyTop}px; z-index: ${zIndex}`
const parentElm = el.parentNode || document.documentElement
const placeholder = document.createElement('div')
placeholder.style.display = 'none'
placeholder.style.width = `${elWidth}px`
placeholder.style.height = `${elHeight}px`
parentElm.insertBefore(placeholder, el)
let active = false
const getScroll = (target, top) => {
const prop = top ? 'pageYOffset' : 'pageXOffset'
const method = top ? 'scrollTop' : 'scrollLeft'
let ret = target[prop]
if (typeof ret !== 'number') {
ret = window.document.documentElement[method]
}
return ret
}
const sticky = () => {
if (active) {
return
}
if (!elStyle.height) {
elStyle.height = `${el.offsetHeight}px`
}
elStyle.position = 'fixed'
elStyle.width = `${elWidth}px`
placeholder.style.display = 'inline-block'
active = true
}
const reset = () => {
if (!active) {
return
}
elStyle.position = ''
placeholder.style.display = 'none'
active = false
}
const check = () => {
const scrollTop = getScroll(window, true)
const offsetTop = el.getBoundingClientRect().top
if (offsetTop < stickyTop) {
sticky()
} else {
if (scrollTop < elHeight + stickyTop) {
reset()
}
}
}
listenAction = () => {
check()
}
window.addEventListener('scroll', listenAction)
},
unbind() {
window.removeEventListener('scroll', listenAction)
}
}
waves.js:
import './waves.css'
const context = '@@wavesContext'
function handleClick(el, binding) {
function handle(e) {
const customOpts = Object.assign({}, binding.value)
const opts = Object.assign({
ele: el, // 波纹作用元素
type: 'hit', // hit 点击位置扩散 center中心点扩展
color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
},
customOpts
)
const target = opts.ele
if (target) {
target.style.position = 'relative'
target.style.overflow = 'hidden'
const rect = target.getBoundingClientRect()
let ripple = target.querySelector('.waves-ripple')
if (!ripple) {
ripple = document.createElement('span')
ripple.className = 'waves-ripple'
ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
target.appendChild(ripple)
} else {
ripple.className = 'waves-ripple'
}
switch (opts.type) {
case 'center':
ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px'
ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px'
break
default:
ripple.style.top =
(e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop ||
document.body.scrollTop) + 'px'
ripple.style.left =
(e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft ||
document.body.scrollLeft) + 'px'
}
ripple.style.backgroundColor = opts.color
ripple.className = 'waves-ripple z-active'
return false
}
}
if (!el[context]) {
el[context] = {
removeHandle: handle
}
} else {
el[context].removeHandle = handle
}
return handle
}
export default {
bind(el, binding) {
el.addEventListener('click', handleClick(el, binding), false)
},
update(el, binding) {
el.removeEventListener('click', el[context].removeHandle, false)
el.addEventListener('click', handleClick(el, binding), false)
},
unbind(el) {
el.removeEventListener('click', el[context].removeHandle, false)
el[context] = null
delete el[context]
}
}
waves.css:
.waves-ripple {
position: absolute;
border-radius: 100%;
background-color: rgba(0, 0, 0, 0.15);
background-clip: padding-box;
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-transform: scale(0);
-ms-transform: scale(0);
transform: scale(0);
opacity: 1;
}
.waves-ripple.z-active {
opacity: 0;
-webkit-transform: scale(2);
-ms-transform: scale(2);
transform: scale(2);
-webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
transition: opacity 1.2s ease-out, transform 0.6s ease-out;
transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
}
在main.js中引入js文件,再进行全局注册:
main.js:
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
import waves from './directive/waves/waves.js'
Vue.directive('waves', waves)
import copy from './directive/copy.js'
Vue.directive('copy', copy)
import time from './directive/time.js'
Vue.directive('time', time)
new Vue({
render: h => h(App),
}).$mount('#app')