utils.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. import React from 'react';
  2. import { animationType } from './types';
  3. import { maxTouchTime, defaultAnimationConfig } from './variables';
  4. /**
  5. * 是否为移动端设备
  6. */
  7. export const isMobile: boolean = window.navigator.userAgent.includes('Mobile');
  8. /**
  9. * 从 Touch 事件中获取双指中心点
  10. */
  11. export const getTouchCenter = (evt: React.TouchEvent): {
  12. pageX: number;
  13. pageY: number;
  14. } => {
  15. const firstTouch = evt.touches[0];
  16. const secondTouch = evt.touches[1];
  17. const pageX = (firstTouch.pageX + secondTouch.pageX) / 2;
  18. const pageY = (firstTouch.pageY + secondTouch.pageY) / 2;
  19. return {
  20. pageX,
  21. pageY,
  22. };
  23. };
  24. /**
  25. * 获取图片合适的大小
  26. * @param naturalWidth 图片真实宽度
  27. * @param naturalHeight 图片真实高度
  28. * @return 图片合适的大小
  29. */
  30. export const getSuitableImageSize = (
  31. naturalWidth: number,
  32. naturalHeight: number,
  33. ): {
  34. width: number;
  35. height: number;
  36. } => {
  37. let width = 0;
  38. let height = 0;
  39. const { innerWidth, innerHeight } = window;
  40. if (naturalWidth < innerWidth && naturalHeight < innerHeight) {
  41. width = naturalWidth;
  42. height = naturalHeight;
  43. } else if (naturalWidth < innerWidth && naturalHeight >= innerHeight) {
  44. width = (naturalWidth / naturalHeight) * innerHeight;
  45. height = innerHeight;
  46. } else if (naturalWidth >= innerWidth && naturalHeight < innerHeight) {
  47. width = innerWidth;
  48. height = (naturalHeight / naturalWidth) * innerWidth;
  49. } else if (
  50. naturalWidth >= innerWidth &&
  51. naturalHeight >= innerHeight &&
  52. naturalWidth / naturalHeight > innerWidth / innerHeight
  53. ) {
  54. width = innerWidth;
  55. height = (naturalHeight / naturalWidth) * innerWidth;
  56. } else {
  57. width = (naturalWidth / naturalHeight) * innerHeight;
  58. height = innerHeight;
  59. }
  60. return {
  61. width: Math.floor(width),
  62. height: Math.floor(height),
  63. };
  64. };
  65. export const getPositionOnScale = ({
  66. x,
  67. y,
  68. pageX,
  69. pageY,
  70. fromScale,
  71. toScale,
  72. }: {
  73. x: number;
  74. y: number;
  75. pageX: number;
  76. pageY: number;
  77. fromScale: number;
  78. toScale: number;
  79. }): {
  80. x: number;
  81. y: number;
  82. scale: number;
  83. } => {
  84. const { innerWidth, innerHeight } = window;
  85. let endScale = toScale;
  86. let nextX = x;
  87. let nextY = y;
  88. // 缩放限制
  89. if (toScale < 0.5) {
  90. endScale = 0.5;
  91. } else if (toScale > 5) {
  92. endScale = 5;
  93. } else {
  94. const centerPageX = innerWidth / 2;
  95. const centerPageY = innerHeight / 2;
  96. // 坐标偏移
  97. const lastPositionX = centerPageX + x;
  98. const lastPositionY = centerPageY + y;
  99. // 放大偏移量
  100. nextX = pageX - (pageX - lastPositionX) * (endScale / fromScale) - centerPageX;
  101. nextY = pageY - (pageY - lastPositionY) * (endScale / fromScale) - centerPageY;
  102. }
  103. return {
  104. x: nextX,
  105. y: nextY,
  106. scale: endScale,
  107. };
  108. };
  109. export const slideToPosition = ({
  110. x,
  111. y,
  112. lastX,
  113. lastY,
  114. touchedTime,
  115. }: {
  116. x: number;
  117. y: number;
  118. lastX: number;
  119. lastY: number;
  120. touchedTime: number;
  121. }): {
  122. endX: number;
  123. endY: number;
  124. } & animationType => {
  125. const moveTime = Date.now() - touchedTime;
  126. const speedX = (x - lastX) / moveTime;
  127. const speedY = (y - lastY) / moveTime;
  128. const maxSpeed = Math.max(speedX, speedY);
  129. const slideTime = moveTime < maxTouchTime ? Math.abs(maxSpeed) * 20 + 400 : 0;
  130. return {
  131. endX: Math.floor(x + speedX * slideTime),
  132. endY: Math.floor(y + speedY * slideTime),
  133. animation: {
  134. stiffness: 170,
  135. damping: 32,
  136. },
  137. };
  138. };
  139. /**
  140. * 跳转到合适的图片偏移量
  141. */
  142. export const jumpToSuitableOffset = ({
  143. x,
  144. y,
  145. lastX,
  146. lastY,
  147. width,
  148. height,
  149. scale,
  150. touchedTime,
  151. hasMove,
  152. }: {
  153. x: number;
  154. y: number;
  155. lastX: number;
  156. lastY: number;
  157. width: number;
  158. height: number;
  159. scale: number;
  160. touchedTime: number;
  161. hasMove: boolean;
  162. }): {
  163. x: number;
  164. y: number;
  165. } & animationType => {
  166. // 没有移动图片
  167. if (!hasMove) {
  168. return {
  169. x,
  170. y,
  171. animation: defaultAnimationConfig,
  172. };
  173. }
  174. const { innerWidth, innerHeight } = window;
  175. // 图片超出的长度
  176. const outOffsetX = (width * scale - innerWidth) / 2;
  177. const outOffsetY = (height * scale - innerHeight) / 2;
  178. // 滑动到结果的位置
  179. const { endX, endY, animation } = slideToPosition({ x, y, lastX, lastY, touchedTime });
  180. let currentX = endX;
  181. let currentY = endY;
  182. if (width * scale <= innerWidth) {
  183. currentX = 0;
  184. } else if (endX > 0 && outOffsetX - endX <= 0) {
  185. currentX = outOffsetX;
  186. } else if (endX < 0 && outOffsetX + endX <= 0) {
  187. currentX = -outOffsetX;
  188. }
  189. if (height * scale <= innerHeight) {
  190. currentY = 0;
  191. } else if (endY > 0 && outOffsetY - endY <= 0) {
  192. currentY = outOffsetY;
  193. } else if (endY < 0 && outOffsetY + endY <= 0) {
  194. currentY = -outOffsetY;
  195. }
  196. const isSlide = currentX === endX || currentY === endY;
  197. return {
  198. x: currentX,
  199. y: currentY,
  200. animation: isSlide ? defaultAnimationConfig : animation,
  201. };
  202. };