guide-drag.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. // 弹窗拖动
  2. import { ref, Ref, watch, nextTick, computed, reactive } from 'vue';
  3. type posType = {
  4. top: number;
  5. left: number;
  6. };
  7. type directionType =
  8. | 'TOP'
  9. | 'RIGHT'
  10. | 'BOTTOM'
  11. | 'LEFT'
  12. | 'TOP_RIGHT'
  13. | 'BOTTOM_RIGHT'
  14. | 'BOTTOM_LEFT'
  15. | 'TOP_LEFT';
  16. type baseSizeType = {
  17. /**
  18. * 允许拖动方向 上/上右/右/下右/下/下左/左/上左
  19. */
  20. resizeDirection: boolean[];
  21. layoutTopHeight: number;
  22. windowHeight: number;
  23. windowWidth: number;
  24. // 窗口模式的尺寸
  25. winWidth: number;
  26. winHeight: number;
  27. winMinWidth: number;
  28. minWidth: number;
  29. minHeight: number;
  30. maxHeight: number;
  31. maxWidth: number;
  32. borderRadius: number;
  33. transformX: number;
  34. transformY: number;
  35. width: number;
  36. height: number;
  37. };
  38. /***
  39. * 初始化默认弹窗位置
  40. */
  41. const initPos = {
  42. right: 14,
  43. top: 60
  44. };
  45. const getSizeToUnit = (num: number, unit = 'px') => {
  46. return num > 0 ? num + unit : num + '';
  47. };
  48. /**
  49. * @params classList 可拖动地方的class值,也为唯一值
  50. * @params boxClass 容器class值必须为唯一值
  51. * @params dragShow 弹窗是否显示
  52. * @params initSize 默认尺寸
  53. */
  54. export default function useDrag(
  55. classList: string[],
  56. boxClass: string,
  57. dragShow: Ref<boolean>,
  58. initSize?: any
  59. ) {
  60. const windowInfo = reactive({
  61. // 小窗口 侧边大窗口
  62. currentType: 'SMALL' as 'SMALL' | 'LARGE',
  63. // 弹窗,还是还原
  64. windowType: 'SMALL' as 'SMALL' | 'LARGE',
  65. // showScreen: false, // 是否全屏显示
  66. showType: 'MENU' as 'MENU' | 'CONTENT' // 当前显示哪一部分 - 如果是全屏显示则无效
  67. });
  68. const pos = ref<posType>({
  69. top: -1, // -1 为初始值 代表没有缓存 默认居中
  70. left: -1
  71. });
  72. watch(
  73. () => windowInfo.windowType,
  74. () => {
  75. if (windowInfo.windowType === 'LARGE') {
  76. baseSize.resizeDirection = [
  77. true,
  78. true,
  79. true,
  80. true,
  81. true,
  82. true,
  83. true,
  84. true
  85. ];
  86. } else if (windowInfo.windowType === 'SMALL') {
  87. baseSize.resizeDirection = [
  88. true,
  89. false,
  90. false,
  91. false,
  92. true,
  93. false,
  94. false,
  95. false
  96. ];
  97. }
  98. const dragDirectionPoints = document.querySelectorAll(
  99. `.${boxClass} .dragDirectionPoint`
  100. );
  101. dragDirectionPoints.forEach((element: any, index) => {
  102. if (baseSize.resizeDirection[index]) {
  103. element.style.pointerEvents = 'all';
  104. } else {
  105. element.style.pointerEvents = 'none';
  106. }
  107. });
  108. }
  109. );
  110. const styleDrag = computed(() => {
  111. return {
  112. ...dragStyles,
  113. width: getSizeToUnit(baseSize.width),
  114. height: getSizeToUnit(baseSize.height),
  115. transform: `translate(${baseSize.transformX}px, ${baseSize.transformY}px)`
  116. };
  117. });
  118. const baseSize = reactive<baseSizeType>({
  119. resizeDirection: [true, false, false, false, true, false, false, false],
  120. layoutTopHeight: 0,
  121. windowHeight: window.innerHeight,
  122. windowWidth: window.innerWidth,
  123. // 窗口模式的尺寸
  124. winWidth: 1010,
  125. winHeight: 650,
  126. winMinWidth: 800,
  127. minWidth: 400,
  128. minHeight: 640,
  129. maxHeight: window.innerHeight,
  130. maxWidth: window.innerWidth > 1024 ? 1024 : window.innerWidth,
  131. borderRadius: 12,
  132. transformX: window.innerWidth - 400 - initPos.right,
  133. transformY: (window.innerHeight - 640) / 2,
  134. height: initSize?.height || 640,
  135. width: initSize?.width || 400
  136. });
  137. const dragStyles = reactive({
  138. maxHeight: getSizeToUnit(baseSize.maxHeight),
  139. minWidth: getSizeToUnit(baseSize.minWidth),
  140. minHeight: getSizeToUnit(baseSize.minHeight),
  141. borderRadius: '0px'
  142. });
  143. nextTick(() => {
  144. const layoutTopHeight =
  145. document.querySelector('.layoutTop')?.clientHeight || 0;
  146. baseSize.layoutTopHeight = Math.ceil(layoutTopHeight);
  147. baseSize.windowHeight = window.innerHeight - layoutTopHeight;
  148. baseSize.maxHeight = window.innerHeight - layoutTopHeight;
  149. const translateY = (baseSize.windowHeight - baseSize.minHeight) / 2;
  150. baseSize.transformX =
  151. baseSize.windowWidth - baseSize.minWidth - initPos.right;
  152. baseSize.transformY = translateY;
  153. dragStyles.maxHeight = getSizeToUnit(baseSize.maxHeight);
  154. // 初始化定位
  155. if (initSize?.defaultPosition === 'center') {
  156. // alert(initSize.width)
  157. const transformX = (window.innerWidth - (initSize.width || 400)) / 2;
  158. const transformY = (baseSize.windowHeight - (initSize.height || 640)) / 2;
  159. console.log({ transformX, transformY });
  160. baseSize.transformX = transformX;
  161. baseSize.transformY = transformY;
  162. }
  163. });
  164. // watch(dragShow, () => {
  165. // if (dragShow.value) {
  166. // // 初始化pos值
  167. // // initPos();
  168. // // window.addEventListener('resize', refreshPos);
  169. // nextTick(() => {
  170. // const boxClassDom = document.querySelector(
  171. // `.${boxClass}`
  172. // ) as HTMLElement;
  173. // if (!boxClassDom) {
  174. // return;
  175. // }
  176. // console.log(boxClassDom, 'boxClassDom');
  177. // classList.map((className: string) => {
  178. // const classDom = document.querySelector(
  179. // `.${className}`
  180. // ) as HTMLElement;
  181. // if (classDom) {
  182. // classDom.style.cursor = 'move';
  183. // drag(classDom, boxClassDom, baseSize);
  184. // }
  185. // });
  186. // });
  187. // } else {
  188. // // window.removeEventListener('resize', refreshPos);
  189. // }
  190. // });
  191. nextTick(() => {
  192. const boxClassDom = document.querySelector(`.${boxClass}`) as HTMLElement;
  193. if (!boxClassDom) {
  194. return;
  195. }
  196. addReSizeDom(boxClassDom, baseSize.resizeDirection);
  197. classList.map((className: string) => {
  198. const classDom = document.querySelector(`.${className}`) as HTMLElement;
  199. if (classDom) {
  200. classDom.style.cursor = 'move';
  201. drag(classDom, boxClassDom, baseSize);
  202. }
  203. });
  204. });
  205. /**
  206. * 添加功能放大缩小操作DOM
  207. * @param parentElement {添加拖动父级元素}
  208. * @param direction {允许拖动的位置 上/上右/右/下右/下/下左/左/上左}
  209. */
  210. function addReSizeDom(
  211. parentElement: HTMLElement,
  212. direction: boolean[] = [
  213. true,
  214. false,
  215. false,
  216. false,
  217. true,
  218. false,
  219. false,
  220. false
  221. ]
  222. ) {
  223. function addResizeDirection(params: {
  224. width: string;
  225. height: string;
  226. direction: directionType;
  227. top?: string | any;
  228. right?: string | any;
  229. bottom?: string | any;
  230. left?: string | any;
  231. cursor: string;
  232. zIndex?: string;
  233. pointerEvents: string;
  234. }) {
  235. const dom = document.createElement('div');
  236. dom.className = 'dragDirectionPoint';
  237. dom.style.position = 'absolute';
  238. dom.style.userSelect = 'none';
  239. dom.style.width = params.width;
  240. dom.style.height = params.height;
  241. dom.style.left = params.left;
  242. dom.style.top = params.top;
  243. dom.style.bottom = params.bottom;
  244. dom.style.right = params.right;
  245. dom.style.zIndex = params.zIndex || '0';
  246. dom.style.cursor = params.cursor;
  247. dom.style.pointerEvents = params.pointerEvents;
  248. parentElement.appendChild(dom);
  249. drag(dom, parentElement, baseSize, 'RESIZE', params.direction);
  250. }
  251. // 上
  252. addResizeDirection({
  253. width: '100%',
  254. height: '10px',
  255. left: '0',
  256. top: '-5px',
  257. cursor: 'row-resize',
  258. direction: 'TOP',
  259. pointerEvents: direction[0] ? 'all' : 'none'
  260. });
  261. // 上右
  262. addResizeDirection({
  263. width: '20px',
  264. height: '20px',
  265. right: '-10px',
  266. top: '-10px',
  267. zIndex: '1',
  268. cursor: 'ne-resize',
  269. direction: 'TOP_RIGHT',
  270. pointerEvents: direction[1] ? 'all' : 'none'
  271. });
  272. // 右
  273. addResizeDirection({
  274. width: '10px',
  275. height: '100%',
  276. top: '0',
  277. right: '-5px',
  278. cursor: 'col-resize',
  279. direction: 'RIGHT',
  280. pointerEvents: direction[2] ? 'all' : 'none'
  281. });
  282. // 下右
  283. addResizeDirection({
  284. width: '20px',
  285. height: '20px',
  286. right: '-10px',
  287. bottom: '-10px',
  288. cursor: 'se-resize',
  289. zIndex: '1',
  290. direction: 'BOTTOM_RIGHT',
  291. pointerEvents: direction[3] ? 'all' : 'none'
  292. });
  293. // 下
  294. addResizeDirection({
  295. width: '100%',
  296. height: '10px',
  297. left: '0',
  298. bottom: '-5px',
  299. cursor: 'row-resize',
  300. direction: 'BOTTOM',
  301. pointerEvents: direction[4] ? 'all' : 'none'
  302. });
  303. // 下左
  304. addResizeDirection({
  305. width: '20px',
  306. height: '20px',
  307. left: '-10px',
  308. bottom: '-10px',
  309. cursor: 'sw-resize',
  310. zIndex: '1',
  311. direction: 'BOTTOM_LEFT',
  312. pointerEvents: direction[5] ? 'all' : 'none'
  313. });
  314. // 左
  315. addResizeDirection({
  316. width: '10px',
  317. height: '100%',
  318. top: '0',
  319. left: '-5px',
  320. cursor: 'col-resize',
  321. direction: 'LEFT',
  322. pointerEvents: direction[6] ? 'all' : 'none'
  323. });
  324. // 上左
  325. addResizeDirection({
  326. width: '20px',
  327. height: '20px',
  328. left: '-10px',
  329. top: '-10px',
  330. cursor: 'nw-resize',
  331. zIndex: '1',
  332. direction: 'TOP_LEFT',
  333. pointerEvents: direction[7] ? 'all' : 'none'
  334. });
  335. }
  336. function refreshPos() {
  337. if (pos.value.left === -1 && pos.value.top === -1) {
  338. return;
  339. }
  340. const boxClassDom = document.querySelector(`.${boxClass}`) as HTMLElement;
  341. if (!boxClassDom) return;
  342. const parentElementRect = boxClassDom.getBoundingClientRect();
  343. const clientWidth = document.documentElement.clientWidth;
  344. const clientHeight = document.documentElement.clientHeight;
  345. const { top, left } = pos.value;
  346. const maxLeft = clientWidth - parentElementRect.width;
  347. const maxTop = clientHeight - parentElementRect.height;
  348. let moveX = left;
  349. let moveY = top;
  350. const minLeft = 0;
  351. const minTop = 0;
  352. moveX = moveX < minLeft ? minLeft : moveX > maxLeft ? maxLeft : moveX;
  353. moveY = moveY < minTop ? minTop : moveY > maxTop ? maxTop : moveY;
  354. pos.value = {
  355. top: moveY,
  356. left: moveX
  357. };
  358. }
  359. /** 切换窗口 */
  360. function onScreen() {
  361. if (windowInfo.windowType === 'SMALL') {
  362. windowInfo.windowType = 'LARGE';
  363. baseSize.transformX = (baseSize.windowWidth - baseSize.winWidth) / 2;
  364. baseSize.transformY =
  365. (baseSize.windowHeight - baseSize.winHeight) / 2 -
  366. baseSize.layoutTopHeight / 2;
  367. baseSize.width = baseSize.winWidth;
  368. baseSize.height = baseSize.winHeight;
  369. dragStyles.borderRadius = getSizeToUnit(baseSize.borderRadius);
  370. } else if (windowInfo.windowType === 'LARGE') {
  371. windowInfo.windowType = 'SMALL';
  372. const translateY = (baseSize.windowHeight - baseSize.minHeight) / 2;
  373. baseSize.transformX =
  374. baseSize.windowWidth - baseSize.minWidth - initPos.right;
  375. baseSize.transformY =
  376. translateY > initPos.top
  377. ? translateY + (translateY - initPos.top)
  378. : translateY;
  379. baseSize.width = baseSize.minWidth;
  380. baseSize.height = baseSize.minHeight;
  381. dragStyles.borderRadius = '0';
  382. }
  383. }
  384. /** 格式化尺寸 */
  385. function onResize() {
  386. if (windowInfo.currentType === 'SMALL') {
  387. windowInfo.currentType = 'LARGE';
  388. windowInfo.windowType = 'SMALL';
  389. baseSize.transformX = baseSize.windowWidth - baseSize.minWidth;
  390. baseSize.transformY = 0;
  391. baseSize.width = baseSize.minWidth;
  392. baseSize.height = baseSize.maxHeight;
  393. dragStyles.borderRadius = '0';
  394. } else if (windowInfo.currentType === 'LARGE') {
  395. windowInfo.currentType = 'SMALL';
  396. windowInfo.windowType = 'SMALL';
  397. baseSize.transformX =
  398. baseSize.windowWidth - baseSize.minWidth - initPos.right;
  399. baseSize.transformY =
  400. baseSize.windowHeight - baseSize.minHeight - initPos.top;
  401. baseSize.width = baseSize.minWidth;
  402. baseSize.height = baseSize.minHeight;
  403. dragStyles.borderRadius = '0';
  404. }
  405. }
  406. return {
  407. pos,
  408. baseSize,
  409. windowInfo,
  410. styleDrag,
  411. onScreen,
  412. onResize
  413. };
  414. }
  415. // 拖动
  416. function drag(
  417. el: HTMLElement,
  418. parentElement: HTMLElement,
  419. baseSize: baseSizeType,
  420. type = 'MOVE' as 'MOVE' | 'RESIZE',
  421. direction?: directionType
  422. ) {
  423. function onDown(e: MouseEvent | TouchEvent) {
  424. const isTouchEv = isTouchEvent(e);
  425. const event = isTouchEv ? e.touches[0] : e;
  426. const parentElementRect = parentElement.getBoundingClientRect();
  427. const downX = event.clientX;
  428. const downY = event.clientY;
  429. const clientWidth = document.documentElement.clientWidth;
  430. const clientHeight = document.documentElement.clientHeight;
  431. const maxLeft = clientWidth - parentElementRect.width;
  432. const maxTop =
  433. clientHeight - parentElementRect.height - baseSize.layoutTopHeight;
  434. const maxResizeLeft =
  435. clientWidth -
  436. baseSize.winMinWidth -
  437. (clientWidth - parentElementRect.right);
  438. const maxResizeTop =
  439. clientHeight - baseSize.minHeight - baseSize.layoutTopHeight;
  440. const minLeft = 0;
  441. const minTop = 0;
  442. const baseHeight = JSON.parse(JSON.stringify(baseSize.height));
  443. const baseWidth = JSON.parse(JSON.stringify(baseSize.width));
  444. function onTop(moveY: number) {
  445. const maxSuffix =
  446. parentElementRect.bottom -
  447. baseSize.minHeight -
  448. baseSize.layoutTopHeight;
  449. moveY = moveY > maxSuffix ? maxSuffix : moveY;
  450. const suffix = baseSize.transformY - moveY;
  451. if (suffix > 0 || baseSize.height > baseSize.minHeight) {
  452. baseSize.transformY = moveY;
  453. baseSize.height = baseSize.height + suffix;
  454. }
  455. }
  456. function onRight(moveX: number) {
  457. // moveX =
  458. // moveX < minLeft
  459. // ? minLeft
  460. // : moveX > maxResizeLeft
  461. // ? maxResizeLeft
  462. // : moveX;
  463. const suffix = Math.ceil(
  464. baseWidth + moveX - (baseSize.width + baseSize.transformX)
  465. );
  466. if (suffix > 0 || baseSize.width > baseSize.winMinWidth) {
  467. baseSize.width =
  468. baseSize.width + suffix >= baseSize.maxWidth
  469. ? baseSize.maxWidth
  470. : baseSize.width + suffix;
  471. }
  472. }
  473. function onBottom(moveY: number) {
  474. if (baseSize.maxHeight > baseSize.height) {
  475. const suffix = Math.ceil(
  476. baseHeight + moveY - (baseSize.height + baseSize.transformY)
  477. );
  478. baseSize.height = baseSize.height + suffix;
  479. }
  480. }
  481. function onLeft(moveX: number) {
  482. moveX =
  483. moveX < minLeft
  484. ? minLeft
  485. : moveX > maxResizeLeft
  486. ? maxResizeLeft
  487. : moveX;
  488. const suffix = baseSize.transformX - moveX;
  489. if (suffix > 0 || baseSize.width > baseSize.winMinWidth) {
  490. if (baseSize.width + suffix <= baseSize.maxWidth) {
  491. baseSize.transformX = moveX;
  492. baseSize.width = baseSize.width + suffix;
  493. }
  494. }
  495. }
  496. function onMove(e: MouseEvent | TouchEvent) {
  497. const event = isTouchEvent(e) ? e.touches[0] : e;
  498. if (type === 'MOVE') {
  499. let moveX = parentElementRect.left + (event.clientX - downX);
  500. let moveY =
  501. parentElementRect.top -
  502. baseSize.layoutTopHeight +
  503. (event.clientY - downY);
  504. moveX = moveX < minLeft ? minLeft : moveX > maxLeft ? maxLeft : moveX;
  505. moveY = moveY < minTop ? minTop : moveY > maxTop ? maxTop : moveY;
  506. // 移动
  507. baseSize.transformY = moveY;
  508. baseSize.transformX = moveX;
  509. } else if (type === 'RESIZE') {
  510. let moveY =
  511. parentElementRect.top -
  512. baseSize.layoutTopHeight +
  513. (event.clientY - downY);
  514. moveY =
  515. moveY < minTop ? minTop : moveY > maxResizeTop ? maxResizeTop : moveY;
  516. const moveX = parentElementRect.left + (event.clientX - downX);
  517. // 拖动
  518. if (direction === 'TOP') {
  519. onTop(moveY);
  520. } else if (direction === 'RIGHT') {
  521. onRight(moveX);
  522. } else if (direction === 'BOTTOM') {
  523. onBottom(moveY);
  524. } else if (direction === 'LEFT') {
  525. onLeft(moveX);
  526. } else if (direction === 'TOP_RIGHT') {
  527. onTop(moveY);
  528. onRight(moveX);
  529. } else if (direction === 'BOTTOM_RIGHT') {
  530. onBottom(moveY);
  531. onRight(moveX);
  532. } else if (direction === 'BOTTOM_LEFT') {
  533. onBottom(moveY);
  534. onLeft(moveX);
  535. } else if (direction === 'TOP_LEFT') {
  536. onTop(moveY);
  537. onLeft(moveX);
  538. }
  539. }
  540. }
  541. function onUp() {
  542. document.removeEventListener(
  543. isTouchEv ? 'touchmove' : 'mousemove',
  544. onMove
  545. );
  546. document.removeEventListener(isTouchEv ? 'touchend' : 'mouseup', onUp);
  547. }
  548. document.addEventListener(isTouchEv ? 'touchmove' : 'mousemove', onMove);
  549. document.addEventListener(isTouchEv ? 'touchend' : 'mouseup', onUp);
  550. }
  551. el.addEventListener('mousedown', onDown);
  552. el.addEventListener('touchstart', onDown);
  553. }
  554. function isTouchEvent(e: MouseEvent | TouchEvent): e is TouchEvent {
  555. return window.TouchEvent && e instanceof window.TouchEvent;
  556. }