Sfoglia il codice sorgente

键盘事件优化

MinJieLiu 5 anni fa
parent
commit
98a548dc56
6 ha cambiato i file con 118 aggiunte e 134 eliminazioni
  1. 2 1
      package.json
  2. 45 93
      src/Photo.tsx
  3. 36 32
      src/PhotoSlider.tsx
  4. 15 6
      src/PhotoView.tsx
  5. 6 2
      src/components/SlideWrap.tsx
  6. 14 0
      src/utils/useMountedState.ts

+ 2 - 1
package.json

@@ -15,7 +15,8 @@
     "react-photo",
     "react-photo-view",
     "photo",
-    "photo-view"
+    "photo-view",
+    "gallery"
   ],
   "engines": {
     "node": ">=8",

+ 45 - 93
src/Photo.tsx

@@ -1,64 +1,38 @@
 import React from 'react';
 import classNames from 'classnames';
-import throttle from './utils/throttle';
 import Spinner from './components/Spinner';
 import getSuitableImageSize from './utils/getSuitableImageSize';
+import useMountedState from './utils/useMountedState';
 import './Photo.less';
 
 export interface IPhotoProps extends React.HTMLAttributes<any> {
   src: string;
   loaded: boolean;
-  naturalWidth: number;
-  naturalHeight: number;
   width: number;
   height: number;
   className?: string;
-  onPhotoResize?: () => void;
   onImageLoad: (PhotoParams, callback?: Function) => void;
   loadingElement?: JSX.Element;
   brokenElement?: JSX.Element;
 }
 
-type PhotoState = {
-  broken: boolean;
-};
-
-export default class Photo extends React.PureComponent<
-  IPhotoProps,
-  PhotoState
-> {
-  static displayName = 'Photo';
-
-  readonly state = {
-    broken: false,
-  };
-
-  private isMount = true;
-
-  constructor(props: IPhotoProps) {
-    super(props);
-    this.handleResize = throttle(this.handleResize, 8);
-  }
+const Photo: React.FC<IPhotoProps> = ({
+  src,
+  loaded,
+  width,
+  height,
+  className,
+  onImageLoad,
+  loadingElement,
+  brokenElement,
+  ...restProps
+}) => {
+  const [broken, setBroken] = React.useState<boolean>(false);
+  const isMounted = useMountedState();
 
-  componentDidMount() {
-    const { src } = this.props;
-    const currPhoto = new Image();
-    currPhoto.onload = this.handleImageLoaded;
-    currPhoto.onerror = this.handleImageBroken;
-    currPhoto.src = src;
-
-    window.addEventListener('resize', this.handleResize);
-  }
-
-  componentWillUnmount() {
-    this.isMount = false;
-    window.removeEventListener('resize', this.handleResize);
-  }
-
-  handleImageLoaded = e => {
+  function handleImageLoaded(e) {
     const { naturalWidth, naturalHeight } = e.target;
-    if (this.isMount) {
-      const { onImageLoad } = this.props;
+    if (isMounted()) {
       onImageLoad({
         loaded: true,
         naturalWidth,
@@ -66,61 +40,39 @@ export default class Photo extends React.PureComponent<
         ...getSuitableImageSize(naturalWidth, naturalHeight),
       });
     }
-  };
+  }
 
-  handleImageBroken = () => {
-    if (this.isMount) {
-      this.setState({
-        broken: true,
-      });
+  function handleImageBroken() {
+    if (isMounted()) {
+      setBroken(true);
     }
-  };
+  }
 
-  handleResize = () => {
-    const { loaded, naturalWidth, naturalHeight } = this.props;
-    if (loaded && this.isMount) {
-      const { onImageLoad, onPhotoResize } = this.props;
-      onImageLoad(getSuitableImageSize(naturalWidth, naturalHeight));
+  React.useEffect(() => {
+    const currPhoto = new Image();
+    currPhoto.onload = handleImageLoaded;
+    currPhoto.onerror = handleImageBroken;
+    currPhoto.src = src;
+  }, []);
 
-      if (onPhotoResize) {
-        onPhotoResize();
-      }
+  if (src && !broken) {
+    if (loaded) {
+      return (
+        <img
+          className={classNames('PhotoView__Photo', className)}
+          src={src}
+          width={width}
+          height={height}
+          alt=""
+          {...restProps}
+        />
+      );
     }
-  };
+    return loadingElement || <Spinner />;
+  }
+  return brokenElement || null;
+};
 
-  render() {
-    const {
-      src,
-      loaded,
-      width,
-      height,
-      className,
-      loadingElement,
-      brokenElement,
-      // ignore
-      naturalWidth,
-      naturalHeight,
-      onPhotoResize,
-      onImageLoad,
-      ...restProps
-    } = this.props;
-    const { broken } = this.state;
+Photo.displayName = 'Photo';
 
-    if (src && !broken) {
-      if (loaded) {
-        return (
-          <img
-            className={classNames('PhotoView__Photo', className)}
-            src={src}
-            width={width}
-            height={height}
-            alt=""
-            {...restProps}
-          />
-        );
-      }
-      return loadingElement || <Spinner />;
-    }
-    return brokenElement || null;
-  }
-}
+export default Photo;

+ 36 - 32
src/PhotoSlider.tsx

@@ -36,6 +36,8 @@ type PhotoSliderState = {
 
   // 图片处于触摸的状态
   touched: boolean;
+  // 该状态是否需要 transition
+  shouldTransition: boolean;
   // Reach 开始时 x 坐标
   lastClientX: number | undefined;
   // Reach 开始时 y 坐标
@@ -81,6 +83,7 @@ export default class PhotoSlider extends React.Component<
       translateX: 0,
       photoIndex: 0,
       touched: false,
+      shouldTransition: true,
 
       lastClientX: undefined,
       lastClientY: undefined,
@@ -148,10 +151,10 @@ export default class PhotoSlider extends React.Component<
     if (visible) {
       switch (evt.key) {
         case 'ArrowLeft':
-          this.handlePrevious();
+          this.handlePrevious(false);
           break;
         case 'ArrowRight':
-          this.handleNext();
+          this.handleNext(false);
           break;
         case 'Escape':
           this.handleClose();
@@ -214,12 +217,19 @@ export default class PhotoSlider extends React.Component<
     });
   };
 
-  handleIndexChange = (photoIndex: number) => {
+  handleIndexChange = (
+    photoIndex: number,
+    shouldTransition: boolean = true,
+  ) => {
     const singlePageWidth = window.innerWidth + horizontalOffset;
     const translateX = -singlePageWidth * photoIndex;
     this.setState({
+      touched: false,
+      lastClientX: undefined,
+      lastClientY: undefined,
       translateX,
       photoIndex,
+      shouldTransition,
     });
     const { onIndexChange } = this.props;
     if (onIndexChange) {
@@ -227,18 +237,18 @@ export default class PhotoSlider extends React.Component<
     }
   };
 
-  handlePrevious = () => {
+  handlePrevious = (shouldTransition?: boolean) => {
     const { photoIndex } = this.state;
     if (photoIndex > 0) {
-      this.handleIndexChange(photoIndex - 1);
+      this.handleIndexChange(photoIndex - 1, shouldTransition);
     }
   };
 
-  handleNext = () => {
+  handleNext = (shouldTransition?: boolean) => {
     const { images } = this.props;
     const { photoIndex } = this.state;
     if (photoIndex < images.length - 1) {
-      this.handleIndexChange(photoIndex + 1);
+      this.handleIndexChange(photoIndex + 1, shouldTransition);
     }
   };
 
@@ -256,8 +266,7 @@ export default class PhotoSlider extends React.Component<
   };
 
   handleReachUp = (clientX: number, clientY: number) => {
-    const { innerWidth, innerHeight } = window;
-    const { images, onIndexChange, onClose } = this.props;
+    const { images, onClose } = this.props;
     const {
       lastClientX = clientX,
       lastClientY = clientY,
@@ -268,33 +277,26 @@ export default class PhotoSlider extends React.Component<
 
     const offsetClientX = clientX - lastClientX;
     const offsetClientY = clientY - lastClientY;
-    const singlePageWidth = innerWidth + horizontalOffset;
+    let isChangeVisible = false;
+    // 下一张
+    if (offsetClientX < -maxMoveOffset && photoIndex < images.length - 1) {
+      this.handleIndexChange(photoIndex + 1);
+      return;
+    }
+    // 上一张
+    if (offsetClientX > maxMoveOffset && photoIndex > 0) {
+      this.handleIndexChange(photoIndex - 1);
+      return;
+    }
+    const singlePageWidth = window.innerWidth + horizontalOffset;
 
     // 当前偏移
     let currentTranslateX = -singlePageWidth * photoIndex;
     let currentPhotoIndex = photoIndex;
-    let isChangeVisible = false;
 
-    if (Math.abs(offsetClientY) > innerHeight * 0.14 && canPullClose) {
+    if (Math.abs(offsetClientY) > window.innerHeight * 0.14 && canPullClose) {
       isChangeVisible = true;
       onClose();
-      // 下一张
-    } else if (
-      offsetClientX < -maxMoveOffset &&
-      photoIndex < images.length - 1
-    ) {
-      currentPhotoIndex = photoIndex + 1;
-      currentTranslateX = -singlePageWidth * currentPhotoIndex;
-      if (onIndexChange) {
-        onIndexChange(currentPhotoIndex);
-      }
-      // 上一张
-    } else if (offsetClientX > maxMoveOffset && photoIndex > 0) {
-      currentPhotoIndex = photoIndex - 1;
-      currentTranslateX = -singlePageWidth * currentPhotoIndex;
-      if (onIndexChange) {
-        onIndexChange(currentPhotoIndex);
-      }
     }
     this.setState({
       touched: false,
@@ -328,6 +330,7 @@ export default class PhotoSlider extends React.Component<
       photoIndex,
       backdropOpacity,
       overlayVisible,
+      shouldTransition,
     } = this.state;
     const imageLength = images.length;
     const currentImage = images.length ? images[photoIndex] : undefined;
@@ -406,9 +409,10 @@ export default class PhotoSlider extends React.Component<
                             realIndex}px`,
                           WebkitTransform: transform,
                           transform,
-                          transition: touched
-                            ? undefined
-                            : 'transform 0.6s cubic-bezier(0.25, 0.8, 0.25, 1)',
+                          transition:
+                            touched || !shouldTransition
+                              ? undefined
+                              : 'transform 0.6s cubic-bezier(0.25, 0.8, 0.25, 1)',
                         }}
                         loadingElement={loadingElement}
                         brokenElement={brokenElement}

+ 15 - 6
src/PhotoView.tsx

@@ -25,6 +25,7 @@ import {
   OriginRectType,
 } from './types';
 import './PhotoView.less';
+import getSuitableImageSize from './utils/getSuitableImageSize';
 
 export interface IPhotoViewProps {
   // 图片地址
@@ -118,6 +119,7 @@ export default class PhotoView extends React.Component<
   constructor(props: IPhotoViewProps) {
     super(props);
     this.onMove = throttle(this.onMove, 8);
+    this.handleResize = throttle(this.handleResize, 8);
     // 单击与双击事件处理
     this.handlePhotoTap = withContinuousTap(
       this.onPhotoTap,
@@ -133,6 +135,7 @@ export default class PhotoView extends React.Component<
       window.addEventListener('mousemove', this.handleMouseMove);
       window.addEventListener('mouseup', this.handleMouseUp);
     }
+    window.addEventListener('resize', this.handleResize);
   }
 
   componentWillUnmount() {
@@ -143,12 +146,24 @@ export default class PhotoView extends React.Component<
       window.removeEventListener('mousemove', this.handleMouseMove);
       window.removeEventListener('mouseup', this.handleMouseUp);
     }
+    window.removeEventListener('resize', this.handleResize);
   }
 
   handleImageLoad = imageParams => {
     this.setState(imageParams);
   };
 
+  handleResize = () => {
+    const { onPhotoResize } = this.props;
+    const { loaded, naturalWidth, naturalHeight } = this.state;
+    if (loaded) {
+      this.setState(getSuitableImageSize(naturalWidth, naturalHeight));
+      if (onPhotoResize) {
+        onPhotoResize();
+      }
+    }
+  };
+
   handleStart = (clientX: number, clientY: number, touchLength: number = 0) => {
     this.setState(prevState => ({
       touched: true,
@@ -432,7 +447,6 @@ export default class PhotoView extends React.Component<
       style,
       loadingElement,
       brokenElement,
-      onPhotoResize,
       isActive,
 
       showAnimateType,
@@ -442,8 +456,6 @@ export default class PhotoView extends React.Component<
     const {
       width,
       height,
-      naturalWidth,
-      naturalHeight,
       loaded,
       x,
       y,
@@ -475,13 +487,10 @@ export default class PhotoView extends React.Component<
             src={src}
             width={width}
             height={height}
-            naturalWidth={naturalWidth}
-            naturalHeight={naturalHeight}
             loaded={loaded}
             onMouseDown={isMobile ? undefined : this.handleMouseDown}
             onTouchStart={isMobile ? this.handleTouchStart : undefined}
             onWheel={this.handleWheel}
-            onPhotoResize={onPhotoResize}
             style={{
               WebkitTransform: transform,
               transform,

+ 6 - 2
src/components/SlideWrap.tsx

@@ -3,9 +3,10 @@ import { createPortal } from 'react-dom';
 import classNames from 'classnames';
 import './SlideWrap.less';
 
-const SlideWrap: React.FC<{ className?: string }> = ({
+const SlideWrap: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({
   className,
   children,
+  ...restProps
 }) => {
   const dialogNode = React.useMemo(
     () => {
@@ -35,7 +36,10 @@ const SlideWrap: React.FC<{ className?: string }> = ({
   );
 
   return createPortal(
-    <div className={classNames('PhotoView-SlideWrap', className)}>
+    <div
+      className={classNames('PhotoView-SlideWrap', className)}
+      {...restProps}
+    >
       {children}
     </div>,
     dialogNode,

+ 14 - 0
src/utils/useMountedState.ts

@@ -0,0 +1,14 @@
+import React from 'react';
+
+export default function useMountedState(): () => boolean {
+  const mountedRef = React.useRef<boolean>(false);
+  React.useEffect(() => {
+    mountedRef.current = true;
+
+    return () => {
+      mountedRef.current = false;
+    };
+  });
+
+  return React.useCallback(() => mountedRef.current, []);
+}