guide-drag.ts 17 KB

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