useHand.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. import {mathUtil} from "@/graphic/Util/MathUtil";
  2. import {computed, onUnmounted, ref, Ref, watchEffect} from "vue";
  3. import {getPostionByTarget} from "@/components/base/utils";
  4. // @ts-ignore
  5. import matruces from '@/utils/matruces'
  6. type Point = { x: number, y: number }
  7. type PointStore = [Point] | [Point, Point]
  8. enum Mode {
  9. move,
  10. scale,
  11. angle
  12. }
  13. export const HandMode = {
  14. MoveAndScale: [Mode.move, Mode.scale],
  15. AngleAndScale: [Mode.angle, Mode.scale],
  16. Move: [Mode.move],
  17. Scale: [Mode.scale],
  18. Angle: [Mode.angle]
  19. }
  20. export const useHand = (
  21. targetRef: Ref<HTMLElement>,
  22. mode: Mode[],
  23. callback = () => {},
  24. oldMatrix = matruces.translateMatrix(0,0,0)
  25. ) => {
  26. const parent = document.documentElement
  27. const matrix = ref(oldMatrix)
  28. let translate: Point;
  29. let scale: {center: Point, scale: number}
  30. let angle: number
  31. let startPos: PointStore
  32. let currentPos: PointStore
  33. const getScrollPos = (dom: HTMLElement) => {
  34. let x = 0, y = 0;
  35. while (dom && dom !== parent) {
  36. x += dom.scrollLeft
  37. y += dom.scrollTop
  38. dom = dom.offsetParent as HTMLElement
  39. }
  40. return {x, y}
  41. }
  42. const getPointByEvent = (ev: TouchEvent | MouseEvent): PointStore => {
  43. if (ev instanceof TouchEvent) {
  44. const point1 = {
  45. x: ev.touches[0].pageX,
  46. y: ev.touches[0].pageY
  47. }
  48. return ev.touches.length > 1
  49. ? [
  50. point1,
  51. {
  52. x: ev.touches[1].pageX,
  53. y: ev.touches[1].pageY
  54. }
  55. ]
  56. : [point1]
  57. } else {
  58. return [{ x: ev.pageX, y: ev.pageY }]
  59. }
  60. }
  61. let targetPosition, pageCenter, domCenter, pageStart;
  62. const start = (ev: TouchEvent | MouseEvent) => {
  63. const scrollPos = getScrollPos(targetRef.value)
  64. startPos = getPointByEvent(ev)
  65. targetPosition = getPostionByTarget(targetRef.value, parent)
  66. domCenter = {
  67. x: targetRef.value.offsetWidth / 2,
  68. y: targetRef.value.offsetHeight / 2
  69. }
  70. pageStart = {
  71. x: targetPosition.x - scrollPos.x,
  72. y: targetPosition.y - scrollPos.y
  73. }
  74. pageCenter = {
  75. x: pageStart.x + domCenter.x,
  76. y: pageStart.y + domCenter.y
  77. }
  78. if (ev instanceof TouchEvent) {
  79. parent.addEventListener("touchmove", move)
  80. parent.addEventListener("touchend", end)
  81. } else {
  82. parent.addEventListener("mousemove", move)
  83. parent.addEventListener("mouseup", end)
  84. }
  85. ev.stopPropagation()
  86. ev.preventDefault()
  87. }
  88. const move = (ev: MouseEvent | TouchEvent) => {
  89. if ((currentPos = getPointByEvent(ev)).length != startPos.length) {
  90. return startPos = currentPos
  91. }
  92. if (startPos.length !== 2) {
  93. if (mode.includes(Mode.move)) {
  94. translate = {
  95. x: currentPos[0].x - startPos[0].x,
  96. y: currentPos[0].y - startPos[0].y
  97. }
  98. } else if (mode.includes(Mode.angle)) {
  99. const angleAngle = mathUtil.Angle(pageCenter, startPos[0], currentPos[0]) * (Math.PI / 180)
  100. const isClock = mathUtil.isClockwise([pageCenter, startPos[0], currentPos[0]])
  101. angle = isClock ? -angleAngle : angleAngle
  102. }
  103. } else if (mode.includes(Mode.scale)) {
  104. const center = scale?.center || {
  105. x: (startPos[0].x - pageStart.x + startPos[1].x - pageStart.x) / 2,
  106. y: (startPos[0].y - pageStart.y + startPos[1].y - pageStart.y) / 2,
  107. }
  108. scale = {
  109. scale: mathUtil.getDistance(...currentPos) / mathUtil.getDistance(...startPos),
  110. center
  111. }
  112. }
  113. updateMatrix()
  114. startPos = currentPos
  115. }
  116. const end = (ev: MouseEvent | TouchEvent) => {
  117. callback()
  118. translate = scale = angle = null;
  119. if (ev instanceof TouchEvent) {
  120. parent.removeEventListener("touchmove", move)
  121. parent.removeEventListener("touchend", end)
  122. } else {
  123. parent.removeEventListener("mousemove", move)
  124. parent.removeEventListener("mouseup", end)
  125. }
  126. }
  127. const updateMatrix = () => {
  128. let currentMatrix
  129. if (translate) {
  130. currentMatrix = matruces.translateMatrix(
  131. translate.x / matrix.value[0],
  132. translate.y / matrix.value[0],
  133. 0
  134. )
  135. translate = null
  136. } else if (scale) {
  137. const offset = {
  138. x: (scale.center.x - domCenter.x) / (matrix.value[0] * scale.scale),
  139. y: (scale.center.y - domCenter.y) / (matrix.value[0] * scale.scale)
  140. }
  141. currentMatrix = matruces.multiplyMatrices(
  142. matruces.multiplyMatrices(
  143. matruces.translateMatrix(offset.x, offset.y, 0),
  144. matruces.scaleMatrix(scale.scale, scale.scale, 1)
  145. ),
  146. matruces.translateMatrix(-offset.x, -offset.y, 0)
  147. )
  148. scale = null
  149. } else if (angle) {
  150. currentMatrix = matruces.rotateZMatrix(angle)
  151. angle = null
  152. }
  153. if (currentMatrix) {
  154. matrix.value = matruces.multiplyMatrices(
  155. matrix.value,
  156. currentMatrix
  157. )
  158. }
  159. }
  160. const stop = watchEffect((onCleanup) => {
  161. if (targetRef.value) {
  162. targetRef.value.addEventListener("mousedown", start)
  163. targetRef.value.addEventListener("touchstart", start)
  164. }
  165. onCleanup(() => {
  166. if (targetRef.value) {
  167. targetRef.value.removeEventListener("mousedown", start)
  168. targetRef.value.removeEventListener("touchstart", start)
  169. }
  170. parent.removeEventListener("mousemove", move)
  171. parent.removeEventListener("mouseup", end)
  172. parent.removeEventListener("touchmove", move)
  173. parent.removeEventListener("touchend", end)
  174. })
  175. })
  176. const cssMatrix = computed<string>(
  177. () => matruces.matrixArrayToCssMatrix(matrix.value)
  178. )
  179. onUnmounted(stop)
  180. return {
  181. stop,
  182. matrix,
  183. cssMatrix
  184. }
  185. }