代码
drag.js
import Vue from 'vue'
Vue.directive('dragx',(el, binding, vnode) => {
// 默认参数
let defaultOpts = {
dragDirection: 'n, e, s, w, ne, se, sw, nw, all', // 东 e 西 w 南 s 北 n 东北ne 东南 se 西南 sw 西北 nw 全部 all
dragContainerId: '',
dragBarClass: '', // 类选择器
resizeEdge: 10,
dirctDom: true,
canDrag: true,
canResize: true
}
let isMove = false;
binding.value = binding.value || {};
let cfg = Object.assign({}, defaultOpts, binding.value);
// 获取目标元素 resize方向
function getDirection(e) {
let el = e.currentTarget;
let dir = '';
let rect = el.getBoundingClientRect();
let win = el.ownerDocument.defaultView;
let offset = {
top: rect.top + win.pageYOffset,
left: rect.left + win.pageXOffset,
right: rect.right + win.pageXOffset,
bottom: rect.bottom + win.pageYOffset,
}
// 东 E 西 W 南 S 北 N
if(e.pageY > offset.top && e.pageY < offset.top + cfg.resizeEdge){
dir += 'n'
}
else if(e.pageY < offset.bottom && e.pageY > offset.bottom - cfg.resizeEdge){
dir += 's'
}
if (e.pageX > offset.left && e.pageX < offset.left + cfg.resizeEdge) {
dir += 'w';
}
else if (e.pageX < offset.right && e.pageX > offset.right - cfg.resizeEdge) {
dir += 'e';
}
if(binding.value) {
let directions = cfg.dragDirection.split(',');
for (let i = 0; i < directions.length; i++) {
let handle = directions[i].replace(/(^\s*)|(\s*$)/g, '');
if(handle == 'all' || handle === dir){
return dir
}
}
}
return '';
}
el.onmousedown = function (e) {
isMove = false;
// drag1
if (cfg.dragBarClass.length > 0 && e.target.classList.contains(cfg.dragBarClass)) {
isMove = true;
document.body.style.cursor = 'move';
}
let dir = getDirection(e);
let style = window.getComputedStyle(el);
function getStyleNumValue(key) {
return parseInt(style.getPropertyValue(key), 10)
}
let rect = el.getBoundingClientRect();
let data = {
width: getStyleNumValue("width"),
height: getStyleNumValue("height"),
left: getStyleNumValue("left"),
top: getStyleNumValue("top"),
borderLeft:getStyleNumValue("border-left-width"),
borderTop:getStyleNumValue("border-top-width"),
borderRight:getStyleNumValue("border-right-width"),
borderBottom:getStyleNumValue("border-bottom-width"),
deltX: e.pageX - rect.left, // 鼠标在元素内位置
deltY: e.pageY - rect.top,
startX: rect.left, // 元素的位置
startY: rect.top
};
if (dir === '' && !isMove) return;
// 创建遮罩
let mask = document.createElement("div");
mask.style.cssText = "position:absolute;top:0px;bottom:0px;left:0px;right:0px;";
document.body.appendChild(mask);
document.onmousemove = function (edom) {
// 获取当前鼠标位置
// 右
if (dir.indexOf("e") > -1) {
data.width = edom.pageX - data.startX + data.borderLeft+data.borderRight;
}
// 下
if (dir.indexOf("s") > -1) {
data.height = edom.pageY - data.startY + data.borderBottom+data.borderTop;
}
// 上
if (dir.indexOf("n") > -1) {
let deltheight = data.startY + data.borderBottom + data.borderTop - edom.pageY;
data.height += deltheight;
data.top -= deltheight;
data.startY -= deltheight;
}
// 左
if (dir.indexOf("w") > -1) {
let deltwidth = data.startX + data.borderLeft+data.borderRight- edom.pageX;
data.width += deltwidth;
data.left -= deltwidth;
data.startX -= deltwidth;
}
// 移动
if(isMove && cfg.canDrag){
let targetPageX = edom.pageX;
let targetPageY = edom.pageY;
let deltX = targetPageX - data.startX - data.deltX;
let deltY = targetPageY - data.startY - data.deltY;
let newLeft = parseInt(getStyleNumValue('left') || '0', 10) + deltX;
let newTop = parseInt(getStyleNumValue('top') || '0', 10) + deltY;
console.log(window.innerWidth + data.borderLeft+data.borderRight - data.width - cfg.resizeEdge)
if(newLeft <= 0){
newLeft = 0
}
else if(newLeft >= window.innerWidth - data.width - cfg.resizeEdge){
newLeft = window.innerWidth - data.width - cfg.resizeEdge
}
if(newTop <= 0){
newTop = 0
}
else if(newTop >= window.innerHeight - data.height - cfg.resizeEdge){
newLeft = window.innerHeight - data.height - cfg.resizeEdge
}
data.left = newLeft;
data.top = newTop;
data.startX = data.startX + deltX;
data.startY = data.startY + deltY;
}
// 缩放
if (cfg.dirctDom) {
if(cfg.canResize){
el.style.width = data.width + "px";
el.style.height = data.height + "px";
}
if(cfg.canDrag){
el.style.left = data.left + 'px';
el.style.top = data.top + 'px';
}
}
el.dispatchEvent(new CustomEvent('bindUpdate', {detail: data}))
}
document.onmouseup = function (e) {
document.body.style.cursor = '';
document.onmousemove = null;
document.onmouseup = null;
isMove = false;
document.body.removeChild(mask);
}
document.body.style.cursor = dir + '-resize';
}
el.onmousemove = function (e) {
if (cfg.dragBarClass.length > 0 && e.target.classList.contains(cfg.dragBarClass)&&cfg.canDrag) {
el.style.cursor = 'move';
return;
}
let dir = getDirection(e);
if (dir !== '') {
el.style.cursor = dir + '-resize';
return;
}
el.style.cursor = '';
}
el.onmouseleave = function (e) {
el.style.cursor = '';
}
})
vue
<template>
<div id="app1">
<div id="box1" v-dragx="dragBox1">
<div class="drag1"><b>开始拖拽</b></div>
</div>
</div>
</template>
<script>
import "./../directive/dragx";
export default {
name: "App",
data() {
return {
dragBox1: {
dragBarClass: "drag1",
dragContainerId: "app1"
}
};
},
methods:{
bindUpdate(event){
let data=event.detail;
console.log(data)
}
}
};
</script>
<style>
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
border: dashed 1px #000;
height: 500px;
position: relative;
}
#box1 {
width: 100px;
height: 100px;
border: solid 1px rgba(33, 61, 223, 0.541);
position: absolute;
top: 50px;
left: 200px;
background-color: #2d7bf4;
}
.drag1{
background-color:#ccc;
border-top: solid 1px rgba(33, 61, 223, 0.541);
border-bottom: solid 1px rgba(33, 61, 223, 0.541);
}
</style>
开发思路
-
通过鼠标移动实现组件移动,改变大小,一定需要操作dom,查看vue官方文档,从实用性,已经通用性,选择开发一个自定义vue指令
-
通过鼠标移动产生的位移,动态改变大小或位置
-
通过事件通知方式,实现更新bind值
所以有了以上思路,就需要一次掌握三个重要知识
-
vue 如果开发一个自定义指令
-
鼠标移动过程中,MouseEvent对象各种值的含义
-
如何使用并分发一个自定义事件
vue指令文档:https://cn.vuejs.org/v2/guide/custom-directive.html#ad
vue对开发着提供(bind、inserted、update、componentUpdated、unbind)注入点。
-
bind 只调用一次,指令第一次绑定到元素时调用,主要完成指令初始化设置
-
inserted 被绑定元素插入父节点时调用,官网对这里有一个强调 仅保证父节点存在,但不一定被插入文档中
-
update 所在组件的VNode更新时调用,但也可能发生在其子VNode更新之前,所以提供的参数中包含 vnode oldVnode,具体是否需要响应操作,可以比较这两个node中对应的值是否变化
-
componentUpdated 指令所在组件的 VNode极其子VNode全部更新后调用,此钩子函数的补充,就可以解决update钩子,不及时更新问题。
-
unbind 只调用一次,指令与元素解绑是调用
每一个钩子都带有参数 el、binding、vnode、oldVnode
实现
- 获取鼠标在目标元素上方向
// 默认参数
let defaultOpts = {
dragDirection: 'n, e, s, w, ne, se, sw, nw, all', // 东 e 西 w 南 s 北 n 东北ne 东南 se 西南 sw 西北 nw 全部 all
dragContainerId: '',
dragBarClass: '', // 类选择器
resizeEdge: 10,
dirctDom: true,
canDrag: true,
canResize: true
}
let isMove = false;
binding.value = binding.value || {};
let cfg = Object.assign({}, defaultOpts, binding.value);
// 获取目标元素 resize方向
function getDirection(e) {
let el = e.currentTarget;
let dir = '';
let rect = el.getBoundingClientRect();
let win = el.ownerDocument.defaultView;
let offset = {
top: rect.top + win.pageYOffset,
left: rect.left + win.pageXOffset,
right: rect.right + win.pageXOffset,
bottom: rect.bottom + win.pageYOffset,
}
// 东 E 西 W 南 S 北 N
if(e.pageY > offset.top && e.pageY < offset.top + cfg.resizeEdge){
dir += 'n'
}
else if(e.pageY < offset.bottom && e.pageY > offset.bottom - cfg.resizeEdge){
dir += 's'
}
if (e.pageX > offset.left && e.pageX < offset.left + cfg.resizeEdge) {
dir += 'w';
}
else if (e.pageX < offset.right && e.pageX > offset.right - cfg.resizeEdge) {
dir += 'e';
}
if(binding.value) {
let directions = cfg.dragDirection.split(',');
for (let i = 0; i < directions.length; i++) {
let handle = directions[i].replace(/(^\s*)|(\s*$)/g, '');
if(handle == 'all' || handle === dir){
return dir
}
}
}
return '';
}
- 判断是否在目标元素里
cfg.dragBarClass.length > 0
&& e.target.classList.contains(cfg.dragBarClass)
&& cfg.canDrag
- 鼠标点击目标元素
el.onmousedown = function (e) {
isMove = false;
// drag1
if (cfg.dragBarClass.length > 0 && e.target.classList.contains(cfg.dragBarClass)) {
isMove = true;
document.body.style.cursor = 'move';
}
let dir = getDirection(e);
let style = window.getComputedStyle(el);
function getStyleNumValue(key) {
return parseInt(style.getPropertyValue(key), 10)
}
let rect = el.getBoundingClientRect();
let data = {
width: getStyleNumValue("width"),
height: getStyleNumValue("height"),
left: getStyleNumValue("left"),
top: getStyleNumValue("top"),
borderLeft:getStyleNumValue("border-left-width"),
borderTop:getStyleNumValue("border-top-width"),
borderRight:getStyleNumValue("border-right-width"),
borderBottom:getStyleNumValue("border-bottom-width"),
deltX: e.pageX - rect.left, // 鼠标在元素内位置
deltY: e.pageY - rect.top,
startX: rect.left, // 元素的位置
startY: rect.top
};
if (dir === '' && !isMove) return;
// 创建遮罩
let mask = document.createElement("div");
mask.style.cssText = "position:absolute;top:0px;bottom:0px;left:0px;right:0px;";
document.body.appendChild(mask);
document.body.style.cursor = dir + '-resize';
}
- 鼠标在目标元素上移动
document.onmousemove = function (edom) {
// 获取当前鼠标位置
// 右
if (dir.indexOf("e") > -1) {
data.width = edom.pageX - data.startX + data.borderLeft+data.borderRight;
}
// 下
if (dir.indexOf("s") > -1) {
data.height = edom.pageY - data.startY + data.borderBottom+data.borderTop;
}
// 上
if (dir.indexOf("n") > -1) {
let deltheight = data.startY + data.borderBottom + data.borderTop - edom.pageY;
data.height += deltheight;
data.top -= deltheight;
data.startY -= deltheight;
}
// 左
if (dir.indexOf("w") > -1) {
let deltwidth = data.startX + data.borderLeft+data.borderRight- edom.pageX;
data.width += deltwidth;
data.left -= deltwidth;
data.startX -= deltwidth;
}
// 移动
if(isMove && cfg.canDrag){
let targetPageX = edom.pageX;
let targetPageY = edom.pageY;
let deltX = targetPageX - data.startX - data.deltX;
let deltY = targetPageY - data.startY - data.deltY;
let newLeft = parseInt(getStyleNumValue('left') || '0', 10) + deltX;
let newTop = parseInt(getStyleNumValue('top') || '0', 10) + deltY;
console.log(window.innerWidth + data.borderLeft+data.borderRight - data.width - cfg.resizeEdge)
if(newLeft <= 0){
newLeft = 0
}
else if(newLeft >= window.innerWidth - data.width - cfg.resizeEdge){
newLeft = window.innerWidth - data.width - cfg.resizeEdge
}
if(newTop <= 0){
newTop = 0
}
else if(newTop >= window.innerHeight - data.height - cfg.resizeEdge){
newLeft = window.innerHeight - data.height - cfg.resizeEdge
}
data.left = newLeft;
data.top = newTop;
data.startX = data.startX + deltX;
data.startY = data.startY + deltY;
}
// 缩放
if (cfg.dirctDom) {
if(cfg.canResize){
el.style.width = data.width + "px";
el.style.height = data.height + "px";
}
if(cfg.canDrag){
el.style.left = data.left + 'px';
el.style.top = data.top + 'px';
}
}
el.dispatchEvent(new CustomEvent('bindUpdate', {detail: data}))
}
document.onmouseup = function (e) {
document.body.style.cursor = '';
document.onmousemove = null;
document.onmouseup = null;
isMove = false;
document.body.removeChild(mask);
}