Jelajahi Sumber

add context

MinJieLiu 7 tahun lalu
induk
melakukan
ef62093ea8

+ 1 - 0
package.json

@@ -46,6 +46,7 @@
   },
   "dependencies": {
     "lodash.throttle": "^4.1.1",
+    "lodash.uniqueid": "^4.0.1",
     "react-motion": "^0.5.2",
     "styled-components": "^3.0.1"
   }

+ 67 - 0
src/PhotoConsumer.tsx

@@ -0,0 +1,67 @@
+import React from 'react';
+import uniqueId from 'lodash.uniqueid';
+import PhotoContext from './photo-context';
+
+export interface IPhotoViewItem {
+  src: string;
+  children: React.ReactElement<any>;
+  onShow: (dataKey) => void;
+  addItem: (dataKey: string, src: string) => void;
+  removeItem: (dataKey) => void;
+}
+
+class PhotoViewItem extends React.Component<IPhotoViewItem> {
+
+  private dataKey: string = uniqueId();
+
+  componentDidMount() {
+    const { src, addItem } = this.props;
+    addItem(this.dataKey, src);
+  }
+
+  componentWillUnmount() {
+    const { removeItem } = this.props;
+    removeItem(this.dataKey);
+  }
+
+  handleShow = (e) => {
+    const { onShow, children } = this.props;
+    onShow(this.dataKey);
+
+    const { onClick } = children.props;
+    if (onClick) {
+      onClick(e);
+    }
+  }
+
+  render() {
+    const { children } = this.props;
+    if (children) {
+      return React.cloneElement(children, {
+        onClick: this.handleShow,
+      });
+    }
+    return null;
+  }
+}
+
+export interface IPhotoConsumer {
+  src: string;
+  children: React.ReactElement<any>;
+}
+
+const PhotoConsumer: React.SFC<IPhotoConsumer> = ({ src, children, ...restProps }) => (
+  <PhotoContext.Consumer>
+    {value => (
+      <PhotoViewItem
+        {...value}
+        {...restProps}
+        src={src}
+      >
+        {children}
+      </PhotoViewItem>
+    )}
+  </PhotoContext.Consumer>
+);
+
+export default PhotoConsumer;

+ 105 - 0
src/PhotoProvider.tsx

@@ -0,0 +1,105 @@
+import React from 'react';
+import PhotoContext from './photo-context';
+import PhotoSlider from './PhotoSlider';
+import { dataType } from './types';
+
+interface IPhotoProvider {
+  children: React.ReactNode;
+  // className
+  className?: string;
+  // 遮罩 className
+  maskClassName?: string;
+  // 图片容器 className
+  viewClassName?: string;
+  // 图片 className
+  imageClassName?: string;
+}
+
+type PhotoProviderState = {
+  data: dataType[];
+  visible: boolean;
+  index: number;
+  onShow: (dataKey) => void;
+  onClose: () => void;
+  addItem: (dataKey: string, src: string) => void;
+  removeItem: (dataKey) => void;
+};
+
+export default class PhotoProvider extends React.Component<
+  IPhotoProvider,
+  PhotoProviderState
+> {
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      data: [],
+      visible: false,
+      index: 0,
+      addItem: this.handleAddItem,
+      removeItem: this.handleRemoveItem,
+      onShow: this.handleShow,
+      onClose: this.handleClose,
+    };
+  }
+
+  handleAddItem = item => {
+    this.setState(prev => ({
+      data: prev.data.concat(item),
+    }));
+  }
+
+  handleRemoveItem = dataKey => {
+    this.setState(prev => ({
+      data: prev.data.filter(item => item.dataKey !== dataKey),
+    }));
+  }
+
+  handleShow = (dataKey) => {
+    const { data } = this.state;
+    this.setState({
+      visible: true,
+      index: data.findIndex(item => item.dataKey === dataKey),
+    });
+  }
+
+  handleClose = () => {
+    this.setState({
+      visible: false,
+    });
+  }
+
+  handleIndexChange = (index) => {
+    this.setState({
+      index,
+    });
+  }
+
+  render() {
+    const {
+      className,
+      maskClassName,
+      viewClassName,
+      imageClassName,
+      children,
+    } = this.props;
+    const { data, visible, index } = this.state;
+
+    return (
+      <PhotoContext.Provider value={this.state}>
+        {children}
+        <PhotoSlider
+          images={data}
+          visible={visible}
+          index={index}
+          onIndexChange={this.handleIndexChange}
+          onClose={this.handleClose}
+          className={className}
+          maskClassName={maskClassName}
+          viewClassName={viewClassName}
+          imageClassName={imageClassName}
+        />
+      </PhotoContext.Provider>
+    );
+  }
+}

+ 42 - 23
src/PhotoSlider.tsx

