resize.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122
  1. <script lang="ts" setup>
  2. /**
  3. * This components is refactored from vue-drag-resize: https://github.com/kirillmurashov/vue-drag-resize
  4. */
  5. import {
  6. computed,
  7. getCurrentInstance,
  8. nextTick,
  9. onBeforeUnmount,
  10. onMounted,
  11. ref,
  12. toRefs,
  13. watch,
  14. } from 'vue';
  15. const props = defineProps({
  16. stickSize: {
  17. type: Number,
  18. default: 8,
  19. },
  20. parentScaleX: {
  21. type: Number,
  22. default: 1,
  23. },
  24. parentScaleY: {
  25. type: Number,
  26. default: 1,
  27. },
  28. isActive: {
  29. type: Boolean,
  30. default: false,
  31. },
  32. preventActiveBehavior: {
  33. type: Boolean,
  34. default: false,
  35. },
  36. isDraggable: {
  37. type: Boolean,
  38. default: true,
  39. },
  40. isResizable: {
  41. type: Boolean,
  42. default: true,
  43. },
  44. aspectRatio: {
  45. type: Boolean,
  46. default: false,
  47. },
  48. parentLimitation: {
  49. type: Boolean,
  50. default: false,
  51. },
  52. snapToGrid: {
  53. type: Boolean,
  54. default: false,
  55. },
  56. gridX: {
  57. type: Number,
  58. default: 50,
  59. validator(val: number) {
  60. return val >= 0;
  61. },
  62. },
  63. gridY: {
  64. type: Number,
  65. default: 50,
  66. validator(val: number) {
  67. return val >= 0;
  68. },
  69. },
  70. parentW: {
  71. type: Number,
  72. default: 0,
  73. validator(val: number) {
  74. return val >= 0;
  75. },
  76. },
  77. parentH: {
  78. type: Number,
  79. default: 0,
  80. validator(val: number) {
  81. return val >= 0;
  82. },
  83. },
  84. w: {
  85. type: [String, Number],
  86. default: 200,
  87. validator(val: number) {
  88. return typeof val === 'string' ? val === 'auto' : val >= 0;
  89. },
  90. },
  91. h: {
  92. type: [String, Number],
  93. default: 200,
  94. validator(val: number) {
  95. return typeof val === 'string' ? val === 'auto' : val >= 0;
  96. },
  97. },
  98. minw: {
  99. type: Number,
  100. default: 50,
  101. validator(val: number) {
  102. return val >= 0;
  103. },
  104. },
  105. minh: {
  106. type: Number,
  107. default: 50,
  108. validator(val: number) {
  109. return val >= 0;
  110. },
  111. },
  112. x: {
  113. type: Number,
  114. default: 0,
  115. validator(val: number) {
  116. return typeof val === 'number';
  117. },
  118. },
  119. y: {
  120. type: Number,
  121. default: 0,
  122. validator(val: number) {
  123. return typeof val === 'number';
  124. },
  125. },
  126. z: {
  127. type: [String, Number],
  128. default: 'auto',
  129. validator(val: number) {
  130. return typeof val === 'string' ? val === 'auto' : val >= 0;
  131. },
  132. },
  133. dragHandle: {
  134. type: String,
  135. default: null,
  136. },
  137. dragCancel: {
  138. type: String,
  139. default: null,
  140. },
  141. sticks: {
  142. type: Array<'bl' | 'bm' | 'br' | 'ml' | 'mr' | 'tl' | 'tm' | 'tr'>,
  143. default() {
  144. return ['tl', 'tm', 'tr', 'mr', 'br', 'bm', 'bl', 'ml'];
  145. },
  146. },
  147. axis: {
  148. type: String,
  149. default: 'both',
  150. validator(val: string) {
  151. return ['both', 'none', 'x', 'y'].includes(val);
  152. },
  153. },
  154. contentClass: {
  155. type: String,
  156. required: false,
  157. default: '',
  158. },
  159. });
  160. const emit = defineEmits([
  161. 'clicked',
  162. 'dragging',
  163. 'dragstop',
  164. 'resizing',
  165. 'resizestop',
  166. 'activated',
  167. 'deactivated',
  168. ]);
  169. const styleMapping = {
  170. y: {
  171. t: 'top',
  172. m: 'marginTop',
  173. b: 'bottom',
  174. },
  175. x: {
  176. l: 'left',
  177. m: 'marginLeft',
  178. r: 'right',
  179. },
  180. };
  181. function addEvents(events: Map<string, (...args: any[]) => void>) {
  182. events.forEach((cb, eventName) => {
  183. document.documentElement.addEventListener(eventName, cb);
  184. });
  185. }
  186. function removeEvents(events: Map<string, (...args: any[]) => void>) {
  187. events.forEach((cb, eventName) => {
  188. document.documentElement.removeEventListener(eventName, cb);
  189. });
  190. }
  191. const {
  192. stickSize,
  193. parentScaleX,
  194. parentScaleY,
  195. isActive,
  196. preventActiveBehavior,
  197. isDraggable,
  198. isResizable,
  199. aspectRatio,
  200. parentLimitation,
  201. snapToGrid,
  202. gridX,
  203. gridY,
  204. parentW,
  205. parentH,
  206. w,
  207. h,
  208. minw,
  209. minh,
  210. x,
  211. y,
  212. z,
  213. dragHandle,
  214. dragCancel,
  215. sticks,
  216. axis,
  217. contentClass,
  218. } = toRefs(props);
  219. // states
  220. const active = ref(false);
  221. const zIndex = ref<null | number>(null);
  222. const parentWidth = ref<null | number>(null);
  223. const parentHeight = ref<null | number>(null);
  224. const left = ref<null | number>(null);
  225. const top = ref<null | number>(null);
  226. const right = ref<null | number>(null);
  227. const bottom = ref<null | number>(null);
  228. const aspectFactor = ref<null | number>(null);
  229. // state end
  230. const stickDrag = ref(false);
  231. const bodyDrag = ref(false);
  232. const dimensionsBeforeMove = ref({
  233. pointerX: 0,
  234. pointerY: 0,
  235. x: 0,
  236. y: 0,
  237. w: 0,
  238. h: 0,
  239. top: 0,
  240. right: 0,
  241. bottom: 0,
  242. left: 0,
  243. width: 0,
  244. height: 0,
  245. });
  246. const limits = ref({
  247. left: { min: null as null | number, max: null as null | number },
  248. right: { min: null as null | number, max: null as null | number },
  249. top: { min: null as null | number, max: null as null | number },
  250. bottom: { min: null as null | number, max: null as null | number },
  251. });
  252. const currentStick = ref<null | string>(null);
  253. const parentElement = ref<HTMLElement | null>(null);
  254. const width = computed(() => parentWidth.value! - left.value! - right.value!);
  255. const height = computed(() => parentHeight.value! - top.value! - bottom.value!);
  256. const rect = computed(() => ({
  257. left: Math.round(left.value!),
  258. top: Math.round(top.value!),
  259. width: Math.round(width.value),
  260. height: Math.round(height.value),
  261. }));
  262. const saveDimensionsBeforeMove = ({
  263. pointerX,
  264. pointerY,
  265. }: {
  266. pointerX: number;
  267. pointerY: number;
  268. }) => {
  269. dimensionsBeforeMove.value.pointerX = pointerX;
  270. dimensionsBeforeMove.value.pointerY = pointerY;
  271. dimensionsBeforeMove.value.left = left.value as number;
  272. dimensionsBeforeMove.value.right = right.value as number;
  273. dimensionsBeforeMove.value.top = top.value as number;
  274. dimensionsBeforeMove.value.bottom = bottom.value as number;
  275. dimensionsBeforeMove.value.width = width.value as number;
  276. dimensionsBeforeMove.value.height = height.value as number;
  277. aspectFactor.value = width.value / height.value;
  278. };
  279. const sideCorrectionByLimit = (
  280. limit: { max: number; min: number },
  281. current: number,
  282. ) => {
  283. let value = current;
  284. if (limit.min !== null && current < limit.min) {
  285. value = limit.min;
  286. } else if (limit.max !== null && limit.max < current) {
  287. value = limit.max;
  288. }
  289. return value;
  290. };
  291. const rectCorrectionByLimit = (rect: {
  292. newBottom: number;
  293. newLeft: number;
  294. newRight: number;
  295. newTop: number;
  296. }) => {
  297. // const { limits } = this;
  298. let { newRight, newLeft, newBottom, newTop } = rect;
  299. type RectRange = {
  300. max: number;
  301. min: number;
  302. };
  303. newLeft = sideCorrectionByLimit(limits.value.left as RectRange, newLeft);
  304. newRight = sideCorrectionByLimit(limits.value.right as RectRange, newRight);
  305. newTop = sideCorrectionByLimit(limits.value.top as RectRange, newTop);
  306. newBottom = sideCorrectionByLimit(
  307. limits.value.bottom as RectRange,
  308. newBottom,
  309. );
  310. return {
  311. newLeft,
  312. newRight,
  313. newTop,
  314. newBottom,
  315. };
  316. };
  317. const rectCorrectionByAspectRatio = (rect: {
  318. newBottom: number;
  319. newLeft: number;
  320. newRight: number;
  321. newTop: number;
  322. }) => {
  323. let { newLeft, newRight, newTop, newBottom } = rect;
  324. // const { parentWidth, parentHeight, currentStick, aspectFactor, dimensionsBeforeMove } = this;
  325. let newWidth = parentWidth.value! - newLeft - newRight;
  326. let newHeight = parentHeight.value! - newTop - newBottom;
  327. if (currentStick.value![1] === 'm') {
  328. const deltaHeight = newHeight - dimensionsBeforeMove.value.height;
  329. newLeft -= (deltaHeight * aspectFactor.value!) / 2;
  330. newRight -= (deltaHeight * aspectFactor.value!) / 2;
  331. } else if (currentStick.value![0] === 'm') {
  332. const deltaWidth = newWidth - dimensionsBeforeMove.value.width;
  333. newTop -= deltaWidth / aspectFactor.value! / 2;
  334. newBottom -= deltaWidth / aspectFactor.value! / 2;
  335. } else if (newWidth / newHeight > aspectFactor.value!) {
  336. newWidth = aspectFactor.value! * newHeight;
  337. if (currentStick.value![1] === 'l') {
  338. newLeft = parentWidth.value! - newRight - newWidth;
  339. } else {
  340. newRight = parentWidth.value! - newLeft - newWidth;
  341. }
  342. } else {
  343. newHeight = newWidth / aspectFactor.value!;
  344. if (currentStick.value![0] === 't') {
  345. newTop = parentHeight.value! - newBottom - newHeight;
  346. } else {
  347. newBottom = parentHeight.value! - newTop - newHeight;
  348. }
  349. }
  350. return { newLeft, newRight, newTop, newBottom };
  351. };
  352. const stickMove = (delta: { x: number; y: number }) => {
  353. let newTop = dimensionsBeforeMove.value.top;
  354. let newBottom = dimensionsBeforeMove.value.bottom;
  355. let newLeft = dimensionsBeforeMove.value.left;
  356. let newRight = dimensionsBeforeMove.value.right;
  357. switch (currentStick.value![0]) {
  358. case 'b': {
  359. newBottom = dimensionsBeforeMove.value.bottom + delta.y;
  360. if (snapToGrid.value) {
  361. newBottom =
  362. (parentHeight.value as number) -
  363. Math.round(
  364. ((parentHeight.value as number) - newBottom) / gridY.value,
  365. ) *
  366. gridY.value;
  367. }
  368. break;
  369. }
  370. case 't': {
  371. newTop = dimensionsBeforeMove.value.top - delta.y;
  372. if (snapToGrid.value) {
  373. newTop = Math.round(newTop / gridY.value) * gridY.value;
  374. }
  375. break;
  376. }
  377. default: {
  378. break;
  379. }
  380. }
  381. switch (currentStick.value![1]) {
  382. case 'l': {
  383. newLeft = dimensionsBeforeMove.value.left - delta.x;
  384. if (snapToGrid.value) {
  385. newLeft = Math.round(newLeft / gridX.value) * gridX.value;
  386. }
  387. break;
  388. }
  389. case 'r': {
  390. newRight = dimensionsBeforeMove.value.right + delta.x;
  391. if (snapToGrid.value) {
  392. newRight =
  393. (parentWidth.value as number) -
  394. Math.round(((parentWidth.value as number) - newRight) / gridX.value) *
  395. gridX.value;
  396. }
  397. break;
  398. }
  399. default: {
  400. break;
  401. }
  402. }
  403. ({ newLeft, newRight, newTop, newBottom } = rectCorrectionByLimit({
  404. newLeft,
  405. newRight,
  406. newTop,
  407. newBottom,
  408. }));
  409. if (aspectRatio.value) {
  410. ({ newLeft, newRight, newTop, newBottom } = rectCorrectionByAspectRatio({
  411. newLeft,
  412. newRight,
  413. newTop,
  414. newBottom,
  415. }));
  416. }
  417. left.value = newLeft;
  418. right.value = newRight;
  419. top.value = newTop;
  420. bottom.value = newBottom;
  421. emit('resizing', rect.value);
  422. };
  423. const stickUp = () => {
  424. stickDrag.value = false;
  425. // dimensionsBeforeMove.value = {
  426. // pointerX: 0,
  427. // pointerY: 0,
  428. // x: 0,
  429. // y: 0,
  430. // w: 0,
  431. // h: 0,
  432. // };
  433. Object.assign(dimensionsBeforeMove.value, {
  434. pointerX: 0,
  435. pointerY: 0,
  436. x: 0,
  437. y: 0,
  438. w: 0,
  439. h: 0,
  440. });
  441. limits.value = {
  442. left: { min: null, max: null },
  443. right: { min: null, max: null },
  444. top: { min: null, max: null },
  445. bottom: { min: null, max: null },
  446. };
  447. emit('resizing', rect.value);
  448. emit('resizestop', rect.value);
  449. };
  450. const calcDragLimitation = () => {
  451. return {
  452. left: { min: 0, max: (parentWidth.value as number) - width.value },
  453. right: { min: 0, max: (parentWidth.value as number) - width.value },
  454. top: { min: 0, max: (parentHeight.value as number) - height.value },
  455. bottom: { min: 0, max: (parentHeight.value as number) - height.value },
  456. };
  457. };
  458. const calcResizeLimits = () => {
  459. // const { aspectFactor, width, height, bottom, top, left, right } = this;
  460. const parentLim = parentLimitation.value ? 0 : null;
  461. if (aspectRatio.value) {
  462. if (minw.value / minh.value > (aspectFactor.value as number)) {
  463. minh.value = minw.value / (aspectFactor.value as number);
  464. } else {
  465. minw.value = ((aspectFactor.value as number) * minh.value) as number;
  466. }
  467. }
  468. const limits = {
  469. left: {
  470. min: parentLim,
  471. max: (left.value as number) + (width.value - minw.value),
  472. },
  473. right: {
  474. min: parentLim,
  475. max: (right.value as number) + (width.value - minw.value),
  476. },
  477. top: {
  478. min: parentLim,
  479. max: (top.value as number) + (height.value - minh.value),
  480. },
  481. bottom: {
  482. min: parentLim,
  483. max: (bottom.value as number) + (height.value - minh.value),
  484. },
  485. };
  486. if (aspectRatio.value) {
  487. const aspectLimits = {
  488. left: {
  489. min:
  490. left.value! -
  491. Math.min(top.value!, bottom.value!) * aspectFactor.value! * 2,
  492. max:
  493. left.value! +
  494. ((height.value - minh.value!) / 2) * aspectFactor.value! * 2,
  495. },
  496. right: {
  497. min:
  498. right.value! -
  499. Math.min(top.value!, bottom.value!) * aspectFactor.value! * 2,
  500. max:
  501. right.value! +
  502. ((height.value - minh.value!) / 2) * aspectFactor.value! * 2,
  503. },
  504. top: {
  505. min:
  506. top.value! -
  507. (Math.min(left.value!, right.value!) / aspectFactor.value!) * 2,
  508. max:
  509. top.value! +
  510. ((width.value - minw.value) / 2 / aspectFactor.value!) * 2,
  511. },
  512. bottom: {
  513. min:
  514. bottom.value! -
  515. (Math.min(left.value!, right.value!) / aspectFactor.value!) * 2,
  516. max:
  517. bottom.value! +
  518. ((width.value - minw.value) / 2 / aspectFactor.value!) * 2,
  519. },
  520. };
  521. if (currentStick.value![0] === 'm') {
  522. limits.left = {
  523. min: Math.max(limits.left.min!, aspectLimits.left.min),
  524. max: Math.min(limits.left.max, aspectLimits.left.max),
  525. };
  526. limits.right = {
  527. min: Math.max(limits.right.min!, aspectLimits.right.min),
  528. max: Math.min(limits.right.max, aspectLimits.right.max),
  529. };
  530. } else if (currentStick.value![1] === 'm') {
  531. limits.top = {
  532. min: Math.max(limits.top.min!, aspectLimits.top.min),
  533. max: Math.min(limits.top.max, aspectLimits.top.max),
  534. };
  535. limits.bottom = {
  536. min: Math.max(limits.bottom.min!, aspectLimits.bottom.min),
  537. max: Math.min(limits.bottom.max, aspectLimits.bottom.max),
  538. };
  539. }
  540. }
  541. return limits;
  542. };
  543. const positionStyle = computed(() => ({
  544. top: `${top.value}px`,
  545. left: `${left.value}px`,
  546. zIndex: zIndex.value!,
  547. }));
  548. const sizeStyle = computed(() => ({
  549. width: w.value === 'auto' ? 'auto' : `${width.value}px`,
  550. height: h.value === 'auto' ? 'auto' : `${height.value}px`,
  551. }));
  552. const stickStyles = computed(() => (stick: string) => {
  553. const stickStyle = {
  554. width: `${stickSize.value / parentScaleX.value}px`,
  555. height: `${stickSize.value / parentScaleY.value}px`,
  556. };
  557. stickStyle[
  558. styleMapping.y[stick[0] as 'b' | 'm' | 't'] as 'height' | 'width'
  559. ] = `${stickSize.value / parentScaleX.value / -2}px`;
  560. stickStyle[
  561. styleMapping.x[stick[1] as 'l' | 'm' | 'r'] as 'height' | 'width'
  562. ] = `${stickSize.value / parentScaleX.value / -2}px`;
  563. return stickStyle;
  564. });
  565. const bodyMove = (delta: { x: number; y: number }) => {
  566. let newTop = dimensionsBeforeMove.value.top - delta.y;
  567. let newBottom = dimensionsBeforeMove.value.bottom + delta.y;
  568. let newLeft = dimensionsBeforeMove.value.left - delta.x;
  569. let newRight = dimensionsBeforeMove.value.right + delta.x;
  570. if (snapToGrid.value) {
  571. let alignTop = true;
  572. let alignLeft = true;
  573. let diffT = newTop - Math.floor(newTop / gridY.value) * gridY.value;
  574. let diffB =
  575. (parentHeight.value as number) -
  576. newBottom -
  577. Math.floor(((parentHeight.value as number) - newBottom) / gridY.value) *
  578. gridY.value;
  579. let diffL = newLeft - Math.floor(newLeft / gridX.value) * gridX.value;
  580. let diffR =
  581. (parentWidth.value as number) -
  582. newRight -
  583. Math.floor(((parentWidth.value as number) - newRight) / gridX.value) *
  584. gridX.value;
  585. if (diffT > gridY.value / 2) {
  586. diffT -= gridY.value;
  587. }
  588. if (diffB > gridY.value / 2) {
  589. diffB -= gridY.value;
  590. }
  591. if (diffL > gridX.value / 2) {
  592. diffL -= gridX.value;
  593. }
  594. if (diffR > gridX.value / 2) {
  595. diffR -= gridX.value;
  596. }
  597. if (Math.abs(diffB) < Math.abs(diffT)) {
  598. alignTop = false;
  599. }
  600. if (Math.abs(diffR) < Math.abs(diffL)) {
  601. alignLeft = false;
  602. }
  603. newTop -= alignTop ? diffT : diffB;
  604. newBottom = (parentHeight.value as number) - height.value - newTop;
  605. newLeft -= alignLeft ? diffL : diffR;
  606. newRight = (parentWidth.value as number) - width.value - newLeft;
  607. }
  608. ({
  609. newLeft: left.value,
  610. newRight: right.value,
  611. newTop: top.value,
  612. newBottom: bottom.value,
  613. } = rectCorrectionByLimit({ newLeft, newRight, newTop, newBottom }));
  614. emit('dragging', rect.value);
  615. };
  616. const bodyUp = () => {
  617. bodyDrag.value = false;
  618. emit('dragging', rect.value);
  619. emit('dragstop', rect.value);
  620. // dimensionsBeforeMove.value = { pointerX: 0, pointerY: 0, x: 0, y: 0, w: 0, h: 0 };
  621. Object.assign(dimensionsBeforeMove.value, {
  622. pointerX: 0,
  623. pointerY: 0,
  624. x: 0,
  625. y: 0,
  626. w: 0,
  627. h: 0,
  628. });
  629. limits.value = {
  630. left: { min: null, max: null },
  631. right: { min: null, max: null },
  632. top: { min: null, max: null },
  633. bottom: { min: null, max: null },
  634. };
  635. };
  636. const stickDown = (
  637. stick: string,
  638. ev: { pageX: any; pageY: any; touches?: any },
  639. force = false,
  640. ) => {
  641. if ((!isResizable.value || !active.value) && !force) {
  642. return;
  643. }
  644. stickDrag.value = true;
  645. const pointerX = ev.pageX === undefined ? ev.touches[0].pageX : ev.pageX;
  646. const pointerY = ev.pageY === undefined ? ev.touches[0].pageY : ev.pageY;
  647. saveDimensionsBeforeMove({ pointerX, pointerY });
  648. currentStick.value = stick;
  649. limits.value = calcResizeLimits();
  650. };
  651. const move = (ev: MouseEvent & TouchEvent) => {
  652. if (!stickDrag.value && !bodyDrag.value) {
  653. return;
  654. }
  655. ev.stopPropagation();
  656. // touches 兼容性代码
  657. const pageX = ev.pageX === undefined ? ev.touches![0]!.pageX : ev.pageX;
  658. const pageY = ev.pageY === undefined ? ev.touches![0]!.pageY : ev.pageY;
  659. const delta = {
  660. x: (dimensionsBeforeMove.value.pointerX - pageX) / parentScaleX.value,
  661. y: (dimensionsBeforeMove.value.pointerY - pageY) / parentScaleY.value,
  662. };
  663. if (stickDrag.value) {
  664. stickMove(delta);
  665. }
  666. if (bodyDrag.value) {
  667. switch (axis.value) {
  668. case 'none': {
  669. return;
  670. }
  671. case 'x': {
  672. delta.y = 0;
  673. break;
  674. }
  675. case 'y': {
  676. delta.x = 0;
  677. break;
  678. }
  679. // No default
  680. }
  681. bodyMove(delta);
  682. }
  683. };
  684. const up = () => {
  685. if (stickDrag.value) {
  686. stickUp();
  687. } else if (bodyDrag.value) {
  688. bodyUp();
  689. }
  690. };
  691. const deselect = () => {
  692. if (preventActiveBehavior.value) {
  693. return;
  694. }
  695. active.value = false;
  696. };
  697. const domEvents = ref(
  698. new Map([
  699. ['mousedown', deselect],
  700. ['mouseleave', up],
  701. ['mousemove', move],
  702. ['mouseup', up],
  703. ['touchcancel', up],
  704. ['touchend', up],
  705. ['touchmove', move],
  706. ['touchstart', up],
  707. ]),
  708. );
  709. const container = ref<HTMLDivElement>();
  710. onMounted(() => {
  711. const currentInstance = getCurrentInstance();
  712. const $el = currentInstance?.vnode.el as HTMLElement;
  713. parentElement.value = $el?.parentNode as HTMLElement;
  714. parentWidth.value = parentW.value ?? parentElement.value?.clientWidth;
  715. parentHeight.value = parentH.value ?? parentElement.value?.clientHeight;
  716. left.value = x.value;
  717. top.value = y.value;
  718. right.value = (parentWidth.value -
  719. (w.value === 'auto' ? container.value!.scrollWidth : (w.value as number)) -
  720. left.value) as number;
  721. bottom.value = (parentHeight.value -
  722. (h.value === 'auto' ? container.value!.scrollHeight : (h.value as number)) -
  723. top.value) as number;
  724. addEvents(domEvents.value);
  725. if (dragHandle.value) {
  726. [...($el?.querySelectorAll(dragHandle.value) || [])].forEach(
  727. (dragHandle) => {
  728. (dragHandle as HTMLElement).dataset.dragHandle = String(
  729. currentInstance?.uid,
  730. );
  731. },
  732. );
  733. }
  734. if (dragCancel.value) {
  735. [...($el?.querySelectorAll(dragCancel.value) || [])].forEach(
  736. (cancelHandle) => {
  737. (cancelHandle as HTMLElement).dataset.dragCancel = String(
  738. currentInstance?.uid,
  739. );
  740. },
  741. );
  742. }
  743. });
  744. onBeforeUnmount(() => {
  745. removeEvents(domEvents.value);
  746. });
  747. const bodyDown = (ev: MouseEvent & TouchEvent) => {
  748. const { target, button } = ev;
  749. if (!preventActiveBehavior.value) {
  750. active.value = true;
  751. }
  752. if (button && button !== 0) {
  753. return;
  754. }
  755. emit('clicked', ev);
  756. if (!active.value) {
  757. return;
  758. }
  759. if (
  760. dragHandle.value &&
  761. (target! as HTMLElement).dataset.dragHandle !==
  762. getCurrentInstance()?.uid.toString()
  763. ) {
  764. return;
  765. }
  766. if (
  767. dragCancel.value &&
  768. (target! as HTMLElement).dataset.dragCancel ===
  769. getCurrentInstance()?.uid.toString()
  770. ) {
  771. return;
  772. }
  773. if (ev.stopPropagation !== undefined) {
  774. ev.stopPropagation();
  775. }
  776. if (ev.preventDefault !== undefined) {
  777. ev.preventDefault();
  778. }
  779. if (isDraggable.value) {
  780. bodyDrag.value = true;
  781. }
  782. const pointerX = ev.pageX === undefined ? ev.touches[0]!.pageX : ev.pageX;
  783. const pointerY = ev.pageY === undefined ? ev.touches[0]!.pageY : ev.pageY;
  784. saveDimensionsBeforeMove({ pointerX, pointerY });
  785. if (parentLimitation.value) {
  786. limits.value = calcDragLimitation();
  787. }
  788. };
  789. watch(
  790. () => active.value,
  791. (isActive) => {
  792. if (isActive) {
  793. emit('activated');
  794. } else {
  795. emit('deactivated');
  796. }
  797. },
  798. );
  799. watch(
  800. () => isActive.value,
  801. (val) => {
  802. active.value = val;
  803. },
  804. { immediate: true },
  805. );
  806. watch(
  807. () => z.value,
  808. (val) => {
  809. if ((val as number) >= 0 || val === 'auto') {
  810. zIndex.value = val as number;
  811. }
  812. },
  813. { immediate: true },
  814. );
  815. watch(
  816. () => x.value,
  817. (newVal, oldVal) => {
  818. if (stickDrag.value || bodyDrag.value || newVal === left.value) {
  819. return;
  820. }
  821. const delta = oldVal - newVal;
  822. bodyDown({ pageX: left.value!, pageY: top.value! } as MouseEvent &
  823. TouchEvent);
  824. bodyMove({ x: delta, y: 0 });
  825. nextTick(() => {
  826. bodyUp();
  827. });
  828. },
  829. );
  830. watch(
  831. () => y.value,
  832. (newVal, oldVal) => {
  833. if (stickDrag.value || bodyDrag.value || newVal === top.value) {
  834. return;
  835. }
  836. const delta = oldVal - newVal;
  837. bodyDown({ pageX: left.value, pageY: top.value } as MouseEvent &
  838. TouchEvent);
  839. bodyMove({ x: 0, y: delta });
  840. nextTick(() => {
  841. bodyUp();
  842. });
  843. },
  844. );
  845. watch(
  846. () => w.value,
  847. (newVal, oldVal) => {
  848. if (stickDrag.value || bodyDrag.value || newVal === width.value) {
  849. return;
  850. }
  851. const stick = 'mr';
  852. const delta = (oldVal as number) - (newVal as number);
  853. stickDown(
  854. stick,
  855. { pageX: right.value, pageY: top.value! + height.value / 2 },
  856. true,
  857. );
  858. stickMove({ x: delta, y: 0 });
  859. nextTick(() => {
  860. stickUp();
  861. });
  862. },
  863. );
  864. watch(
  865. () => h.value,
  866. (newVal, oldVal) => {
  867. if (stickDrag.value || bodyDrag.value || newVal === height.value) {
  868. return;
  869. }
  870. const stick = 'bm';
  871. const delta = (oldVal as number) - (newVal as number);
  872. stickDown(
  873. stick,
  874. { pageX: left.value! + width.value / 2, pageY: bottom.value },
  875. true,
  876. );
  877. stickMove({ x: 0, y: delta });
  878. nextTick(() => {
  879. stickUp();
  880. });
  881. },
  882. );
  883. watch(
  884. () => parentW.value,
  885. (val) => {
  886. right.value = val - width.value - left.value!;
  887. parentWidth.value = val;
  888. },
  889. );
  890. watch(
  891. () => parentH.value,
  892. (val) => {
  893. bottom.value = val - height.value - top.value!;
  894. parentHeight.value = val;
  895. },
  896. );
  897. </script>
  898. <template>
  899. <div
  900. :class="`${active || isActive ? 'active' : 'inactive'} ${contentClass ? contentClass : ''}`"
  901. :style="positionStyle"
  902. class="resize"
  903. @mousedown="bodyDown($event as TouchEvent & MouseEvent)"
  904. @touchend="up"
  905. @touchstart="bodyDown($event as TouchEvent & MouseEvent)"
  906. >
  907. <div ref="container" :style="sizeStyle" class="content-container">
  908. <slot></slot>
  909. </div>
  910. <div
  911. v-for="(stick, index) of sticks"
  912. :key="index"
  913. :class="[`resize-stick-${stick}`, isResizable ? '' : 'not-resizable']"
  914. :style="stickStyles(stick)"
  915. class="resize-stick"
  916. @mousedown.stop.prevent="
  917. stickDown(stick, $event as TouchEvent & MouseEvent)
  918. "
  919. @touchstart.stop.prevent="
  920. stickDown(stick, $event as TouchEvent & MouseEvent)
  921. "
  922. ></div>
  923. </div>
  924. </template>
  925. <style lang="css" scoped>
  926. .resize {
  927. position: absolute;
  928. box-sizing: border-box;
  929. }
  930. .resize.active::before {
  931. position: absolute;
  932. top: 0;
  933. left: 0;
  934. box-sizing: border-box;
  935. width: 100%;
  936. height: 100%;
  937. content: '';
  938. outline: 1px dashed #d6d6d6;
  939. }
  940. .resize-stick {
  941. position: absolute;
  942. box-sizing: border-box;
  943. font-size: 1px;
  944. background: #fff;
  945. border: 1px solid #6c6c6c;
  946. box-shadow: 0 0 2px #bbb;
  947. }
  948. .inactive .resize-stick {
  949. display: none;
  950. }
  951. .resize-stick-tl,
  952. .resize-stick-br {
  953. cursor: nwse-resize;
  954. }
  955. .resize-stick-tm,
  956. .resize-stick-bm {
  957. left: 50%;
  958. cursor: ns-resize;
  959. }
  960. .resize-stick-tr,
  961. .resize-stick-bl {
  962. cursor: nesw-resize;
  963. }
  964. .resize-stick-ml,
  965. .resize-stick-mr {
  966. top: 50%;
  967. cursor: ew-resize;
  968. }
  969. .resize-stick.not-resizable {
  970. display: none;
  971. }
  972. .content-container {
  973. position: relative;
  974. display: block;
  975. }
  976. </style>