Vue 拖拽功能 之 自定义指令实现元素拖拽功能
拖拽功能主要操作的是真实的DOM元素以及鼠标事件,在vue中使用自定义指令最合适不过了。 当然使用组件封装起来,然后拖拽这个组件,也是可以实现相同的效果,不过我觉得这样有点大材小用,也缺乏灵活性,好处是可以传入更多的控制属性。总之,在组件与指令之间找到平衡点,自有取舍。...
拖拽功能主要操作的是真实的DOM元素以及鼠标事件,在vue中使用自定义指令最合适不过了。 当然使用组件封装起来,然后拖拽这个组件,也是可以实现相同的效果,不过我觉得这样有点大材小用,也缺乏灵活性,好处是可以传入更多的控制属性。总之,在组件与指令之间找到平衡点,自有取舍。
言归正传,要想实现拖拽的效果 要么使用 drag 事件, 要么使用 mouse 鼠标事件,我这里选用的是 mouse 鼠标事件组合。
拖拽元素的主要思路是:
- 鼠标按下时(mousedown 事件),记录下坐标,作为开始拖拽的起点
- 鼠标移动时(mousemove 事件),用移动后的坐标 与 开始拖拽的起点坐标,计算出移动的距离
- 用元素所在位置的坐标,加减移动的距离,则可以计算出元素移动后的位置坐标
- 既然已经知道了,元素移动后的位置坐标,那就可以为所欲为了
- 最后,鼠标松开时(mouseup 事件),移除监听事件,恢复到初始状态
使用方法:
1. 注册自定义指令 v-draggable
, 全局注册使用或者局部注册使用
全局注册
// main.js
import draggable from "./directives/draggable";
Vue.directive("draggable", draggable);
局部注册
// 需要使用的文件
import draggable from "./directives/draggable";
export default {
data() {
return {
// ...
};
},
directives:{
directive
}
};
2. 简单应用
元素可以随意拖拽,没有边界限制
<div style="position: fixed;" v-draggable></div>
3. 简单应用
元素可以随意拖拽,有边界限制, 无法拖动到屏幕之外, 关键在于 设置 【sticky】 修饰符
<div style="position: fixed;" v-draggable.sticky></div>
4. 自定义应用
拖拽的数据,可以通过 handleDraggable 返回,由你决定数据如何使用, 如何显示
<div style="position: fixed;" v-draggable="handleDraggable"></div>
<script>
export default {
methods: {
handleDraggable(config){
console.log(config)
if (config.dragging) {
this.style = {
left: `${config.x}px`,
top: `${config.y}px`,
width: `${config.rect.width}px`,
height: `${config.rect.height}px`,
};
}
}
}
};
</script>
5. 如何区分点击事件
当拖拽的元素,必须需要获取点击事件时,我们可以通过自定义应用的方式实现这一个目标;
一般情况下,我们认为元素没有移动时,鼠标的一次按下与松开,为一次点击事件,故而可以如下处理
handleDraggable(config){
console.log(config)
const {type, isMove} = config
if (type === "mouseup" && !isMove) {
// 这里为点击事件
return;
}
}
6. 返回的数据有哪些
id: 唯一自增id
binding, 自定义指令中的 binding
vnode, 自定义指令中的 vnode
target: 拖拽元素
type: 触发的鼠标事件名称,用于区分不同阶段 mousedown、mousemove、mouseup
rect: 返回getBoundingClientRect()获取拖拽元素的位置
x: 拖拽元素距离屏幕左侧的距离
y: 拖拽元素距离屏幕上方的距离
dragstartX: 鼠标按下时坐标
dragstartY:
dragendX: 鼠标松开时坐标, 无值时为undefined
dragendY:
startX: 拖拽起点坐标
startY:
diffX: 拖拽元素当前移动端额狙击
diffY:
dragging: 元素是否可以拖拽
isMove: 拖拽的元素是否有移动
7. 自定义指令实现元素拖拽功能源码
let seed = 0;
const ctx = "@@draggableContext";
function handleMousedown(event) {
event.preventDefault();
const el = this;
const rect = el.getBoundingClientRect();
Object.assign(el[ctx], {
type: "mousedown",
rect: rect,
x: rect.x || rect.left,
y: rect.y || rect.top,
dragstartX: event.clientX, // 鼠标按下时坐标
dragstartY: event.clientY,
dragendX: void 0, // 鼠标松开时坐标
dragendY: void 0,
startX: event.clientX, // 起点坐标
startY: event.clientY,
dragging: true,
isMove: false,
});
callback(el);
window.addEventListener("mousemove", el[ctx]._handleMousemove, false);
window.addEventListener("mouseup", el[ctx]._handleMouseup, false);
}
function handleMousemove(el) {
return function(event) {
event.preventDefault();
if (event.target === document.documentElement) return;
const current = {
x: event.clientX,
y: event.clientY,
};
const diff = {
x: current.x - el[ctx].startX,
y: current.y - el[ctx].startY,
};
if (el[ctx].binding.modifiers.sticky) {
// 不会拖出屏幕边缘
const clientWidth = document.documentElement.clientWidth;
const clientHeight = document.documentElement.clientHeight;
const {
x,
y,
rect: { width, height },
} = el[ctx];
if (diff.x < 0 && x + diff.x <= 0) {
el[ctx].x = 0;
} else if (diff.x > 0 && x + width - clientWidth >= 0) {
el[ctx].x = clientWidth - width;
} else {
el[ctx].x += diff.x;
}
if (diff.y < 0 && y + diff.y <= 0) {
el[ctx].y = 0;
} else if (diff.y > 0 && y + height - clientHeight >= 0) {
el[ctx].y = clientHeight - height;
} else {
el[ctx].y += diff.y;
}
} else {
el[ctx].x += diff.x;
el[ctx].y += diff.y;
}
Object.assign(el[ctx], {
type: "mousemove",
startX: current.x,
startY: current.y,
diffX: diff.x,
diffY: diff.y,
isMove: true,
});
callback(el);
};
}
function handleMouseup(el) {
return function(event) {
event.preventDefault();
const lastType = el[ctx].type;
Object.assign(el[ctx], {
type: "mouseup",
dragendX: event.clientX, // 鼠标按下时坐标
dragendY: event.clientY,
dragging: false,
isMove: lastType === "mousemove",
});
callback(el);
window.removeEventListener("mousemove", el[ctx]._handleMousemove, false);
window.removeEventListener("mouseup", el[ctx]._handleMouseup, false);
};
}
function callback(el) {
const bindingFn = el[ctx]?.binding?.value;
if (typeof bindingFn === "function") {
bindingFn({ ...el[ctx], target: el });
} else {
const { x, y, rect, dragging } = el[ctx];
if (!dragging) return;
el.style.cssText = `
left: ${x}px;
top: ${y}px;
width: ${rect.width}px;
height: ${rect.height}px;
`;
}
}
/**
* v-draggable
* @desc
* @example
* ```vue
* <div v-draggable>
*
* <div v-draggable.sticky>
* <div v-draggable="handleDraggable">
* ```
*/
export default {
bind(el, binding, vnode) {
const id = seed++;
el[ctx] = {
id,
binding,
vnode,
_handleMousemove: handleMousemove(el, binding, vnode),
_handleMouseup: handleMouseup(el, binding, vnode),
};
el.addEventListener("mousedown", handleMousedown, false);
},
unbind(el) {
window.removeEventListener("mousemove", el[ctx]._handleMousemove, false);
window.removeEventListener("mouseup", el[ctx]._handleMouseup, false);
el.removeEventListener("mousedown", handleMousedown, false);
delete el[ctx];
},
};
8. 对应移动端代码实现
let seed = 0;
const ctx = "@@draggableContext";
function handleMousedown(event) {
// event.preventDefault();
const el = this;
const rect = el.getBoundingClientRect();
Object.assign(el[ctx], {
type: "mousedown",
rect: rect,
x: rect.x || rect.left,
y: rect.y || rect.top,
dragstartX: event.touches[0].clientX, // 鼠标按下时坐标
dragstartY: event.touches[0].clientY,
dragendX: void 0, // 鼠标抬起时坐标
dragendY: void 0,
startX: event.touches[0].clientX, // 起点坐标
startY: event.touches[0].clientY,
dragging: true,
isMove: false,
});
callback(el);
window.addEventListener("touchmove", el[ctx]._handleMousemove, false);
window.addEventListener("touchend", el[ctx]._handleMouseup, false);
}
function handleMousemove(el) {
return function(event) {
// event.preventDefault();
if (event.target === document.documentElement) return;
const current = {
x: event.touches[0].clientX,
y: event.touches[0].clientY,
};
const diff = {
x: current.x - el[ctx].startX,
y: current.y - el[ctx].startY,
};
if (el[ctx].binding.modifiers.sticky) {
// 不会拖出屏幕边缘
const clientWidth = document.documentElement.clientWidth;
const clientHeight = document.documentElement.clientHeight;
const {
x,
y,
rect: { width, height },
} = el[ctx];
if (diff.x < 0 && x + diff.x <= 0) {
el[ctx].x = 0;
} else if (diff.x > 0 && x + width - clientWidth >= 0) {
el[ctx].x = clientWidth - width;
} else {
el[ctx].x += diff.x;
}
if (diff.y < 0 && y + diff.y <= 0) {
el[ctx].y = 0;
} else if (diff.y > 0 && y + height - clientHeight >= 0) {
el[ctx].y = clientHeight - height;
} else {
el[ctx].y += diff.y;
}
} else {
el[ctx].x += diff.x;
el[ctx].y += diff.y;
}
Object.assign(el[ctx], {
type: "mousemove",
startX: current.x,
startY: current.y,
diffX: diff.x,
diffY: diff.y,
isMove: true,
});
callback(el);
};
}
function handleMouseup(el) {
return function(event) {
// event.preventDefault();
const lastType = el[ctx].type;
Object.assign(el[ctx], {
type: "mouseup",
dragendX: el[ctx].startX, // 鼠标按下时坐标
dragendY: el[ctx].startY,
dragging: false,
isMove: lastType === "mousemove",
});
callback(el);
window.removeEventListener("touchmove", el[ctx]._handleMousemove, false);
window.removeEventListener("touchend", el[ctx]._handleMouseup, false);
};
}
function callback(el) {
const bindingFn = el[ctx]?.binding?.value;
if (typeof bindingFn === "function") {
bindingFn({ ...el[ctx], target: el });
} else {
const { x, y, rect, dragging } = el[ctx];
if (!dragging) return;
el.style.cssText = `
left: ${x}px;
top: ${y}px;
width: ${rect.width}px;
height: ${rect.height}px;
`;
}
}
/**
* v-draggable
* @desc
* @example
* ```vue
* <div v-draggable>
*
* <div v-draggable.sticky>
* <div v-draggable="handleDraggable">
* ```
*/
export default {
bind(el, binding, vnode) {
const id = seed++;
el[ctx] = {
id,
binding,
vnode,
_handleMousemove: handleMousemove(el, binding, vnode),
_handleMouseup: handleMouseup(el, binding, vnode),
};
el.addEventListener("touchstart", handleMousedown, false);
},
unbind(el) {
window.removeEventListener("touchmove", el[ctx]._handleMousemove, false);
window.removeEventListener("touchstart", el[ctx]._handleMouseup, false);
el.removeEventListener("touchend", handleMousedown, false);
delete el[ctx];
},
};
更多推荐
所有评论(0)