guide-drag.ts 18 KB

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