previewImageFoundation.ts 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. import BaseFoundation, { DefaultAdapter } from "../base/foundation";
  2. import { handlePrevent } from "../utils/a11y";
  3. import { throttle, isUndefined } from "lodash";
  4. export interface PreviewImageAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
  5. getOriginImageSize: () => { originImageWidth: number; originImageHeight: number; };
  6. setOriginImageSize: (size: { originImageWidth: number; originImageHeight: number; }) => void;
  7. getContainerRef: () => any;
  8. getImageRef: () => any;
  9. getMouseMove: () => boolean;
  10. setStartMouseMove: (move: boolean) => void;
  11. getMouseOffset: () => { x: number; y: number };
  12. setStartMouseOffset: (offset: { x: number; y: number }) => void;
  13. setLoading: (loading: boolean) => void;
  14. }
  15. export interface DragDirection {
  16. canDragVertical: boolean;
  17. canDragHorizontal: boolean;
  18. }
  19. export interface ExtremeBounds {
  20. left: number;
  21. top: number;
  22. }
  23. export interface ImageOffset {
  24. x: number;
  25. y: number;
  26. }
  27. export default class PreviewImageFoundation<P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<PreviewImageAdapter<P, S>, P, S> {
  28. constructor(adapter: PreviewImageAdapter<P, S>) {
  29. super({ ...adapter });
  30. }
  31. _isImageVertical = (): boolean => this.getProp("rotation") % 180 !== 0;
  32. _getImageBounds = (): DOMRect => {
  33. const imageRef = this._adapter.getImageRef();
  34. return imageRef?.getBoundingClientRect();
  35. };
  36. _getContainerBounds = (): DOMRect => {
  37. const containerRef = this._adapter.getContainerRef();
  38. return containerRef?.current?.getBoundingClientRect();
  39. }
  40. _getOffset = (e: any): ImageOffset => {
  41. const { left, top } = this._getImageBounds();
  42. return {
  43. x: e.clientX - left,
  44. y: e.clientY - top,
  45. };
  46. }
  47. setLoading = (loading: boolean) => {
  48. this._adapter.setLoading(loading);
  49. }
  50. handleWindowResize = (): void => {
  51. const { setRatio } = this.getProps();
  52. const { ratio } = this.getProps();
  53. const { originImageWidth, originImageHeight } = this._adapter.getOriginImageSize();
  54. if (originImageWidth && originImageHeight) {
  55. if (ratio !== "adaptation") {
  56. setRatio("adaptation");
  57. } else {
  58. this.handleResizeImage();
  59. }
  60. }
  61. };
  62. handleLoad = (e: any): void => {
  63. if (e.target) {
  64. const { width: w, height: h } = e.target as any;
  65. this._adapter.setOriginImageSize({ originImageWidth: w, originImageHeight: h });
  66. this.setState({
  67. loading: false,
  68. } as any);
  69. this.handleResizeImage();
  70. }
  71. const { src, onLoad } = this.getProps();
  72. onLoad && onLoad(src);
  73. }
  74. handleError = (e: any): void => {
  75. const { onError, src } = this.getProps();
  76. this.setState({
  77. loading: false,
  78. } as any);
  79. onError && onError(src);
  80. }
  81. handleResizeImage = () => {
  82. const horizontal = !this._isImageVertical();
  83. const { originImageWidth, originImageHeight } = this._adapter.getOriginImageSize();
  84. const imgWidth = horizontal ? originImageWidth : originImageHeight;
  85. const imgHeight = horizontal ? originImageHeight : originImageWidth;
  86. const { onZoom } = this.getProps();
  87. const containerRef = this._adapter.getContainerRef();
  88. if (containerRef && containerRef.current) {
  89. const { width: containerWidth, height: containerHeight } = this._getContainerBounds();
  90. const reservedWidth = containerWidth - 80;
  91. const reservedHeight = containerHeight - 80;
  92. const _zoom = Number(
  93. Math.min(reservedWidth / imgWidth, reservedHeight / imgHeight).toFixed(2)
  94. );
  95. onZoom(_zoom);
  96. }
  97. }
  98. handleRightClickImage = (e: any) => {
  99. const { disableDownload } = this.getProps();
  100. if (disableDownload) {
  101. e.preventDefault();
  102. e.stopPropagation();
  103. return false;
  104. } else {
  105. return true;
  106. }
  107. };
  108. handleWheel = (e: React.WheelEvent<HTMLImageElement>) => {
  109. this.onWheel(e);
  110. handlePrevent(e);
  111. }
  112. onWheel = throttle((e: React.WheelEvent<HTMLImageElement>): void => {
  113. const { onZoom, zoomStep, maxZoom, minZoom } = this.getProps();
  114. const { currZoom } = this.getStates();
  115. let _zoom:number;
  116. if (e.deltaY < 0) {
  117. /* zoom in */
  118. if (currZoom + zoomStep <= maxZoom) {
  119. _zoom = Number((currZoom + zoomStep).toFixed(2));
  120. }
  121. } else if (e.deltaY > 0) {
  122. /* zoom out */
  123. if (currZoom - zoomStep >= minZoom) {
  124. _zoom = Number((currZoom - zoomStep).toFixed(2));
  125. }
  126. }
  127. if (!isUndefined(_zoom)) {
  128. onZoom(_zoom);
  129. }
  130. }, 50);
  131. calcCanDragDirection = (): DragDirection => {
  132. const { width, height } = this.getStates();
  133. const { rotation } = this.getProps();
  134. const { width: containerWidth, height: containerHeight } =this._getContainerBounds();
  135. let canDragHorizontal = width > containerWidth;
  136. let canDragVertical = height > containerHeight;
  137. if (this._isImageVertical()) {
  138. canDragHorizontal = height > containerWidth;
  139. canDragVertical = width > containerHeight;
  140. }
  141. return {
  142. canDragVertical,
  143. canDragHorizontal,
  144. };
  145. };
  146. handleZoomChange = (newZoom: number, e: any): void => {
  147. const imageRef = this._adapter.getImageRef();
  148. const { originImageWidth, originImageHeight } = this._adapter.getOriginImageSize();
  149. const { canDragVertical, canDragHorizontal } = this.calcCanDragDirection();
  150. const canDrag = canDragVertical || canDragHorizontal;
  151. const { width: containerWidth, height: containerHeight } = this._getContainerBounds();
  152. const newWidth = Math.floor(originImageWidth * newZoom);
  153. const newHeight = Math.floor(originImageHeight * newZoom);
  154. // debugger;
  155. let _offset;
  156. const horizontal = !this._isImageVertical();
  157. let newTop = 0;
  158. let newLeft = 0;
  159. if (horizontal) {
  160. _offset = {
  161. x: 0.5 * (containerWidth - newWidth),
  162. y: 0.5 * (containerHeight - newHeight),
  163. };
  164. newLeft = _offset.x;
  165. newTop= _offset.y;
  166. } else {
  167. _offset = {
  168. x: 0.5 * (containerWidth - newHeight),
  169. y: 0.5 * (containerHeight - newWidth),
  170. };
  171. newLeft = _offset.x - (newWidth - newHeight) / 2;
  172. newTop = _offset.y + (newWidth - newHeight) / 2;
  173. }
  174. this.setState({
  175. width: newWidth,
  176. height: newHeight,
  177. offset: _offset,
  178. left: newLeft,
  179. top: newTop,
  180. currZoom: newZoom,
  181. } as any);
  182. imageRef && (imageRef.style.cursor = canDrag ? "grab" : "default");
  183. };
  184. calcExtremeBounds = (): ExtremeBounds => {
  185. const { width, height } = this.getStates();
  186. const { width: containerWidth, height: containerHeight } = this._getContainerBounds();
  187. let extremeLeft = containerWidth - width;
  188. let extremeTop = containerHeight - height;
  189. if (this._isImageVertical()) {
  190. extremeLeft = containerWidth - height;
  191. extremeTop = containerHeight - width;
  192. }
  193. return {
  194. left: extremeLeft,
  195. top: extremeTop,
  196. };
  197. };
  198. handleMoveImage = (e: any): void => {
  199. const { offset, width, height, left, top } = this.getStates();
  200. const { rotation } = this.getProps();
  201. const startMouseMove = this._adapter.getMouseMove();
  202. const startMouseOffset = this._adapter.getMouseOffset();
  203. const { canDragVertical, canDragHorizontal } = this.calcCanDragDirection();
  204. if (startMouseMove && (canDragVertical || canDragHorizontal)) {
  205. const { pageX, pageY } = e;
  206. const { left: extremeLeft, top: extremeTop } = this.calcExtremeBounds();
  207. let newX = canDragHorizontal ? pageX - startMouseOffset.x : offset.x;
  208. let newY = canDragVertical ? pageY - startMouseOffset.y : offset.y;
  209. if (canDragHorizontal) {
  210. newX = newX > 0 ? 0 : newX < extremeLeft ? extremeLeft : newX;
  211. }
  212. if (canDragVertical) {
  213. newY = newY > 0 ? 0 : newY < extremeTop ? extremeTop : newY;
  214. }
  215. const _offset = {
  216. x: newX,
  217. y: newY,
  218. };
  219. this.setState({
  220. offset: _offset,
  221. left: this._isImageVertical() ? _offset.x - (width - height) / 2 : _offset.x,
  222. top: this._isImageVertical() ? _offset.y + (width - height) / 2 : _offset.y,
  223. } as any);
  224. }
  225. };
  226. handleImageMouseDown = (e: any): void => {
  227. this._adapter.setStartMouseOffset(this._getOffset(e));
  228. this._adapter.setStartMouseMove(true);
  229. };
  230. handleImageMouseUp = (): void => {
  231. this._adapter.setStartMouseMove(false);
  232. };
  233. }