123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- 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 (
- <SlideWrap className={className}>
- <Backdrop
- className={maskClassName}
- style={{ background: `rgba(0, 0, 0, ${backdropOpacity})` }}
- />
- {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 (
- <PhotoView
- key={
- isStrItem
- ? (item as string) + realIndex
- : (item as dataType).dataKey
- }
- src={isStrItem ? (item as string) : (item as dataType).src}
- onReachTopMove={this.handleReachVerticalMove}
- onReachBottomMove={this.handleReachVerticalMove}
- onReachRightMove={
- realIndex < imageLength - 1
- ? this.handleReachHorizontalMove
- : undefined
- }
- onReachLeftMove={
- realIndex > 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}
- </SlideWrap>
- );
- }
- return null;
- }
- }
|