import React from 'react'; import PhotoView from './PhotoView'; import SlideWrap from './components/SlideWrap'; import Backdrop from './components/Backdrop'; import { dataType } from './types'; import { maxMoveOffset, defaultOpacity, horizontalOffset } from './variables'; interface IPhotoSliderProps { // 图片列表 images: (string | dataType)[]; // 图片当前索引 index?: number; // 可见 visible: boolean; // 关闭事件 onClose: Function; // 索引改变回调 onIndexChange?: Function; // 自定义容器 overlay?: React.ReactNode; // className className?: string; // 遮罩 className maskClassName?: string; // 图片容器 className viewClassName?: string; // 图片 className imageClassName?: string; // 自定义 loading loadingElement?: JSX.Element; // 加载失败 Element brokenElement?: JSX.Element; } type PhotoSliderState = { // 偏移量 translateX: number; // 图片当前的 index photoIndex: number; // 图片处于触摸的状态 touched: boolean; // Reach 开始时 x 坐标 lastClientX: number | undefined; // Reach 开始时 y 坐标 lastClientY: number | undefined; // 背景透明度 backdropOpacity: number; // 缩放度 photoScale: number; }; export default class PhotoSlider extends React.Component< IPhotoSliderProps, PhotoSliderState > { static displayName = 'PhotoSlider'; static getDerivedStateFromProps(nextProps, prevState) { if ( nextProps.index !== undefined && nextProps.index !== prevState.photoIndex ) { return { photoIndex: nextProps.index, translateX: -(window.innerWidth + horizontalOffset) * nextProps.index, }; } return null; } constructor(props) { super(props); this.state = { translateX: 0, photoIndex: 0, touched: false, lastClientX: undefined, lastClientY: undefined, backdropOpacity: defaultOpacity, photoScale: 1, }; } componentDidMount() { const { index = 0 } = this.props; this.setState({ translateX: index * -(window.innerWidth + horizontalOffset), photoIndex: index, }); window.addEventListener('resize', this.handleResize); } componentWillUnmount() { window.removeEventListener('resize', this.handleResize); } handleResize = () => { const { innerWidth } = window; this.setState(({ photoIndex }) => { return { translateX: -(innerWidth + horizontalOffset) * photoIndex, lastClientX: undefined, lastClientY: undefined, }; }); } handleReachVerticalMove = (clientX, clientY) => { this.setState(({ lastClientY, backdropOpacity }) => { if (lastClientY === undefined) { return { touched: true, lastClientY: clientY, backdropOpacity, photoScale: 1, }; } const offsetClientY = Math.abs(clientY - lastClientY); return { touched: true, lastClientY, backdropOpacity: Math.max( Math.min(defaultOpacity, defaultOpacity - offsetClientY / 100 / 2), 0, ), photoScale: Math.max(Math.min(1, 1 - offsetClientY / 100 / 10), 0.6), }; }); } handleReachHorizontalMove = (clientX) => { const { innerWidth } = window; this.setState(({ lastClientX, translateX, photoIndex }) => { if (lastClientX === undefined) { return { touched: true, lastClientX: clientX, translateX, }; } const offsetClientX = clientX - lastClientX; return { touched: true, lastClientX: lastClientX, translateX: -(innerWidth + horizontalOffset) * photoIndex + offsetClientX, }; }); } handleReachUp = (clientX, clientY) => { const { innerWidth, innerHeight } = window; const { images, onIndexChange, onClose } = this.props; const { lastClientX = clientX, lastClientY = clientY, photoIndex } = this.state; const offsetClientX = clientX - lastClientX; const offsetClientY = clientY - lastClientY; const singlePageWidth = innerWidth + horizontalOffset; // 当前偏移 let currentTranslateX = -singlePageWidth * photoIndex; let currentPhotoIndex = photoIndex; if (Math.abs(offsetClientY) > innerHeight * 0.14) { 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, translateX: currentTranslateX, photoIndex: currentPhotoIndex, lastClientX: undefined, lastClientY: undefined, backdropOpacity: defaultOpacity, photoScale: 1, }); } render() { const { images, visible, overlay, className, maskClassName, viewClassName, imageClassName, loadingElement, brokenElement, } = this.props; const { translateX, touched, photoIndex, backdropOpacity, photoScale, } = this.state; const imageLength = images.length; const transform = `translate3d(${translateX}px, 0px, 0)`; if (visible) { const { innerWidth } = window; return ( {images .slice( // 加载相邻三张 Math.max(photoIndex - 1, 0), Math.min(photoIndex + 2, imageLength + 1) ) .map((item: string | dataType, index) => { const isStrItem = typeof item === 'string'; // 截取之前的索引位置 const realIndex = photoIndex === 0 ? photoIndex + index : photoIndex - 1 + index; return ( 0 ? this.handleReachHorizontalMove : undefined } onReachUp={this.handleReachUp} photoScale={photoIndex === realIndex ? photoScale : 1} wrapClassName={viewClassName} className={imageClassName} style={{ left: `${(innerWidth + horizontalOffset) * realIndex}px`, WebkitTransform: transform, transform, transition: touched ? undefined : 'transform 0.6s cubic-bezier(0.25, 0.8, 0.25, 1)', }} loadingElement={loadingElement} brokenElement={brokenElement} /> ); })} {overlay} ); } return null; } }