index.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. <template>
  2. <div
  3. ref="elRef"
  4. v-ele-move-directive.prevent.stop="{
  5. moveStart,
  6. moveElement,
  7. moveStop: moveElementOver,
  8. }"
  9. :class="classes"
  10. :style="styles"
  11. >
  12. <slot></slot>
  13. <div class="resize-control">
  14. <template v-for="(control, index) in controlPoints" :key="index">
  15. <div
  16. v-ele-move-directive.prevent.stop="{
  17. moveElement: control.movePoint,
  18. moveStop: control.movePointOver,
  19. }"
  20. :class="control.classes"
  21. ></div>
  22. </template>
  23. <div class="control-line control-line-left"></div>
  24. <div class="control-line control-line-right"></div>
  25. <div class="control-line control-line-top"></div>
  26. <div class="control-line control-line-bottom"></div>
  27. </div>
  28. </div>
  29. </template>
  30. <script setup lang="ts">
  31. import {
  32. reactive,
  33. ref,
  34. computed,
  35. onMounted,
  36. onBeforeMount,
  37. CSSProperties,
  38. } from "vue";
  39. import { vEleMoveDirective } from "../../directives/eleMove";
  40. import { objModifyAssign } from "../../utils/tool";
  41. import {
  42. defaultActive,
  43. defaultFitParent,
  44. SizeData,
  45. ActionType,
  46. FitParentItem,
  47. ContronItem,
  48. PositionData,
  49. } from "./types";
  50. defineOptions({
  51. name: "ElementResize",
  52. });
  53. const emit = defineEmits<{
  54. (event: "update:modelValue", data: SizeData): void;
  55. (event: "change", data: SizeData): void;
  56. (event: "resizeOver", data: SizeData): void;
  57. (event: "onClick"): void;
  58. }>();
  59. interface Props {
  60. modelValue: SizeData;
  61. active?: ActionType[];
  62. move?: boolean;
  63. minWidth?: number;
  64. maxWidth?: number;
  65. minHeight?: number;
  66. maxHeight?: number;
  67. fitParent?: FitParentItem[];
  68. isCompact?: boolean;
  69. }
  70. const props = withDefaults(defineProps<Props>(), {
  71. active: () => [...defaultActive],
  72. move: true,
  73. minWidth: 30,
  74. maxWidth: 0,
  75. minHeight: 30,
  76. maxHeight: 0,
  77. fitParent: () => [...defaultFitParent],
  78. isCompact: false,
  79. });
  80. const sizePosOrigin = reactive({ x: 0, y: 0, w: 0, h: 0 });
  81. const sizePos = reactive({ x: 0, y: 0, w: 0, h: 0 });
  82. const offsetTopOrigin = ref(0);
  83. const lastSizePos = reactive({ x: 0, y: 0, w: 0, h: 0 });
  84. const initOver = ref(false);
  85. const controlPoints = ref<ContronItem[]>([]);
  86. const parentNodeSize = reactive({ w: 0, h: 0 });
  87. const elRef = ref();
  88. const styles = computed(() => {
  89. return initOver.value
  90. ? {
  91. left: `${sizePos.x}px`,
  92. top: `${sizePos.y}px`,
  93. width: `${sizePos.w}px`,
  94. height: `${sizePos.h}px`,
  95. zIndex: props.modelValue.zindex || "auto",
  96. position: "absolute" as CSSProperties["position"],
  97. }
  98. : undefined;
  99. });
  100. const classes = computed(() => {
  101. return [
  102. "element-resize",
  103. {
  104. "element-resize-move": props.move,
  105. "element-resize-init": initOver.value,
  106. "element-resize-compact": props.isCompact,
  107. },
  108. ];
  109. });
  110. const fitParentTypeWidth = computed(() => {
  111. return props.fitParent.includes("w");
  112. });
  113. const fitParentTypeHeight = computed(() => {
  114. return props.fitParent.includes("h");
  115. });
  116. function initControlPoints() {
  117. const actions = {
  118. l: moveLeftPoint,
  119. r: moveRightPoint,
  120. t: moveTopPoint,
  121. b: moveBottomPoint,
  122. lt: moveLeftTopPoint,
  123. rt: moveRightTopPoint,
  124. lb: moveLeftBottomPoint,
  125. rb: moveRightBottomPoint,
  126. };
  127. controlPoints.value = props.active.map((type) => {
  128. return {
  129. classes: ["control-point", `control-point-${type}`],
  130. movePoint: actions[type],
  131. movePointOver: (data: PositionData) => {
  132. actions[type](data);
  133. movePointOver();
  134. },
  135. };
  136. });
  137. }
  138. function initSize() {
  139. const elDom = elRef.value as HTMLElement;
  140. const resizeDom = elDom.firstElementChild as Element;
  141. objModifyAssign(sizePos, props.modelValue);
  142. objModifyAssign(lastSizePos, props.modelValue);
  143. objModifyAssign(sizePosOrigin, props.modelValue);
  144. initOver.value = true;
  145. }
  146. function fetchValidSizePos(
  147. sizeData: SizeData,
  148. actionType: ActionType | "move"
  149. ) {
  150. if (sizeData.w <= props.minWidth) {
  151. sizeData.w = props.minWidth;
  152. if (actionType.includes("l"))
  153. sizeData.x = lastSizePos.x + lastSizePos.w - sizeData.w;
  154. }
  155. if (props.maxWidth !== 0 && sizeData.w >= props.maxWidth) {
  156. sizeData.w = props.maxWidth;
  157. }
  158. if (sizeData.h <= props.minHeight) {
  159. sizeData.h = props.minHeight;
  160. if (actionType.includes("t"))
  161. sizeData.y = lastSizePos.y + lastSizePos.h - sizeData.h;
  162. }
  163. if (props.maxHeight !== 0 && sizeData.h >= props.maxHeight) {
  164. sizeData.h = props.maxHeight;
  165. }
  166. if (!props.fitParent.length) {
  167. objModifyAssign(lastSizePos, sizeData);
  168. return sizeData;
  169. }
  170. // 不同的定位方式,计算方式有差异
  171. const elDom = elRef.value as HTMLElement;
  172. const elParentDom = elDom.offsetParent as HTMLElement;
  173. parentNodeSize.w = elParentDom.offsetWidth;
  174. parentNodeSize.h = elParentDom.offsetHeight;
  175. if (fitParentTypeWidth.value) {
  176. if (sizeData.x <= 0) {
  177. sizeData.x = 0;
  178. if (actionType.includes("l")) sizeData.w = lastSizePos.w + lastSizePos.x;
  179. }
  180. if (sizeData.x + sizeData.w >= parentNodeSize.w) {
  181. if (actionType === "move") {
  182. sizeData.x = parentNodeSize.w - sizeData.w;
  183. } else {
  184. sizeData.w = parentNodeSize.w - sizeData.x;
  185. }
  186. }
  187. }
  188. if (fitParentTypeHeight.value) {
  189. if (sizeData.y <= 0) {
  190. sizeData.y = 0;
  191. if (actionType.includes("t")) sizeData.h = lastSizePos.h + lastSizePos.y;
  192. }
  193. if (sizeData.y + sizeData.h >= parentNodeSize.h) {
  194. if (actionType === "move") {
  195. sizeData.y = parentNodeSize.h - sizeData.h;
  196. } else {
  197. sizeData.h = parentNodeSize.h - sizeData.y;
  198. }
  199. }
  200. }
  201. objModifyAssign(lastSizePos, sizeData);
  202. return sizeData;
  203. }
  204. function getLeftSize(left: number) {
  205. return {
  206. w: -left + sizePosOrigin.w,
  207. x: left + sizePosOrigin.x,
  208. };
  209. }
  210. function getRightSize(left: number) {
  211. return {
  212. w: left + sizePosOrigin.w,
  213. };
  214. }
  215. function getTopSize(top: number) {
  216. return {
  217. h: -top + sizePosOrigin.h,
  218. y: top + sizePosOrigin.y,
  219. };
  220. }
  221. function getBottomSize(top: number) {
  222. return {
  223. h: top + sizePosOrigin.h,
  224. };
  225. }
  226. function moveLeftPoint({ left }: Pick<PositionData, "left">) {
  227. const sp = { ...sizePos, ...getLeftSize(left) };
  228. objModifyAssign(sizePos, fetchValidSizePos(sp, "l"));
  229. emitChange();
  230. }
  231. function moveRightPoint({ left }: Pick<PositionData, "left">) {
  232. const sp = { ...sizePos, ...getRightSize(left) };
  233. objModifyAssign(sizePos, fetchValidSizePos(sp, "r"));
  234. emitChange();
  235. }
  236. function moveTopPoint({ top }: Pick<PositionData, "top">) {
  237. const sp = { ...sizePos, ...getTopSize(top) };
  238. objModifyAssign(sizePos, fetchValidSizePos(sp, "t"));
  239. emitChange();
  240. }
  241. function moveBottomPoint({ top }: Pick<PositionData, "top">) {
  242. const sp = { ...sizePos, ...getBottomSize(top) };
  243. objModifyAssign(sizePos, fetchValidSizePos(sp, "b"));
  244. emitChange();
  245. }
  246. function moveLeftTopPoint({ left, top }: PositionData) {
  247. const sp = {
  248. ...sizePos,
  249. ...getLeftSize(left),
  250. ...getTopSize(top),
  251. };
  252. objModifyAssign(sizePos, fetchValidSizePos(sp, "lt"));
  253. emitChange();
  254. }
  255. function moveRightTopPoint({ left, top }: PositionData) {
  256. const sp = {
  257. ...sizePos,
  258. ...getRightSize(left),
  259. ...getTopSize(top),
  260. };
  261. objModifyAssign(sizePos, fetchValidSizePos(sp, "rt"));
  262. emitChange();
  263. }
  264. function moveLeftBottomPoint({ left, top }: PositionData) {
  265. const sp = {
  266. ...sizePos,
  267. ...getLeftSize(left),
  268. ...getBottomSize(top),
  269. };
  270. objModifyAssign(sizePos, fetchValidSizePos(sp, "lb"));
  271. emitChange();
  272. }
  273. function moveRightBottomPoint({ left, top }: PositionData) {
  274. const sp = {
  275. ...sizePos,
  276. ...getRightSize(left),
  277. ...getBottomSize(top),
  278. };
  279. objModifyAssign(sizePos, fetchValidSizePos(sp, "rb"));
  280. emitChange();
  281. }
  282. function movePointOver() {
  283. objModifyAssign(sizePosOrigin, sizePos);
  284. objModifyAssign(lastSizePos, sizePos);
  285. emit("resizeOver", sizePos);
  286. }
  287. function moveStart() {
  288. emit("onClick");
  289. }
  290. function moveElement({ left, top }: PositionData) {
  291. if (!props.move) return;
  292. const sp = {
  293. ...sizePos,
  294. ...{
  295. x: left + sizePosOrigin.x,
  296. y: top + sizePosOrigin.y,
  297. },
  298. };
  299. objModifyAssign(sizePos, fetchValidSizePos(sp, "move"));
  300. emitChange();
  301. }
  302. function moveElementOver({ left, top }: PositionData) {
  303. if (!props.move) return;
  304. moveElement({ left, top });
  305. objModifyAssign(sizePosOrigin, sizePos);
  306. objModifyAssign(lastSizePos, sizePos);
  307. emit("resizeOver", sizePos);
  308. }
  309. function emitChange() {
  310. emit("update:modelValue", sizePos);
  311. emit("change", sizePos);
  312. }
  313. onMounted(() => {
  314. initSize();
  315. });
  316. onBeforeMount(() => {
  317. initControlPoints();
  318. });
  319. </script>
  320. <style lang="less" scoped>
  321. .element-resize {
  322. position: static;
  323. z-index: auto;
  324. background: transparent;
  325. box-sizing: content-box;
  326. &-move {
  327. cursor: move;
  328. }
  329. &-init {
  330. > div:first-child {
  331. width: 100% !important;
  332. height: 100% !important;
  333. position: relative !important;
  334. top: 0 !important;
  335. left: 0 !important;
  336. overflow: hidden;
  337. }
  338. }
  339. .control-point {
  340. position: absolute;
  341. width: 12px;
  342. height: 12px;
  343. background: #f53f3f;
  344. z-index: 99;
  345. &-l {
  346. left: 0;
  347. top: 50%;
  348. width: 12px;
  349. height: 12px;
  350. margin-top: -6px;
  351. border-radius: 0;
  352. cursor: w-resize;
  353. }
  354. &-lt {
  355. left: 0;
  356. top: 0;
  357. cursor: nw-resize;
  358. }
  359. &-lb {
  360. left: 0;
  361. bottom: 0;
  362. cursor: sw-resize;
  363. }
  364. &-r {
  365. right: 0;
  366. top: 50%;
  367. margin-top: -6px;
  368. cursor: e-resize;
  369. }
  370. &-rt {
  371. right: 0;
  372. top: 0;
  373. cursor: ne-resize;
  374. }
  375. &-rb {
  376. right: 0;
  377. bottom: 0;
  378. cursor: se-resize;
  379. }
  380. &-t {
  381. left: 50%;
  382. top: 0;
  383. margin-left: -6px;
  384. cursor: n-resize;
  385. }
  386. &-b {
  387. left: 50%;
  388. bottom: 0;
  389. margin-left: -6px;
  390. cursor: s-resize;
  391. }
  392. }
  393. .control-line {
  394. position: absolute;
  395. z-index: 98;
  396. &-left {
  397. height: 100%;
  398. left: 0;
  399. top: 0;
  400. border-left: 4px solid #f53f3f;
  401. }
  402. &-right {
  403. height: 100%;
  404. right: 0;
  405. top: 0;
  406. border-left: 4px solid #f53f3f;
  407. }
  408. &-top {
  409. width: 100%;
  410. left: 0;
  411. top: 0;
  412. border-top: 4px solid #f53f3f;
  413. }
  414. &-bottom {
  415. width: 100%;
  416. left: 0;
  417. bottom: 0;
  418. border-top: 4px solid #f53f3f;
  419. }
  420. }
  421. &-compact {
  422. .control-line {
  423. &-left {
  424. left: 0;
  425. border-left: 1px dashed #bbb;
  426. }
  427. &-right {
  428. right: 0;
  429. border-left: 1px dashed #bbb;
  430. }
  431. &-top {
  432. top: 0;
  433. border-top: 1px dashed #bbb;
  434. }
  435. &-bottom {
  436. bottom: 0;
  437. border-top: 1px dashed #bbb;
  438. }
  439. }
  440. }
  441. }
  442. </style>