@@ -2,7 +2,8 @@ import React from 'react';
 import PhotoView from './PhotoView';
 import SlideWrap from './components/SlideWrap';
 import Backdrop from './components/Backdrop';
-import { maxMoveOffset, closePageOffset, defaultOpacity } from './variables';
+import { dataType } from './types';
+import { maxMoveOffset, defaultOpacity } from './variables';
 
 export interface IPhotoSliderProps {
   // 图片列表
@@ -17,6 +18,14 @@ export interface IPhotoSliderProps {
   onIndexChange?: Function;
   // 自定义容器
   overlay?: React.ReactNode;
+  // className
+  className?: string;
+  // 遮罩 className
+  maskClassName?: string;
+  // 图片容器 className
+  viewClassName?: string;
+  // 图片 className
+  imageClassName?: string;
 }
 
 type PhotoSliderState = {
@@ -43,16 +52,11 @@ export default class PhotoSlider extends React.Component<
 > {
   static displayName = 'PhotoSlider';
 
-  static defaultProps = {
-    index: 0,
-    translateX: 0,
-  };
-
   static getDerivedStateFromProps(nextProps, prevState) {
     if (nextProps.index !== undefined && nextProps.index !== prevState.photoIndex) {
       return {
         photoIndex: nextProps.index,
-        translateX: window.innerWidth * nextProps.index,
+        translateX: -window.innerWidth * nextProps.index,
       };
     }
     return null;
@@ -60,9 +64,10 @@ export default class PhotoSlider extends React.Component<
 
   constructor(props) {
     super(props);
+    const { index = 0 } = props;
     this.state = {
-      translateX: props.index * window.innerWidth,
-      photoIndex: props.index || 0,
+      translateX: index * -window.innerWidth,
+      photoIndex: index,
       touched: false,
 
       lastPageX: undefined,
@@ -84,14 +89,14 @@ export default class PhotoSlider extends React.Component<
     const { innerWidth } = window;
     this.setState(({ photoIndex }) => {
       return {
-        translateX: innerWidth * photoIndex,
+        translateX: -innerWidth * photoIndex,
         lastPageX: undefined,
         lastPageY: undefined,
       };
     });
   }
 
-  handleReachTopMove = (pageX, pageY) => {
+  handleReachVerticalMove = (pageX, pageY) => {
     this.setState(({ lastPageY, backdropOpacity }) => {
       if (lastPageY === undefined) {
         return {
@@ -101,7 +106,7 @@ export default class PhotoSlider extends React.Component<
           photoScale: 1,
         };
       }
-      const offsetPageY = pageY - lastPageY;
+      const offsetPageY = Math.abs(pageY - lastPageY);
       return {
         touched: true,
         lastPageY,
@@ -131,37 +136,37 @@ export default class PhotoSlider extends React.Component<
       return {
         touched: true,
         lastPageX: lastPageX,
-        translateX: innerWidth * photoIndex - offsetPageX,
+        translateX: -innerWidth * photoIndex + offsetPageX,
       };
     });
   }
 
   handleReachUp = (pageX, pageY) => {
-    const { innerWidth } = window;
+    const { innerWidth, innerHeight } = window;
     const { images, onIndexChange, onClose } = this.props;
     const { lastPageX = pageX, lastPageY = pageY, photoIndex } = this.state;
 
     const offsetPageX = pageX - lastPageX;
     const offsetPageY = pageY - lastPageY;
 
-    if (offsetPageY > closePageOffset) {
+    if (Math.abs(offsetPageY) > innerHeight * 0.14) {
       onClose();
       return;
     }
     // 当前偏移
-    let currentTranslateX = innerWidth * photoIndex;
+    let currentTranslateX = -innerWidth * photoIndex;
     let currentPhotoIndex = photoIndex;
     // 下一张
     if (offsetPageX < - maxMoveOffset && photoIndex < images.length - 1) {
       currentPhotoIndex = photoIndex + 1;
-      currentTranslateX = innerWidth * currentPhotoIndex;
+      currentTranslateX = -innerWidth * currentPhotoIndex;
       if (onIndexChange) {
         onIndexChange(currentPhotoIndex);
       }
       // 上一张
     } else if (offsetPageX > maxMoveOffset && photoIndex > 0) {
       currentPhotoIndex = photoIndex - 1;
-      currentTranslateX = innerWidth * currentPhotoIndex;
+      currentTranslateX = -innerWidth * currentPhotoIndex;
       if (onIndexChange) {
         onIndexChange(currentPhotoIndex);
       }
@@ -179,7 +184,15 @@ export default class PhotoSlider extends React.Component<
 
   render() {
     const { innerWidth } = window;
-    const { images, visible, overlay } = this.props;
+    const {
+      images,
+      visible,
+      overlay,
+      className,
+      maskClassName,
+      viewClassName,
+      imageClassName,
+    } = this.props;
     const {
       translateX,
       touched,
@@ -187,24 +200,30 @@ export default class PhotoSlider extends React.Component<
       backdropOpacity,
       photoScale,
     } = this.state;
-    const transform = `translate3d(-${translateX}px, 0px, 0)`;
+    const transform = `translate3d(${translateX}px, 0px, 0)`;
 
     if (visible) {
       return (
-        <SlideWrap>
-          <Backdrop style={{ background: `rgba(0, 0, 0, ${backdropOpacity})` }} />
+        <SlideWrap className={className}>
+          <Backdrop
+            className={maskClassName}
+            style={{ background: `rgba(0, 0, 0, ${backdropOpacity})` }}
+          />
           {images.map((src, index) => {
             return (
               <PhotoView
                 key={src + index}
                 src={src}
-                onReachTopMove={this.handleReachTopMove}
+                onReachTopMove={this.handleReachVerticalMove}
+                onReachBottomMove={this.handleReachVerticalMove}
                 onReachRightMove={index < images.length - 1
                   ? this.handleReachHorizontalMove
                   : undefined}
                 onReachLeftMove={index > 0 ? this.handleReachHorizontalMove : undefined}
                 onReachUp={this.handleReachUp}
                 photoScale={photoIndex === index ? photoScale : 1}
+                wrapClassName={viewClassName}
+                className={imageClassName}
                 style={{
                   left: `${innerWidth * index}px`,
                   WebkitTransform: transform,

+ 15 - 11
src/PhotoView.tsx

@@ -297,17 +297,21 @@ export default class PhotoView extends React.Component<
           touched: false,
           scale: toScale,
           reachState: 0, // 重置触发状态
-          ...slideToSuitableOffset({
-            x,
-            y,
-            lastX,
-            lastY,
-            width,
-            height,
-            scale,
-            touchedTime,
-            hasMove,
-          }),
+          ...hasMove
+            ? slideToSuitableOffset({
+              x,
+              y,
+              lastX,
+              lastY,
+              width,
+              height,
+              scale,
+              touchedTime,
+            }) : {
+              x,
+              y,
+              animation: defaultAnimationConfig,
+            },
         };
       });
     }

+ 3 - 2
src/components/SlideWrap.tsx

@@ -13,6 +13,7 @@ const Container = styled.div`
 `;
 
 export default class SlideWrap extends React.Component<{
+  className?: string;
   children: any;
 }> {
   static displayName = 'SlideWrap';
@@ -52,10 +53,10 @@ export default class SlideWrap extends React.Component<{
   }
 
   render() {
-    const { children } = this.props;
+    const { className, children } = this.props;
 
     return createPortal(
-      <Container>{children}</Container>,
+      <Container className={className}>{children}</Container>,
       this.dialogNode,
     );
   }

+ 7 - 0
src/photo-context.ts

@@ -0,0 +1,7 @@
+import React from 'react';
+
+export default React.createContext({
+  onShow(dataKey) {},
+  addItem(dataKey, src) {},
+  removeItem(dataKey) {},
+});

+ 8 - 0
src/types.ts

@@ -8,3 +8,11 @@ export type springType = {
 export type animationType = {
   animation: springType;
 };
+
+/**
+ * 图片 item 类型
+ */
+export type dataType = {
+  dataKey: string;
+  src: string;
+};

+ 1 - 1
src/utils/slideToPosition.ts

@@ -24,7 +24,7 @@ const slideToPosition = ({
   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;
+  const slideTime = moveTime < maxTouchTime ? Math.abs(maxSpeed) * 20 + maxTouchTime : 0;
   return {
     endX: Math.floor(x + speedX * slideTime),
     endY: Math.floor(y + speedY * slideTime),

+ 0 - 10
src/utils/slideToSuitableOffset.ts

@@ -15,7 +15,6 @@ const slideToSuitableOffset = ({
   height,
   scale,
   touchedTime,
-  hasMove,
 }: {
   x: number;
   y: number;
@@ -25,19 +24,10 @@ const slideToSuitableOffset = ({
   height: number;
   scale: number;
   touchedTime: number;
-  hasMove: boolean;
 }): {
   x: number;
   y: number;
 } & animationType => {
-  // 没有移动图片
-  if (!hasMove) {
-    return {
-      x,
-      y,
-      animation: defaultAnimationConfig,
-    };
-  }
   // 滑动到结果的位置
   const { endX, endY, animation } = slideToPosition({
     x,

+ 0 - 5
src/variables.ts

@@ -16,11 +16,6 @@ export const maxMoveOffset: number = 40;
 export const minReachOffset: number = 20;
 
 /**
- * 下拉关闭页面触发距离
- */
-export const closePageOffset: number = 80;
-
-/**
  * 默认背景透明度
  */
 export const defaultOpacity: number = 0.6;