核心原理就是在四条边、四个顶点加上透明的div,给不同方向提供按下移动鼠标监听 ,对应计算宽度高度、坐标变化
特性:
- 支持设置拖拽的最小宽度、最小高度、最大宽度、最大高度
- 可以双击某一条边,最大化对应方向的尺寸;再一次双击,则会恢复到原始大小
sgDragSize.vue
<template>
<!-- 注意:此组件要放置于被拖拽修改尺寸的元素里面最后一个子节点位置! -->
<div
:class="$options.name"
:disabled="disabled"
draggable="false"
:sgDragSize_move="sgDragSize_move"
>
<div
:class="`resize-handle resize-${a}`"
draggable="false"
@mousedown.stop="clickResizeHandle(a)"
@dblclick="dblclickResizeHandle(a, $event)"
v-for="(a, i) in sizeIndexs"
:key="i"
/>
</div>
</template>
<script>
export default {
name: `sgDragSize`,
data() {
return {
form: {},
dragSizeIndex: ``,
originRect: {},
dblclickOriginRect: {},
sizeIndexs: [
`top`,
`right`,
`bottom`,
`left`,
`top-left`,
`top-right`,
`bottom-left`,
`bottom-right`,
],
sgDragSize_move: ``,
disabled: false, //屏蔽
taskbarHeight: 0, //任务栏高度
minWidth: 50, //拖拽的最小宽度
minHeight: 50, //拖拽的最小高度
maxWidth: innerWidth, //拖拽的最大宽度
maxHeight: innerHeight, //拖拽的最大高度
};
},
props: [`data`],
watch: {
disabled: {
handler(newValue, oldValue) {
newValue && this.__removeWindowEvents();
},
deep: true,
immediate: true,
},
data: {
handler(newValue, oldValue) {
//console.log(`深度监听${this.$options.name}:`, newValue, oldValue);
if (Object.keys(newValue || {}).length) {
this.form = this.$g.deepCopy(newValue);
this.$g.convertForm2ComponentParam(`disabled`, this);
this.$g.convertForm2ComponentParam(`taskbarHeight`, this);
this.$g.convertForm2ComponentParam(`minWidth`, this);
this.$g.convertForm2ComponentParam(`minHeight`, this);
this.$g.convertForm2ComponentParam(`maxWidth`, this);
this.$g.convertForm2ComponentParam(`maxHeight`, this);
}
},
deep: true, //深度监听
immediate: true, //立即执行
},
},
beforeDestroy() {
this.__removeWindowEvents();
},
methods: {
view_innerHeight() {
return innerHeight - this.taskbarHeight;
},
clickResizeHandle(d) {
this.dragSizeIndex = d;
this.mousedown(d);
},
dblclickResizeHandle(d, $event) {
let rect = this.$el.getBoundingClientRect();
rect.width < innerWidth &&
rect.height < this.view_innerHeight() &&
(this.dblclickOriginRect = rect);
this.dblResize(d, rect, $event);
},
__addWindowEvents() {
this.__removeWindowEvents();
addEventListener(`mousemove`, this.mousemove_window);
addEventListener(`mouseup`, this.mouseup_window);
},
__removeWindowEvents() {
removeEventListener(`mousemove`, this.mousemove_window);
removeEventListener(`mouseup`, this.mouseup_window);
},
mousedown(e) {
this.originRect = this.$el.getBoundingClientRect();
this.originRect.bottomRightX = this.originRect.x + this.originRect.width; //右下角坐标.x
this.originRect.bottomRightY = this.originRect.y + this.originRect.height; //右下角坐标.y
this.$emit(`dragStart`, e);
this.__addWindowEvents();
},
mousemove_window(e) {
let offsetDis = 5; //提供一个偏差值,防止拖拽过程鼠标手型闪烁变化
this.sgDragSize_move = `ing`;
let { x, y } = e;
let minWidth = this.minWidth,
minHeight = this.minHeight,
maxWidth = this.maxWidth,
maxHeight = this.maxHeight;
x < 0 && (x = 0),
y < 0 && (y = 0),
x > innerWidth && (x = innerWidth),
y > this.view_innerHeight() && (y = this.view_innerHeight());
let style = {};
switch (this.dragSizeIndex) {
case `top-left`:
style.left = x;
style.top = y;
style.width = this.originRect.bottomRightX - x;
style.width <= minWidth &&
((style.width = minWidth),
(style.left = this.originRect.bottomRightX - minWidth));
style.height = this.originRect.bottomRightY - y;
style.height <= minHeight &&
((style.height = minHeight),
(style.top = this.originRect.bottomRightY - minHeight));
break;
case `top`:
style.left = this.originRect.x;
style.top = y;
style.width = this.originRect.width;
style.height = this.originRect.bottomRightY - y;
style.height <= minHeight &&
((style.height = minHeight),
(style.top = this.originRect.bottomRightY - minHeight));
break;
case `top-right`:
style.left = this.originRect.x;
style.top = y;
style.width = x - this.originRect.x + offsetDis / 2;
style.width <= minWidth &&
((style.width = minWidth), (style.left = this.originRect.x));
style.height = this.originRect.bottomRightY - y;
style.height <= minHeight &&
((style.height = minHeight),
(style.top = this.originRect.bottomRightY - minHeight));
break;
case `left`:
style.left = x;
style.top = this.originRect.y;
style.width = this.originRect.bottomRightX - x;
style.width <= minWidth &&
((style.width = minWidth),
(style.left = this.originRect.bottomRightX - minWidth));
style.height = this.originRect.height;
break;
case `right`:
style.left = this.originRect.x;
style.top = this.originRect.y;
style.width = x - this.originRect.x + offsetDis / 2;
style.width <= minWidth &&
((style.width = minWidth), (style.left = this.originRect.x));
style.height = this.originRect.height;
break;
case `bottom-left`:
style.left = x;
style.top = this.originRect.y;
style.width = this.originRect.bottomRightX - x;
style.width <= minWidth &&
((style.width = minWidth),
(style.left = this.originRect.bottomRightX - minWidth));
style.height = y - this.originRect.y + offsetDis;
style.height <= minHeight &&
((style.height = minHeight), (style.top = this.originRect.y));
break;
case `bottom`:
style.left = this.originRect.x;
style.top = this.originRect.y;
style.width = this.originRect.width;
style.height = y - this.originRect.y + offsetDis / 2;
style.height <= minHeight &&
((style.height = minHeight), (style.top = this.originRect.y));
break;
case `bottom-right`:
style.left = this.originRect.x;
style.top = this.originRect.y;
style.width = x - this.originRect.x + offsetDis;
style.width <= minWidth &&
((style.width = minWidth), (style.left = this.originRect.x));
style.height = y - this.originRect.y + offsetDis;
style.height <= minHeight &&
((style.height = minHeight), (style.top = this.originRect.y));
break;
default:
}
style.width > maxWidth && (style.width = maxWidth);
style.height > maxHeight && (style.height = maxHeight);
Object.keys(style).forEach((k) => (style[k] = `${style[k]}px`));
this.$emit(`dragging`, { e, style });
},
dblResize(d, rect, e) {
this.$emit(`dblResize`, { e, style });
let style = {};
switch (d) {
case `top-left`:
break;
case `top`:
case `bottom`:
style.left = this.originRect.x;
style.top =
rect.height >= this.view_innerHeight() ? this.dblclickOriginRect.y : 0;
style.width = this.originRect.width;
style.height =
rect.height >= this.view_innerHeight()
? this.dblclickOriginRect.height
: this.view_innerHeight();
break;
case `top-right`:
break;
case `left`:
case `right`:
style.left = rect.width >= innerWidth ? this.dblclickOriginRect.x : 0;
style.top = this.originRect.y;
style.width =
rect.width >= innerWidth ? this.dblclickOriginRect.width : innerWidth;
style.height = this.originRect.height;
break;
case `bottom-left`:
break;
case `bottom-right`:
break;
default:
}
Object.keys(style).forEach((k) => (style[k] = `${style[k]}px`));
this.$emit(`dragging`, { e, style });
},
mouseup_window(e) {
this.sgDragSize_move = ``;
this.$emit(`dragEnd`, e);
this.__removeWindowEvents();
},
},
};
</script>
<style lang="scss">
.sgDragSize {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
pointer-events: none;
.resize-handle {
position: absolute;
z-index: 100;
display: block;
pointer-events: auto;
}
&[disabled] {
.resize-handle {
pointer-events: none;
}
}
.resize-top {
cursor: n-resize;
top: -3px;
left: 0px;
height: 7px;
width: 100%;
}
.resize-right {
cursor: e-resize;
right: -3px;
top: 0px;
width: 7px;
height: 100%;
}
.resize-bottom {
cursor: s-resize;
bottom: -3px;
left: 0px;
height: 7px;
width: 100%;
}
.resize-left {
cursor: w-resize;
left: -3px;
top: 0px;
width: 7px;
height: 100%;
}
.resize-top-right {
cursor: ne-resize;
width: 16px;
height: 16px;
right: -8px;
top: -8px;
}
.resize-bottom-right {
cursor: se-resize;
width: 20px;
height: 20px;
right: -8px;
bottom: -8px;
// 绘制三横线----------------------------------------
&::after {
pointer-events: none;
content: "";
position: absolute;
width: 10px;
height: 10px;
background-image: linear-gradient(
135deg,
transparent 50%,
#888 50%,
#888 55%,
transparent 55%,
transparent 70%,
#888 70%,
#888 75%,
transparent 75%,
transparent 90%,
#888 90%,
#888 95%,
transparent 95%
);
}
// ----------------------------------------
}
.resize-bottom-left {
cursor: sw-resize;
width: 16px;
height: 16px;
left: -8px;
bottom: -8px;
}
.resize-top-left {
cursor: nw-resize;
width: 16px;
height: 16px;
left: -8px;
top: -8px;
}
}
//防止拖拽物体内嵌的监听方法冲突,影响拖拽连续性----------------------------------------
body:has([sgDragSize_move="ing"]) {
.explorer {
pointer-events: none !important;
}
}
</style>
应用
<template>
<div>
<div class="box" :style="style">
<label>最小尺寸:宽度400px,高度200px</label>
<sgDragSize @dragging="dragging" :minWidth="400" :minHeight="200" />
</div>
</div>
</template>
<script>
import sgDragSize from "@/vue/components/admin/sgDragSize";
export default {
components: {
sgDragSize,
},
data() {
return {
style: {
height: '500px',
width: '800px',
left: '100px',
top: '100px',
},
}
},
methods: {
dragging({ style }) {
this.style = style;
},
},
};
</script>
<style lang="scss" scoped>
.box {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
background-color: #409EFF55;
box-sizing: border-box;
border: 1px solid #409EFF;
label {
user-select: none;
color: #409EFF;
}
}
</style>