import React from 'react'; import { Motion, spring } from 'react-motion'; import throttle from 'lodash.throttle'; import Photo from './Photo'; import { PhotoContainer, Backdrop } from './StyledElements'; import { isMobile, getMultipleTouchPosition, getPositionOnMoveOrScale, slideToSuitableOffset, } from './utils'; import { defaultAnimationConfig } from './variables'; import { animationType } from './types'; export interface IPhotoViewProps { src: string; } type PhotoViewState = { // 图片 X 偏移量 x: number; // 图片 y 偏移量 y: number; // 图片缩放程度 scale: number; // 图片处于触摸的状态 touched: boolean; // 触摸开始时 x 原始坐标 pageX: number; // 触摸开始时 y 原始坐标 pageY: number; // 触摸开始时图片 x 偏移量 lastX: number; // 触摸开始时图片 y 偏移量 lastY: number; // 多指触控间距 lastTouchLength: number; // 触摸开始时时间 touchedTime: number; } & animationType; export default class PhotoView extends React.Component< IPhotoViewProps, PhotoViewState > { static displayName = 'PhotoView'; readonly state = { x: 0, y: 0, scale: 1, touched: false, pageX: 0, pageY: 0, lastX: 0, lastY: 0, originX: 0, originY: 0, originTranslateX: 0, originTranslateY: 0, touchedTime: 0, lastTouchLength: 0, animation: defaultAnimationConfig, }; private photoRef; constructor(props) { super(props); this.handleMove = throttle(this.handleMove, 8); } componentDidMount() { if (isMobile) { window.addEventListener('touchmove', this.handleTouchMove); window.addEventListener('touchend', this.handleTouchEnd); } else { window.addEventListener('mousemove', this.handleMouseMove); window.addEventListener('mouseup', this.handleMouseUp); } } componentWillUnmount() { if (isMobile) { window.removeEventListener('touchmove', this.handleTouchMove); window.removeEventListener('touchend', this.handleTouchEnd); } else { window.removeEventListener('mousemove', this.handleMouseMove); window.removeEventListener('mouseup', this.handleMouseUp); } } handleStart = (pageX: number, pageY: number, touchLength: number = 0) => { this.setState(prevState => ({ touched: true, pageX, pageY, lastX: prevState.x, lastY: prevState.y, lastTouchLength: touchLength, touchedTime: Date.now(), })); } handleMove = (newPageX: number, newPageY: number, touchLength: number = 0) => { if (this.state.touched) { this.setState(({ pageX, pageY, lastX, lastY, scale, lastTouchLength }) => { return { lastTouchLength: touchLength, ...getPositionOnMoveOrScale({ x: newPageX - pageX + lastX, y: newPageY - pageY + lastY, pageX, pageY, fromScale: scale, toScale: scale + (touchLength - lastTouchLength) / (window.innerHeight / 2) * scale, }), }; }); } } handleDoubleClick = (e) => { const { pageX, pageY } = e; this.setState(({ x, y, scale }) => { return { pageX, pageY, ...getPositionOnMoveOrScale({ x, y, pageX, pageY, fromScale: scale, toScale: scale !== 1 ? 1 : 2, }), animation: defaultAnimationConfig, }; }); } handleWheel = (e) => { const { pageX, pageY, deltaY } = e; this.setState(({ x, y, scale }) => { return { pageX, pageY, ...getPositionOnMoveOrScale({ x, y, pageX, pageY, fromScale: scale, toScale: scale - deltaY / 100 / 2, }), animation: defaultAnimationConfig, }; }); } handleTouchStart = e => { if (e.touches.length >= 2) { const { pageX, pageY, touchLength } = getMultipleTouchPosition(e); this.handleStart(pageX, pageY, touchLength); } else { const { pageX, pageY } = e.touches[0]; this.handleStart(pageX, pageY); } } handleMouseDown = e => { this.handleStart(e.pageX, e.pageY); } handleTouchMove = e => { e.preventDefault(); if (e.touches.length >= 2) { const { pageX, pageY, touchLength } = getMultipleTouchPosition(e); this.handleMove(pageX, pageY, touchLength); } else { const { pageX, pageY } = e.touches[0]; this.handleMove(pageX, pageY); } } handleMouseMove = e => { e.preventDefault(); this.handleMove(e.pageX, e.pageY); } handleUp = (newPageX: number, newPageY: number) => { const { width, height } = this.photoRef.state; this.setState(({ x, y, lastX, lastY, scale, touchedTime, pageX, pageY, }) => { const hasMove = pageX !== newPageX || pageY !== newPageY; return { touched: false, ...slideToSuitableOffset({ x, y, lastX, lastY, width, height, scale, touchedTime, hasMove, }), }; }); } handleTouchEnd = (e) => { const { pageX, pageY } = e.changedTouches[0]; this.handleUp(pageX, pageY); } handleMouseUp = (e) => { const { pageX, pageY } = e; this.handleUp(pageX, pageY); } handlePhotoRef = (ref) => { this.photoRef = ref; } render() { const { src } = this.props; const { x, y, scale, touched, animation } = this.state; const style = { currX: touched ? x : spring(x, animation), currY: touched ? y : spring(y, animation), currScale: touched ? scale : spring(scale, animation), }; return ( {({ currX, currY, currScale }) => { const transform = `translate3d(${currX}px, ${currY}px, 0) scale(${currScale})`; return ( ); }} ); } }