import React from 'react'; import PhotoView from './PhotoView'; import SlideWrap from './components/SlideWrap'; import Backdrop from './components/Backdrop'; import { Close, Counter, BannerWrap, BannerRight } from './components/BannerWrap'; import FooterWrap from './components/FooterWrap'; import isMobile from './utils/isMobile'; import { dataType, IPhotoProviderBase } from './types'; import { defaultOpacity, horizontalOffset, maxMoveOffset } from './variables'; interface IPhotoSliderProps extends IPhotoProviderBase { // 图片列表 images: dataType[]; // 图片当前索引 index?: number; // 可见 visible: boolean; // 关闭事件 onClose: (evt?: React.MouseEvent | React.TouchEvent) => void; // 索引改变回调 onIndexChange?: Function; } type PhotoSliderState = { // 偏移量 translateX: number; // 图片当前的 index photoIndex: number; // 图片处于触摸的状态 touched: boolean; // Reach 开始时 x 坐标 lastClientX: number | undefined; // Reach 开始时 y 坐标 lastClientY: number | undefined; // 背景透明度 backdropOpacity: number; // 缩放度 photoScale: number; // 覆盖物可见度 overlayVisible: boolean; }; export default class PhotoSlider extends React.Component< IPhotoSliderProps, PhotoSliderState > { static displayName = 'PhotoSlider'; static defaultProps = { maskClosable: true, bannerVisible: true, introVisible: true, }; 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, overlayVisible: true, }; } 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); } handlePhotoTap = () => { this.setState(prevState => ({ overlayVisible: !prevState.overlayVisible, })); } handlePhotoMaskTap = () => { const { maskClosable, onClose } = this.props; if (maskClosable) { onClose(); this.setState({ overlayVisible: true, }); } } 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: number, clientY: number) => { const { innerWidth, innerHeight } = window; const { images, onIndexChange, onClose } = this.props; const { lastClientX = clientX, lastClientY = clientY, photoIndex, overlayVisible, } = this.state; const offsetClientX = clientX - lastClientX; const offsetClientY = clientY - lastClientY; const singlePageWidth = innerWidth + horizontalOffset; // 当前偏移 let currentTranslateX = -singlePageWidth * photoIndex; let currentPhotoIndex = photoIndex; let isChangeVisible = false; if (Math.abs(offsetClientY) > innerHeight * 0.14) { 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, translateX: currentTranslateX, photoIndex: currentPhotoIndex, lastClientX: undefined, lastClientY: undefined, backdropOpacity: defaultOpacity, photoScale: 1, overlayVisible: isChangeVisible ? true : overlayVisible, }); } render() { const { images, visible, overlay, className, maskClassName, viewClassName, imageClassName, onClose, bannerVisible, introVisible, loadingElement, brokenElement, } = this.props; const { translateX, touched, photoIndex, backdropOpacity, photoScale, overlayVisible, } = this.state; const imageLength = images.length; const transform = `translate3d(${translateX}px, 0px, 0)`; // Overlay const overlayIntro = imageLength ? images[photoIndex].intro : undefined; const overlayStyle = { opacity: +overlayVisible }; if (visible) { const { innerWidth } = window; return ( {bannerVisible ? ( {photoIndex + 1} / {imageLength} ) : undefined} {images .slice( // 加载相邻三张 Math.max(photoIndex - 1, 0), Math.min(photoIndex + 2, imageLength + 1) ) .map((item: dataType, index) => { // 截取之前的索引位置 const realIndex = photoIndex === 0 ? photoIndex + index : photoIndex - 1 + index; return ( 0 ? this.handleReachHorizontalMove : undefined } onReachUp={this.handleReachUp} onPhotoTap={this.handlePhotoTap} onMaskTap={this.handlePhotoMaskTap} 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} /> ); })} {introVisible && overlayIntro ? ( {overlayIntro} ) : undefined} {overlay} ); } return null; } }