UNPKG

7.51 kBJavaScriptView Raw
1import React, { useRef } from 'react';
2import { useSpring, animated } from '@react-spring/web';
3import { useSize } from 'ahooks';
4import { rubberbandIfOutOfBounds } from '../../utils/rubberband';
5import { useDragAndPinch } from '../../utils/use-drag-and-pinch';
6import { bound } from '../../utils/bound';
7import * as mat from '../../utils/matrix';
8const classPrefix = `adm-image-viewer`;
9export 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 * Calculate the min and max value of x and y
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 * Check if is reach the bound
71 */
72 const getReachBound = (position, min, max, buffer = 0) => {
73 return [position <= min - buffer, position >= max + buffer];
74 };
75 /**
76 * Limit the matrix in the bound
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 // 判断点击时间>0是为了过滤掉非正常操作,例如用户长按选择图片之后的取消操作(也是一次点击)
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