PanZoomRotate.js 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import React from "react";
  2. import { atom, useRecoilState } from "recoil";
  3. export const PanZoomRotateState = atom({
  4. key: "PanZoomRotate",
  5. default: {
  6. translateX: 0,
  7. translateY: 0,
  8. scale: 1,
  9. rotate: 0,
  10. },
  11. });
  12. const PanZoomRotate = ({ children }) => {
  13. const [dim, setDim] = useRecoilState(PanZoomRotateState);
  14. const wrapperRef = React.useRef(null);
  15. const wrappedRef = React.useRef(null);
  16. const stateRef = React.useRef({
  17. moving: false,
  18. });
  19. const onWheel = (e) => {
  20. if (e.altKey) {
  21. return;
  22. }
  23. const scaleMult = (e.deltaY * dim.scale) / 20;
  24. setDim((prevDim) => {
  25. let newScale = prevDim.scale - scaleMult;
  26. if (newScale > 8) {
  27. newScale = 8;
  28. }
  29. if (newScale < 0.3) {
  30. newScale = 0.3;
  31. }
  32. const { top, left } = wrappedRef.current.getBoundingClientRect();
  33. const displayX = e.clientX - left;
  34. const deltaX = displayX - (displayX / dim.scale) * newScale;
  35. const displayY = e.clientY - top;
  36. const deltaY = displayY - (displayY / dim.scale) * newScale;
  37. return {
  38. ...prevDim,
  39. scale: newScale,
  40. translateX: prevDim.translateX + deltaX,
  41. translateY: prevDim.translateY + deltaY,
  42. };
  43. });
  44. };
  45. const onMouseDown = (e) => {
  46. if (e.button === 1 || e.altKey) {
  47. stateRef.current.moving = true;
  48. stateRef.current.startX = e.clientX;
  49. stateRef.current.startY = e.clientY;
  50. stateRef.current.startTranslateX = dim.translateX;
  51. stateRef.current.startTranslateY = dim.translateY;
  52. wrapperRef.current.style.cursor = "move";
  53. }
  54. };
  55. const onMouseMouve = (e) => {
  56. if (stateRef.current.moving) {
  57. const [deltaX, deltaY] = [
  58. e.clientX - stateRef.current.startX,
  59. e.clientY - stateRef.current.startY,
  60. ];
  61. setDim((prevDim) => ({
  62. ...prevDim,
  63. translateX: stateRef.current.startTranslateX + deltaX,
  64. translateY: stateRef.current.startTranslateY + deltaY,
  65. }));
  66. }
  67. };
  68. const onMouseUp = (e) => {
  69. if (e.button === 1 || e.altKey) {
  70. stateRef.current.moving = false;
  71. wrapperRef.current.style.cursor = "auto";
  72. }
  73. };
  74. const style = {
  75. transform: `translate(${dim.translateX}px, ${dim.translateY}px) scale(${dim.scale}) rotate(${dim.rotate}deg)`,
  76. transformOrigin: "top left",
  77. };
  78. return (
  79. <div
  80. onWheel={onWheel}
  81. onMouseDown={onMouseDown}
  82. onMouseMove={onMouseMouve}
  83. onMouseUp={onMouseUp}
  84. ref={wrapperRef}
  85. style={{}}
  86. >
  87. <div style={{ ...style, display: "inline-block" }} ref={wrappedRef}>
  88. {children}
  89. </div>
  90. </div>
  91. );
  92. };
  93. export default PanZoomRotate;