1.复制指令
主要用到 document.execCommand('Copy')这个api
app.directive('copy', {
beforeMount(el, binding) {
el.targetContent = binding.value;
el.addEventListener('click', () => {
if (!el.targetContent) return console.warn('没有需要复制的目标内容');
// 创建textarea标签
const textarea = document.createElement('textarea');
// 设置相关属性
textarea.readOnly = 'readonly';
textarea.style.position = 'fixed';
textarea.style.top = '-99999px';
// 把目标内容赋值给它的value属性
textarea.value = el.targetContent;
// 插入到页面
document.body.appendChild(textarea);
// 调用onselect()方法
textarea.select();
const success = binding.arg;
// 把目标内容复制进剪贴板, 该API会返回一个Boolean
const res = document.execCommand('Copy');
res && success ? success(el.targetContent) : ''
// 移除textarea标签
document.body.removeChild(textarea);
})
},
updated(el, binding) {
// 实时更新最新的目标内容
el.targetContent = binding.value;
},
unmounted(el) {
el.removeEventListener('click', () => { })
}
})
在组件中使用:v-copy="复制的数据"
<template>
<div v-copy="copyvalue">点击复制</div>
</template>
<script setup>
import { ref } from 'vue'
const copyvalue = ref('点击复制哈哈哈 ')
</script>
如果复制之后还有其他操作,需要个回调方法做其他事情:v-copy:[回调函数]="要复制的数据"
<template>
<div v-copy:[success]="copyvalue">点击复制</div>
</template>
<script setup>
import { ref } from 'vue'
const copyvalue = ref('点击复制哈哈哈 ')
const success = (val) => {
console.log('内容是', val)
}
</script>
2.拖拽指令
让一个盒子限制宽高的父级元素内移动,没有限制的时候是相对于屏幕上的可视区内移动的。
app.directive("dragable",{
mounted(el, binding) {
// 设置目标元素基础属性
el.style.cursor = 'move';
el.style.position = 'fixed';
// 获取容器宽高
const containerId = binding.arg || null;
let containerWidth = window.innerWidth- getScrollWidth();
let containerHeight = window.innerHeight;
// 存在父级容器
if (containerId) {
const containerEle = document.getElementById(containerId);
let { width, height } = containerEle.getBoundingClientRect();
containerWidth = width;
containerHeight = height;
if (!['fixed', 'absolute', 'relative'].includes(getStyle(containerEle, 'position'))) {
containerEle.style.position = 'relative';
}
el.style.position = 'absolute';
}
// 鼠标在目标元素上按下
el.addEventListener('mousedown', (e) => {
let { width, height } = el.getBoundingClientRect();
// 当前目标元素的left与top
const left = el.offsetLeft;
const top = el.offsetTop;
// 保存按下的鼠标的X与Y
const mouseX = e.clientX;
const mouseY = e.clientY;
// 计算边界值
const leftLimit = left;
const rightLimit = containerWidth - left - width;
const topLimit = top;
const bottomLimit = containerHeight - top - height;
// 监听鼠标移动
document.onmousemove = (e) => {
// 鼠标移动的距离
let disX = e.clientX - mouseX;
let disY = e.clientY - mouseY;
// 左右边界
if (disX < 0 && disX <= -leftLimit) {
el.style.left = (left - leftLimit) + 'px';
} else if (disX > 0 && disX >= rightLimit) {
el.style.left = (left + rightLimit) + 'px';
} else {
el.style.left = (left + disX) + 'px';
}
// 上下边界
if (disY < 0 && disY <= -topLimit) {
el.style.top = (top - topLimit) + 'px';
} else if (disY > 0 && disY >= bottomLimit) {
el.style.top = (top + bottomLimit) + 'px';
} else {
el.style.top = (top + disY) + 'px';
}
return false;
}
// 监听鼠标抬起
document.onmouseup = () => {
document.onmousemove = null;
document.onmouseup = null;
}
});
// 获取元素的相关CSS
function getStyle(el, attr) {
return el.currentStyle ? el.currentStyle[attr] : window.getComputedStyle(el, false)[attr];
}
// 返回滚动条的宽度, 没有则返回0
function getScrollWidth() {
let noScroll, scroll, oDiv = document.createElement("DIV");
oDiv.style.cssText = "position:absolute; top:-1000px; width:100px; height:100px; overflow:hidden;";
noScroll = document.body.appendChild(oDiv).clientWidth;
oDiv.style.overflowY = "scroll";
scroll = oDiv.clientWidth;
document.body.removeChild(oDiv);
let isExsit = document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight);
return isExsit ? noScroll - scroll : 0
}
}
})
使用:给父级唯一个 id
值,通过 v-draggable:id属性,
传递进去。或者 直接v-draggable则是以浏览器为准
<div id="dragbox" style="width: 500px;height:500px;border:1px solid red;margin: 100px auto">
<div v-dragable:dragbox style="width:100px;height:100px;background:green;"></div>
</div>
3.按钮长按指令
点击一个按钮,不松开达到一定的时间才触发 ,移动端,pc端均可使用:
app.directive('longhandle', {
beforeMount(el, binding) {
const cb = binding.value;
el.$duration = binding.arg || 3000; // 获取长按时长, 默认3秒执行长按事件
if (typeof cb !== 'function') return console.warn('v-longpress指令必须接收一个回调函数');
let timer = null;
const add = (e) => {
// 排除点击与右键情况, event.button: 0-左键 2-右键
if (e.type === 'click' && e.button !== 0) return;
e.preventDefault();
if (timer === null) {
timer = setTimeout(() => {
cb();
timer = null;
}, el.$duration)
}
}
const cancel = () => {
if (timer !== null) {
clearTimeout(timer);
timer = null;
}
}
// 添加计时器
el.addEventListener('mousedown', add);
el.addEventListener('touchstart', add);
// 取消计时器
el.addEventListener('click', cancel);
el.addEventListener('mouseout', cancel);
el.addEventListener('touchend', cancel)
el.addEventListener('touchcancel', cancel)
},
updated(el, binding) {
// 可以实时更新时长
el.$duration = binding.arg;
},
unmounted(el) {
el.removeEventListener('mousedown', () => { });
el.removeEventListener('touchstart', () => { });
el.removeEventListener('click', () => { });
el.removeEventListener('mouseout', () => { });
el.removeEventListener('touchend', () => { });
el.removeEventListener('touchcancel', () => { });
}
})
使用:v-longhandle:[毫秒时间]="回调函数"
<template>
<button v-longhandle:[2000]="longpress">按钮</button>
</template>
<script setup>
const longpress = () => {
console.log('时间')
}
</script>
一般项目中自定义指令比较多,都是放在一个文件里面,然后统一在main.js中注册
utils下的directive.js文件:
export const copy = {
beforeMount(el, binding) {
el.targetContent = binding.value;
el.addEventListener('click', () => {
if (!el.targetContent) return console.warn('没有需要复制的目标内容');
// 创建textarea标签
const textarea = document.createElement('textarea');
// 设置相关属性
textarea.readOnly = 'readonly';
textarea.style.position = 'fixed';
textarea.style.top = '-99999px';
// 把目标内容赋值给它的value属性
textarea.value = el.targetContent;
// 插入到页面
document.body.appendChild(textarea);
// 调用onselect()方法
textarea.select();
const success = binding.arg;
// 把目标内容复制进剪贴板, 该API会返回一个Boolean
const res = document.execCommand('Copy');
res && success ? success(el.targetContent) : ''
// 移除textarea标签
document.body.removeChild(textarea);
})
},
updated(el, binding) {
// 实时更新最新的目标内容
el.targetContent = binding.value;
},
unmounted(el) {
el.removeEventListener('click', () => { })
}
}
export const longhandle = {
beforeMount(el, binding) {
const cb = binding.value;
el.$duration = binding.arg || 3000; // 获取长按时长, 默认3秒执行长按事件
if (typeof cb !== 'function') return console.warn('v-longpress指令必须接收一个回调函数');
let timer = null;
const add = (e) => {
// 排除点击与右键情况, event.button: 0-左键 2-右键
if (e.type === 'click' && e.button !== 0) return;
e.preventDefault();
if (timer === null) {
timer = setTimeout(() => {
cb();
timer = null;
}, el.$duration)
}
}
const cancel = () => {
if (timer !== null) {
clearTimeout(timer);
timer = null;
}
}
// 添加计时器
el.addEventListener('mousedown', add);
el.addEventListener('touchstart', add);
// 取消计时器
el.addEventListener('click', cancel);
el.addEventListener('mouseout', cancel);
el.addEventListener('touchend', cancel)
el.addEventListener('touchcancel', cancel)
},
updated(el, binding) {
// 可以实时更新时长
el.$duration = binding.arg;
},
unmounted(el) {
el.removeEventListener('mousedown', () => { });
el.removeEventListener('touchstart', () => { });
el.removeEventListener('click', () => { });
el.removeEventListener('mouseout', () => { });
el.removeEventListener('touchend', () => { });
el.removeEventListener('touchcancel', () => { });
}
}
export const dragable = {
mounted(el, binding) {
// 设置目标元素基础属性
el.style.cursor = 'move';
el.style.position = 'fixed';
// 获取容器宽高
const containerId = binding.arg || null;
let containerWidth = window.innerWidth- getScrollWidth();
let containerHeight = window.innerHeight;
// 存在父级容器
if (containerId) {
const containerEle = document.getElementById(containerId);
let { width, height } = containerEle.getBoundingClientRect();
containerWidth = width;
containerHeight = height;
if (!['fixed', 'absolute', 'relative'].includes(getStyle(containerEle, 'position'))) {
containerEle.style.position = 'relative';
}
el.style.position = 'absolute';
}
// 鼠标在目标元素上按下
el.addEventListener('mousedown', (e) => {
let { width, height } = el.getBoundingClientRect();
// 当前目标元素的left与top
const left = el.offsetLeft;
const top = el.offsetTop;
// 保存按下的鼠标的X与Y
const mouseX = e.clientX;
const mouseY = e.clientY;
// 计算边界值
const leftLimit = left;
const rightLimit = containerWidth - left - width;
const topLimit = top;
const bottomLimit = containerHeight - top - height;
// 监听鼠标移动
document.onmousemove = (e) => {
// 鼠标移动的距离
let disX = e.clientX - mouseX;
let disY = e.clientY - mouseY;
// 左右边界
if (disX < 0 && disX <= -leftLimit) {
el.style.left = (left - leftLimit) + 'px';
} else if (disX > 0 && disX >= rightLimit) {
el.style.left = (left + rightLimit) + 'px';
} else {
el.style.left = (left + disX) + 'px';
}
// 上下边界
if (disY < 0 && disY <= -topLimit) {
el.style.top = (top - topLimit) + 'px';
} else if (disY > 0 && disY >= bottomLimit) {
el.style.top = (top + bottomLimit) + 'px';
} else {
el.style.top = (top + disY) + 'px';
}
return false;
}
// 监听鼠标抬起
document.onmouseup = () => {
document.onmousemove = null;
document.onmouseup = null;
}
});
// 获取元素的相关CSS
function getStyle(el, attr) {
return el.currentStyle ? el.currentStyle[attr] : window.getComputedStyle(el, false)[attr];
}
// 返回滚动条的宽度, 没有则返回0
function getScrollWidth() {
let noScroll, scroll, oDiv = document.createElement("DIV");
oDiv.style.cssText = "position:absolute; top:-1000px; width:100px; height:100px; overflow:hidden;";
noScroll = document.body.appendChild(oDiv).clientWidth;
oDiv.style.overflowY = "scroll";
scroll = oDiv.clientWidth;
document.body.removeChild(oDiv);
let isExsit = document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight);
return isExsit ? noScroll - scroll : 0
}
}
}
main.js中注册
import { createApp } from 'vue'
import App from './App.vue'
import * as dictive from './utils/directive'
const app = createApp(App)
//注册
let dictValue= Object.values(dictive)
Object.keys(dictive).forEach((item,i)=>{
app.directive(item,dictValue[i])
})