<template> <div :class="classes" :style="styles" v-move-ele.prevent.stop="{ moveStart, moveElement, moveStop: moveElementOver, }" > <slot></slot> <div class="resize-control"> <div v-for="(control, index) in controlPoints" :key="index" :class="control.classes" v-move-ele.prevent.stop="{ moveElement: control.movePoint, moveStop: control.movePointOver, }" ></div> <div class="control-line control-line-left"></div> <div class="control-line control-line-right"></div> <div class="control-line control-line-top"></div> <div class="control-line control-line-bottom"></div> </div> </div> </template> <script> import MoveEle from "../../directives/move-ele"; export default { name: "element-resize", directives: { MoveEle }, props: { value: { type: Object, required: true, }, active: { type: Array, default() { return ["r", "rb", "b", "lb", "l", "lt", "t", "rt"]; }, }, move: { type: Boolean, default: true, }, minWidth: { type: Number, default: 30, validator(val) { return val >= 0; }, }, maxWidth: { type: Number, default: 0, validator(val) { return val >= 0; }, }, minHeight: { type: Number, default: 30, validator(val) { return val >= 0; }, }, maxHeight: { type: Number, default: 0, validator(val) { return val >= 0; }, }, fitParent: { type: Array, default() { return ["w", "h"]; }, }, isCompact: { type: Boolean, default: false, }, transformFit: { type: Function, }, elementPk: { type: String, default: "", }, }, data() { return { sizePosOrigin: { x: 0, y: 0, w: 0, h: 0, }, offsetTopOrigin: 0, sizePos: { x: 0, y: 0, w: 0, h: 0, }, lastSizePos: {}, initOver: false, controlPoints: [], positionType: "static", parentNodeSize: { w: 0, h: 0, }, }; }, computed: { styles() { return this.initOver ? { left: this.sizePos.x + "px", top: this.sizePos.y + "px", width: this.sizePos.w + "px", height: this.sizePos.h + "px", zIndex: this.sizePos.zindex, position: this.positionType, } : {}; }, classes() { return [ "element-resize", { "element-resize-move": this.move, "element-resize-init": this.initOver, "element-resize-compact": this.isCompact, }, ]; }, fitParentTypeWidth() { return this.fitParent.includes("w"); }, fitParentTypeHeight() { return this.fitParent.includes("h"); }, }, created() { this.initControlPoints(); }, mounted() { this.initSize(); }, methods: { initControlPoints() { const posName = { l: "Left", r: "Right", t: "Top", b: "Bottom", }; this.controlPoints = this.active.map((type) => { const posFullName = type .split("") .map((item) => { return posName[item]; }) .join(""); return { classes: ["control-point", `control-point-${type}`], movePoint: this[`move${posFullName}Point`], movePointOver: this.moveOver, }; }); }, initSize() { const resizeDom = this.$el.childNodes[0]; this.positionType = window.getComputedStyle(resizeDom).position; this.sizePos = { ...this.value }; this.lastSizePos = { ...this.value }; this.sizePosOrigin = { ...this.value }; if (this.positionType === "relative") this.offsetTopOrigin = this.$el.offsetTop; this.initOver = true; }, fetchValidSizePos(sizePos, actionType) { if (sizePos.w <= this.minWidth) { sizePos.w = this.minWidth; } if (this.maxWidth !== 0 && sizePos.w >= this.maxWidth) { sizePos.w = this.maxWidth; } if (sizePos.h <= this.minHeight) { sizePos.h = this.minHeight; } if (this.maxHeight !== 0 && sizePos.h >= this.maxHeight) { sizePos.h = this.maxHeight; } if (!this.fitParent.length) { this.lastSizePos = { ...sizePos }; return sizePos; } // 不同的定位方式,计算方式有差异 this.parentNodeSize = { w: this.$el.offsetParent.offsetWidth, h: this.$el.offsetParent.offsetHeight, }; if (this.fitParentTypeWidth) { if (sizePos.x <= 0) { sizePos.x = 0; if (actionType.includes("left")) sizePos.w = this.lastSizePos.w; } if (sizePos.x + sizePos.w > this.parentNodeSize.w) { sizePos.x = this.lastSizePos.x; sizePos.w = this.parentNodeSize.w - sizePos.x; } } if (this.fitParentTypeHeight) { if (this.positionType === "relative") { const elOffsetTop = this.$el.offsetTop; if (this.sizePosOrigin.y - sizePos.y >= this.offsetTopOrigin) { sizePos.h = this.lastSizePos.h; sizePos.y = this.sizePosOrigin.y - this.offsetTopOrigin; } if (elOffsetTop + sizePos.h >= this.parentNodeSize.h) { sizePos.y = this.lastSizePos.y; sizePos.h = this.lastSizePos.h; // TODO:这里如果拖动太快,会有问题 // console.log(this.parentNodeSize.h, elOffsetTop, sizePos.h); } } else { if (sizePos.y <= 0) { sizePos.y = 0; if (actionType.includes("top")) sizePos.h = this.lastSizePos.h; } if (sizePos.y + sizePos.h > this.parentNodeSize.h) { sizePos.y = this.lastSizePos.y; sizePos.h = this.parentNodeSize.h - sizePos.y; } } } this.lastSizePos = { ...sizePos }; return this.transformFit ? Object.assign( {}, sizePos, this.transformFit({ id: this.elementPk, ...sizePos }, actionType) ) : sizePos; }, getLeftSize(left) { return { w: -left + this.sizePosOrigin.w, x: left + this.sizePosOrigin.x, }; }, getRightSize(left) { return { w: left + this.sizePosOrigin.w, }; }, getTopSize(top) { return { h: -top + this.sizePosOrigin.h, y: top + this.sizePosOrigin.y, }; }, getBottomSize(top) { return { h: top + this.sizePosOrigin.h, }; }, moveLeftPoint({ left }) { const sp = { ...this.sizePos, ...this.getLeftSize(left) }; this.sizePos = { ...this.fetchValidSizePos(sp, "left") }; this.emitChange(); }, moveRightPoint({ left }) { const sp = { ...this.sizePos, ...this.getRightSize(left) }; this.sizePos = { ...this.fetchValidSizePos(sp, "right") }; this.emitChange(); }, moveTopPoint({ top }) { const sp = { ...this.sizePos, ...this.getTopSize(top) }; this.sizePos = { ...this.fetchValidSizePos(sp, "top") }; this.emitChange(); }, moveBottomPoint({ top }) { const sp = { ...this.sizePos, ...this.getBottomSize(top) }; this.sizePos = { ...this.fetchValidSizePos(sp, "bottom") }; this.emitChange(); }, moveLeftTopPoint({ left, top }) { const sp = { ...this.sizePos, ...this.getLeftSize(left), ...this.getTopSize(top), }; this.sizePos = { ...this.fetchValidSizePos(sp, "left-top") }; this.emitChange(); }, moveRightTopPoint({ left, top }) { const sp = { ...this.sizePos, ...this.getRightSize(left), ...this.getTopSize(top), }; this.sizePos = { ...this.fetchValidSizePos(sp, "right-top") }; this.emitChange(); }, moveLeftBottomPoint({ left, top }) { const sp = { ...this.sizePos, ...this.getLeftSize(left), ...this.getBottomSize(top), }; this.sizePos = { ...this.fetchValidSizePos(sp, "left-bottom") }; this.emitChange(); }, moveRightBottomPoint({ left, top }) { const sp = { ...this.sizePos, ...this.getRightSize(left), ...this.getBottomSize(top), }; this.sizePos = { ...this.fetchValidSizePos(sp, "right-bottom") }; this.emitChange(); }, moveOver() { this.sizePosOrigin = { ...this.sizePos }; this.lastSizePos = { ...this.sizePos }; if (this.positionType === "relative") this.offsetTopOrigin = this.$el.offsetTop < 0 ? 0 : this.$el.offsetTop; this.emitChange(); this.$emit("resize-over", this.sizePos); }, moveStart() { this.$emit("on-click"); }, moveElement({ left, top }) { if (!this.move) return; const sp = { ...this.sizePos, ...{ x: left + this.sizePosOrigin.x, y: top + this.sizePosOrigin.y, }, }; this.sizePos = { ...this.fetchValidSizePos(sp, "move") }; this.emitChange(); }, moveElementOver() { if (!this.move) return; this.moveOver(); }, emitChange() { this.$emit("input", this.sizePos); this.$emit("change", this.sizePos); }, }, }; </script> <style lang="scss" scope> .element-resize { position: static; z-index: auto; background: #fff; box-sizing: content-box; &-move { cursor: move; } &-init { > div:first-child { width: 100% !important; height: 100% !important; position: relative !important; top: 0 !important; left: 0 !important; overflow: hidden; } } .control-point { position: absolute; width: 8px; height: 8px; border-radius: 50%; background: #617bea; z-index: 99; &-l { left: 0; top: 50%; width: 5px; height: 20px; margin-top: -10px; margin-left: -3px; border-radius: 0; padding-top: 3px; cursor: w-resize; text-align: center; color: #fff; &::before { content: "."; display: block; font-size: 16px; line-height: 1; margin-top: -9px; } &::after { content: "."; display: block; font-size: 16px; line-height: 1; margin-top: -9px; } } &-lt { left: 0; top: 0; margin-top: -5px; margin-left: -5px; cursor: nw-resize; } &-lb { left: 0; bottom: 0; margin-bottom: -5px; margin-left: -5px; cursor: sw-resize; } &-r { right: 0; top: 50%; width: 5px; height: 20px; margin-top: -10px; margin-right: -3px; cursor: e-resize; border-radius: 0; padding-top: 3px; text-align: center; color: #fff; &::before { content: "."; display: block; font-size: 16px; line-height: 1; margin-top: -9px; } &::after { content: "."; display: block; font-size: 16px; line-height: 1; margin-top: -9px; } } &-rt { right: 0; top: 0; margin-top: -5px; margin-right: -5px; cursor: ne-resize; } &-rb { right: 0; bottom: 0; margin-bottom: -5px; margin-right: -5px; cursor: se-resize; } &-t { left: 50%; top: 0; width: 30px; height: 5px; border-radius: 0; margin-top: -3px; margin-left: -15px; cursor: n-resize; text-align: center; color: #fff; &::before { content: "..."; display: inline-block; vertical-align: top; font-size: 16px; line-height: 1; margin-top: -10px; } } &-b { left: 50%; bottom: 0; width: 30px; height: 5px; border-radius: 0; margin-bottom: -3px; margin-left: -15px; cursor: s-resize; text-align: center; color: #fff; &::before { content: "..."; display: inline-block; vertical-align: top; font-size: 16px; line-height: 1; margin-top: -10px; } } } .control-line { position: absolute; z-index: 98; &-left { height: 100%; left: -1px; top: 0; border-left: 1px solid #617bea; } &-right { height: 100%; right: -1px; top: 0; border-left: 1px solid #617bea; } &-top { width: 100%; left: 0; top: -1px; border-top: 1px solid #617bea; } &-bottom { width: 100%; left: 0; bottom: -1px; border-top: 1px solid #617bea; } } &-compact { .control-line { &-left { left: 0; border-left: 1px dashed #bbb; } &-right { right: 0; border-left: 1px dashed #bbb; } &-top { top: 0; border-top: 1px dashed #bbb; } &-bottom { bottom: 0; border-top: 1px dashed #bbb; } } } } </style>