MinJieLiu hace 7 años
padre
commit
95e99970a0

+ 32 - 11
src/PhotoSlider.tsx

@@ -17,6 +17,8 @@ export interface IPhotoSliderProps {
 }
 
 type PhotoSliderState = {
+  translateX: number;
+  photoIndex: number;
 };
 
 export default class PhotoSlider extends React.Component<
@@ -26,6 +28,8 @@ export default class PhotoSlider extends React.Component<
   static displayName = 'PhotoSlider';
 
   readonly state = {
+    translateX: 0,
+    photoIndex: 0,
   };
 
   componentDidMount() {
@@ -38,29 +42,46 @@ export default class PhotoSlider extends React.Component<
   }
 
   handleReachLeftMove = () => {
+    this.setState({
+      photoIndex: 0,
+    });
   }
 
   handleReachRightMove = () => {
+    this.setState({
+      photoIndex: 1,
+    });
   }
 
   render() {
     const { images, visible } = this.props;
+    const { photoIndex } = this.state;
+    const { innerWidth } = window;
 
     if (visible) {
       return (
         <SlideWrap>
           <Backdrop />
-          {images.map((src, index) => (
-            <PhotoView
-              key={src + index}
-              src={src}
-              onReachTopMove={this.handleReachTopMove}
-              onReachRightMove={index < images.length
-                ? this.handleReachRightMove
-                : undefined}
-              onReachLeftMove={index > 0 ? this.handleReachLeftMove : undefined}
-            />
-          ))}
+          {images.map((src, index) => {
+            const transform = `translate3d(-${photoIndex * innerWidth}px, 0px, 0)`;
+            return (
+              <PhotoView
+                key={src + index}
+                src={src}
+                onReachTopMove={this.handleReachTopMove}
+                onReachRightMove={index < images.length - 1
+                  ? this.handleReachRightMove
+                  : undefined}
+                onReachLeftMove={index > 0 ? this.handleReachLeftMove : undefined}
+                style={{
+                  left: `${innerWidth * index}px`,
+                  WebkitTransform: transform,
+                  transform,
+                  transition: 'transform 0.6s cubic-bezier(0.25, 0.8, 0.25, 1)',
+                }}
+              />
+            );
+          })}
         </SlideWrap>
       );
     }

+ 40 - 8
src/PhotoView.tsx

@@ -7,6 +7,7 @@ import isMobile from './utils/isMobile';
 import getMultipleTouchPosition from './utils/getMultipleTouchPosition';
 import getPositionOnMoveOrScale from './utils/getPositionOnMoveOrScale';
 import slideToSuitableOffset from './utils/slideToSuitableOffset';
+import { getClosedHorizontal, getClosedVertical } from './utils/getCloseEdge';
 import { defaultAnimationConfig } from './variables';
 
 export interface IPhotoViewProps {
@@ -16,6 +17,8 @@ export interface IPhotoViewProps {
   wrapClassName?: string;
   // 图片类名
   className?: string;
+  // style
+  style?: object;
   // 自定义容器
   overlay?: React.ReactNode;
 
@@ -116,7 +119,14 @@ export default class PhotoView extends React.Component<
         scale,
         lastTouchLength,
       }) => {
+        if (touchLength === 0) {
+          const isStopMove = this.handleReachCallback(x, y, scale);
+          if (isStopMove) {
+            return null;
+          }
+        }
         const offsetScale = (touchLength - lastTouchLength) / 100 / 2 * scale;
+
         return {
           lastTouchLength: touchLength,
           ...getPositionOnMoveOrScale({
@@ -244,22 +254,44 @@ export default class PhotoView extends React.Component<
     this.setState(initialState);
   }
 
+  handleReachCallback = (x: number, y: number, scale: number): boolean => {
+    const { width, height } = this.photoRef.state;
+
+    const horizontalType = getClosedHorizontal(x, scale, width);
+    const verticalType = getClosedVertical(y, scale, height);
+    const { onReachTopMove, onReachRightMove, onReachBottomMove, onReachLeftMove } = this.props;
+    //  触碰到边缘
+    if (verticalType && onReachTopMove && y > 0) {
+      onReachTopMove(y);
+    } else if (verticalType && onReachBottomMove && y < 0) {
+      onReachBottomMove(y);
+    } else if (horizontalType && onReachLeftMove && x > 0) {
+      onReachLeftMove(x);
+      return true;
+    } else if (horizontalType && onReachRightMove && x < 0) {
+      onReachRightMove(x);
+      return true;
+    }
+    return false;
+  }
+
   handlePhotoRef = (ref) => {
     this.photoRef = ref;
   }
 
   render() {
-    const { src, wrapClassName, className, overlay } = this.props;
+    const { src, wrapClassName, className, style, overlay } = this.props;
     const { x, y, scale, touched, animation } = this.state;
-    const style = {
-      currX: touched ? x : spring(x, animation),
-      currY: touched ? y : spring(y, animation),
-      currScale: touched ? scale : spring(scale, animation),
-    };
 
     return (
-      <PhotoWrap className={wrapClassName}>
-        <Motion style={style}>
+      <PhotoWrap className={wrapClassName} style={style}>
+        <Motion
+          style={{
+            currX: touched ? x : spring(x, animation),
+            currY: touched ? y : spring(y, animation),
+            currScale: touched ? scale : spring(scale, animation),
+          }}
+        >
           {({ currX, currY, currScale }) => {
             const transform = `translate3d(${currX}px, ${currY}px, 0) scale(${currScale})`;
             return (

+ 52 - 0
src/utils/getCloseEdge.ts

@@ -0,0 +1,52 @@
+/**
+ * 接触左边或右边边缘
+ * @param x
+ * @param scale
+ * @param width
+ * @return 0. 未超出 1. 小于屏幕宽度 2. 接触左边 3. 接触右边
+ */
+export const getClosedHorizontal = (
+  x: number,
+  scale: number,
+  width: number,
+): number => {
+  const { innerWidth } = window;
+  const currentWidth = width * scale;
+  // 图片超出的宽度
+  const outOffsetX = (currentWidth - innerWidth) / 2;
+  if (currentWidth <= innerWidth) {
+    return 1;
+  } else if (x > 0 && outOffsetX - x <= 0) {
+    return 2;
+  } else if (x < 0 && outOffsetX + x <= 0) {
+    return 3;
+  }
+  return 0;
+};
+
+/**
+ * 接触上边或下边边缘
+ * @param y
+ * @param scale
+ * @param height
+ * @return 0. 未超出 1. 小于屏幕高度 2. 接触上边 3. 接触下边
+ */
+export const getClosedVertical = (
+  y: number,
+  scale: number,
+  height: number,
+): number => {
+  const { innerHeight } = window;
+  const currentHeight = height * scale;
+  // 图片超出的高度
+  const outOffsetY = (currentHeight - innerHeight) / 2;
+
+  if (currentHeight <= innerHeight) {
+    return 1;
+  } else if (y > 0 && outOffsetY - y <= 0) {
+    return 2;
+  } else if (y < 0 && outOffsetY + y <= 0) {
+    return 3;
+  }
+  return 0;
+};

+ 35 - 0
src/utils/slideToPosition.ts

@@ -0,0 +1,35 @@
+import { animationType } from '../types';
+import { maxTouchTime, slideAnimationConfig } from '../variables';
+
+/**
+ * 根据速度滑动至目标位置
+ */
+const slideToPosition = ({
+  x,
+  y,
+  lastX,
+  lastY,
+  touchedTime,
+}: {
+  x: number;
+  y: number;
+  lastX: number;
+  lastY: number;
+  touchedTime: number;
+}): {
+  endX: number;
+  endY: number;
+} & animationType => {
+  const moveTime = Date.now() - touchedTime;
+  const speedX = (x - lastX) / moveTime;
+  const speedY = (y - lastY) / moveTime;
+  const maxSpeed = Math.max(speedX, speedY);
+  const slideTime = moveTime < maxTouchTime ? Math.abs(maxSpeed) * 20 + 400 : 0;
+  return {
+    endX: Math.floor(x + speedX * slideTime),
+    endY: Math.floor(y + speedY * slideTime),
+    animation: slideAnimationConfig,
+  };
+};
+
+export default slideToPosition;

+ 21 - 46
src/utils/slideToSuitableOffset.ts

@@ -1,36 +1,7 @@
 import { animationType } from '../types';
-import { maxTouchTime, defaultAnimationConfig } from '../variables';
-
-const slideToPosition = ({
-  x,
-  y,
-  lastX,
-  lastY,
-  touchedTime,
-}: {
-  x: number;
-  y: number;
-  lastX: number;
-  lastY: number;
-  touchedTime: number;
-}): {
-  endX: number;
-  endY: number;
-} & animationType => {
-  const moveTime = Date.now() - touchedTime;
-  const speedX = (x - lastX) / moveTime;
-  const speedY = (y - lastY) / moveTime;
-  const maxSpeed = Math.max(speedX, speedY);
-  const slideTime = moveTime < maxTouchTime ? Math.abs(maxSpeed) * 20 + 400 : 0;
-  return {
-    endX: Math.floor(x + speedX * slideTime),
-    endY: Math.floor(y + speedY * slideTime),
-    animation: {
-      stiffness: 170,
-      damping: 32,
-    },
-  };
-};
+import { defaultAnimationConfig } from '../variables';
+import slideToPosition from './slideToPosition';
+import { getClosedHorizontal, getClosedVertical } from './getCloseEdge';
 
 /**
  * 适应到合适的图片偏移量
@@ -67,12 +38,6 @@ const slideToSuitableOffset = ({
       animation: defaultAnimationConfig,
     };
   }
-
-  const { innerWidth, innerHeight } = window;
-  // 图片超出的长度
-  const outOffsetX = (width * scale - innerWidth) / 2;
-  const outOffsetY = (height * scale - innerHeight) / 2;
-
   // 滑动到结果的位置
   const { endX, endY, animation } = slideToPosition({
     x,
@@ -85,27 +50,37 @@ const slideToSuitableOffset = ({
   let currentX = endX;
   let currentY = endY;
 
-  if (width * scale <= innerWidth) {
+  const { innerWidth, innerHeight } = window;
+  // 图片超出的长度
+  const outOffsetX = (width * scale - innerWidth) / 2;
+  const outOffsetY = (height * scale - innerHeight) / 2;
+
+  const horizontalType = getClosedHorizontal(endX, scale, width);
+  const verticalType = getClosedVertical(endY, scale, height);
+
+  // x
+  if (horizontalType === 1) {
     currentX = 0;
-  } else if (endX > 0 && outOffsetX - endX <= 0) {
+  } else if (horizontalType === 2) {
     currentX = outOffsetX;
-  } else if (endX < 0 && outOffsetX + endX <= 0) {
+  } else if (horizontalType === 3) {
     currentX = -outOffsetX;
   }
-  if (height * scale <= innerHeight) {
+  // y
+  if (verticalType === 1) {
     currentY = 0;
-  } else if (endY > 0 && outOffsetY - endY <= 0) {
+  } else if (verticalType === 2) {
     currentY = outOffsetY;
-  } else if (endY < 0 && outOffsetY + endY <= 0) {
+  } else if (verticalType === 3) {
     currentY = -outOffsetY;
   }
 
-  const isBumpEdge = currentX !== endX || currentY !== endY;
+  const isClosedEdge = horizontalType !== 0 || verticalType !== 0;
 
   return {
     x: currentX,
     y: currentY,
-    animation: isBumpEdge ? defaultAnimationConfig : animation,
+    animation: isClosedEdge ? defaultAnimationConfig : animation,
   };
 };
 

+ 8 - 0
src/variables.ts

@@ -12,3 +12,11 @@ export const defaultAnimationConfig: springType = {
   stiffness: 240,
   damping: 30,
 };
+
+/**
+ * 滑动时动画参数
+ */
+export const slideAnimationConfig: springType = {
+  stiffness: 170,
+  damping: 32,
+};