1 | import React, { useRef } from 'react';
|
2 | import { useSpring, animated } from '@react-spring/web';
|
3 | import { useSize } from 'ahooks';
|
4 | import { rubberbandIfOutOfBounds } from '../../utils/rubberband';
|
5 | import { useDragAndPinch } from '../../utils/use-drag-and-pinch';
|
6 | import { bound } from '../../utils/bound';
|
7 | import * as mat from '../../utils/matrix';
|
8 | const classPrefix = `adm-image-viewer`;
|
9 | export const Slide = props => {
|
10 | const {
|
11 | dragLockRef,
|
12 | maxZoom
|
13 | } = props;
|
14 | const initialMartix = useRef([]);
|
15 | const controlRef = useRef(null);
|
16 | const imgRef = useRef(null);
|
17 | const [{
|
18 | matrix
|
19 | }, api] = useSpring(() => ({
|
20 | matrix: mat.create(),
|
21 | config: {
|
22 | tension: 200
|
23 | }
|
24 | }));
|
25 | const controlSize = useSize(controlRef);
|
26 | const imgSize = useSize(imgRef);
|
27 | const pinchLockRef = useRef(false);
|
28 | |
29 |
|
30 |
|
31 | const getMinAndMax = nextMatrix => {
|
32 | if (!controlSize || !imgSize) return {
|
33 | x: {
|
34 | position: 0,
|
35 | minX: 0,
|
36 | maxX: 0
|
37 | },
|
38 | y: {
|
39 | position: 0,
|
40 | minY: 0,
|
41 | maxY: 0
|
42 | }
|
43 | };
|
44 | const controlLeft = -controlSize.width / 2;
|
45 | const controlTop = -controlSize.height / 2;
|
46 | const imgLeft = -imgSize.width / 2;
|
47 | const imgTop = -imgSize.height / 2;
|
48 | const zoom = mat.getScaleX(nextMatrix);
|
49 | const scaledImgWidth = zoom * imgSize.width;
|
50 | const scaledImgHeight = zoom * imgSize.height;
|
51 | const minX = controlLeft - (scaledImgWidth - controlSize.width);
|
52 | const maxX = controlLeft;
|
53 | const minY = controlTop - (scaledImgHeight - controlSize.height);
|
54 | const maxY = controlTop;
|
55 | const [x, y] = mat.apply(nextMatrix, [imgLeft, imgTop]);
|
56 | return {
|
57 | x: {
|
58 | position: x,
|
59 | minX,
|
60 | maxX
|
61 | },
|
62 | y: {
|
63 | position: y,
|
64 | minY,
|
65 | maxY
|
66 | }
|
67 | };
|
68 | };
|
69 | |
70 |
|
71 |
|
72 | const getReachBound = (position, min, max, buffer = 0) => {
|
73 | return [position <= min - buffer, position >= max + buffer];
|
74 | };
|
75 | |
76 |
|
77 |
|
78 | const boundMatrix = (nextMatrix, type, last = false) => {
|
79 | if (!controlSize || !imgSize) return nextMatrix;
|
80 | const zoom = mat.getScaleX(nextMatrix);
|
81 | const scaledImgWidth = zoom * imgSize.width;
|
82 | const scaledImgHeight = zoom * imgSize.height;
|
83 | const {
|
84 | x: {
|
85 | position: x,
|
86 | minX,
|
87 | maxX
|
88 | },
|
89 | y: {
|
90 | position: y,
|
91 | minY,
|
92 | maxY
|
93 | }
|
94 | } = getMinAndMax(nextMatrix);
|
95 | if (type === 'translate') {
|
96 | let boundedX = x;
|
97 | let boundedY = y;
|
98 | if (scaledImgWidth > controlSize.width) {
|
99 | boundedX = last ? bound(x, minX, maxX) : rubberbandIfOutOfBounds(x, minX, maxX, zoom * 50);
|
100 | } else {
|
101 | boundedX = -scaledImgWidth / 2;
|
102 | }
|
103 | if (scaledImgHeight > controlSize.height) {
|
104 | boundedY = last ? bound(y, minY, maxY) : rubberbandIfOutOfBounds(y, minY, maxY, zoom * 50);
|
105 | } else {
|
106 | boundedY = -scaledImgHeight / 2;
|
107 | }
|
108 | return mat.translate(nextMatrix, boundedX - x, boundedY - y);
|
109 | }
|
110 | if (type === 'scale' && last) {
|
111 | const [boundedX, boundedY] = [scaledImgWidth > controlSize.width ? bound(x, minX, maxX) : -scaledImgWidth / 2, scaledImgHeight > controlSize.height ? bound(y, minY, maxY) : -scaledImgHeight / 2];
|
112 | return mat.translate(nextMatrix, boundedX - x, boundedY - y);
|
113 | }
|
114 | return nextMatrix;
|
115 | };
|
116 | useDragAndPinch({
|
117 | onDrag: state => {
|
118 | var _a;
|
119 | if (state.first) {
|
120 | const {
|
121 | x: {
|
122 | position: x,
|
123 | minX,
|
124 | maxX
|
125 | }
|
126 | } = getMinAndMax(matrix.get());
|
127 | initialMartix.current = getReachBound(x, minX, maxX);
|
128 | return;
|
129 | }
|
130 | if (state.pinching) return state.cancel();
|
131 | if (state.tap && state.elapsedTime > 0 && state.elapsedTime < 1000) {
|
132 |
|
133 | (_a = props.onTap) === null || _a === void 0 ? void 0 : _a.call(props);
|
134 | return;
|
135 | }
|
136 | const currentZoom = mat.getScaleX(matrix.get());
|
137 | if (dragLockRef) {
|
138 | dragLockRef.current = currentZoom !== 1;
|
139 | }
|
140 | if (!pinchLockRef.current && currentZoom <= 1) {
|
141 | api.start({
|
142 | matrix: mat.create()
|
143 | });
|
144 | } else {
|
145 | const currentMatrix = matrix.get();
|
146 | const offset = [state.offset[0] - mat.getTranslateX(currentMatrix), state.offset[1] - mat.getTranslateY(currentMatrix)];
|
147 | const nextMatrix = mat.translate(currentMatrix, ...(state.last ? [offset[0] + state.velocity[0] * state.direction[0] * 200, offset[1] + state.velocity[1] * state.direction[1] * 200] : offset));
|
148 | api.start({
|
149 | matrix: boundMatrix(nextMatrix, 'translate', state.last),
|
150 | immediate: !state.last
|
151 | });
|
152 | const {
|
153 | x: {
|
154 | position: x,
|
155 | minX,
|
156 | maxX
|
157 | }
|
158 | } = getMinAndMax(nextMatrix);
|
159 | if (state.last && initialMartix.current.some(i => i) && getReachBound(x, minX, maxX).some(i => i)) {
|
160 | if (dragLockRef) {
|
161 | dragLockRef.current = false;
|
162 | }
|
163 | api.start({
|
164 | matrix: mat.create()
|
165 | });
|
166 | }
|
167 | }
|
168 | },
|
169 | onPinch: state => {
|
170 | var _a;
|
171 | pinchLockRef.current = !state.last;
|
172 | const [d] = state.offset;
|
173 | if (d < 0) return;
|
174 | let mergedMaxZoom;
|
175 | if (maxZoom === 'auto') {
|
176 | mergedMaxZoom = controlSize && imgSize ? Math.max(controlSize.height / imgSize.height, controlSize.width / imgSize.width) : 1;
|
177 | } else {
|
178 | mergedMaxZoom = maxZoom;
|
179 | }
|
180 | const nextZoom = state.last ? bound(d, 1, mergedMaxZoom) : d;
|
181 | (_a = props.onZoomChange) === null || _a === void 0 ? void 0 : _a.call(props, nextZoom);
|
182 | if (state.last && nextZoom <= 1) {
|
183 | api.start({
|
184 | matrix: mat.create()
|
185 | });
|
186 | if (dragLockRef) {
|
187 | dragLockRef.current = false;
|
188 | }
|
189 | } else {
|
190 | if (!controlSize) return;
|
191 | const currentMatrix = matrix.get();
|
192 | const currentZoom = mat.getScaleX(currentMatrix);
|
193 | const originOffsetX = state.origin[0] - controlSize.width / 2;
|
194 | const originOffsetY = state.origin[1] - controlSize.height / 2;
|
195 | let nextMatrix = mat.translate(currentMatrix, -originOffsetX, -originOffsetY);
|
196 | nextMatrix = mat.scale(nextMatrix, nextZoom / currentZoom);
|
197 | nextMatrix = mat.translate(nextMatrix, originOffsetX, originOffsetY);
|
198 | api.start({
|
199 | matrix: boundMatrix(nextMatrix, 'scale', state.last),
|
200 | immediate: !state.last
|
201 | });
|
202 | if (dragLockRef) {
|
203 | dragLockRef.current = true;
|
204 | }
|
205 | }
|
206 | }
|
207 | }, {
|
208 | target: controlRef,
|
209 | drag: {
|
210 | from: () => [mat.getTranslateX(matrix.get()), mat.getTranslateY(matrix.get())],
|
211 | pointer: {
|
212 | touch: true
|
213 | }
|
214 | },
|
215 | pinch: {
|
216 | from: () => [mat.getScaleX(matrix.get()), 0],
|
217 | pointer: {
|
218 | touch: true
|
219 | }
|
220 | }
|
221 | });
|
222 | return React.createElement("div", {
|
223 | className: `${classPrefix}-slide`
|
224 | }, React.createElement("div", {
|
225 | className: `${classPrefix}-control`,
|
226 | ref: controlRef
|
227 | }, React.createElement(animated.div, {
|
228 | className: `${classPrefix}-image-wrapper`,
|
229 | style: {
|
230 | matrix
|
231 | }
|
232 | }, React.createElement("img", {
|
233 | ref: imgRef,
|
234 | src: props.image,
|
235 | draggable: false,
|
236 | alt: props.image
|
237 | }))));
|
238 | }; |
\ | No newline at end of file |