su-react-mobile-imgview
Version:
A image album for mobile preview, built with reactjs. esay to use and less config.
373 lines (314 loc) • 12.8 kB
JavaScript
/**********************************************************************************************
* This component is designed for Tribe Project in QQ mobile as a Imageviewer
* You can use it as a independent component in your App
*
* @ examples you can find examples in folder examples or README.md
*
* @ param(array) imagelist: The list of images to view
* @ param(bool) disablePinch: Disable pinch function
* @ param(bool) disableRotate: Disable rotate function
* @ param(bool) disableDoubleTap: Disable double tap function
* @ param(function) longTap: Events called after the long tap
* @ param(function) close: the function to close the viewer
*
* Copyright by nemoliao( liaozksysu@gmail.com), nemo is a member of AlloyTeam in Tencent.
*
**********************************************************************************************/
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import styles from './index.css'
import AlloyFinger from './libs/alloyfinger.js'
import Transform from './libs/transform.js'
import { CenterImage } from './components.js'
import Singleton from 'react-singleton'
import './index.less'
const MARGIN = 30
class ImageView extends Component {
static defaultProps = {
gap: MARGIN,
current: 0,
disablePageNum: false,
desc: '',
maxScale: 2
}
static propTypes = {
gap: PropTypes.number,
maxScale: PropTypes.number,
current: PropTypes.number,
imagelist: PropTypes.array.isRequired,
disablePageNum: PropTypes.bool,
disablePinch: PropTypes.bool,
enableRotate: PropTypes.bool,
disableDoubleTap: PropTypes.bool,
longTap: PropTypes.func,
close: PropTypes.func.isRequired,
changeIndex: PropTypes.func,
initCallback: PropTypes.func
}
constructor(props) {
super();
this.arrLength = props.imagelist.length;
this.state = {
current: props.current
}
}
initScale = 1;
screenWidth = window.innerWidth || window.screen.availWidth;
screenHeight = window.innerHeight || window.screen.availHeight;
list = null;
ob = null;
focused = null;
render() {
const { desc, disablePageNum, children, gap } = this.props;
return (
<div className="imageview">
<AlloyFinger
onSingleTap={this.onSingleTap.bind(this)}
onPressMove={this.onPressMove.bind(this)}
onSwipe={this.onSwipe.bind(this)}>
<ul ref="imagelist" className="imagelist">
{
this.props.imagelist.map((item, i) => {
const labelsData = this.props.cleanData? this.props.cleanData(item):null
return (
<li className="imagelist-item" style={{ marginRight: gap + 'px'}} key={"img"+i}>
<AlloyFinger
onPressMove={this.onPicPressMove.bind(this)}
onMultipointStart={this.onMultipointStart.bind(this)}
onLongTap={this.onLongTap.bind(this)}
onPinch={this.onPinch.bind(this)}
onRotate={this.onRotate.bind(this)}
onMultipointEnd={this.onMultipointEnd.bind(this)}
onDoubleTap={this.onDoubleTap.bind(this)}>
<CenterImage id={`view${i}`} className="imagelist-item-img" lazysrc={item.url} index={i} current={this.state.current}/>
</AlloyFinger>
<div className="su-imagelist-item-tags-div" style={{
position: 'absolute',
zIndex: 9999999,
top: 50,
left: 10,
backgroundColor: '#fff',
padding:5,
borderRadius:5,
opacity: 0.6
}}>{labelsData? labelsData:null}
</div>
</li>
)
})
}
</ul>
</AlloyFinger>
{
disablePageNum ? null : <div className="page" ref="page">{ this.state.current + 1 } / { this.arrLength }</div>
}
{
desc ? <div dangerouslySetInnerHTML={{__html: desc}}></div> : null
}
{ children }
</div>
)
}
componentDidMount() {
const { current } = this.state,
{ imagelist, initCallback } = this.props;
this.arrLength = imagelist.length;
this.list = this.refs['imagelist'];
Transform(this.list);
current && this.changeIndex(current, false);
this.bindStyle(current);
initCallback && initCallback();
}
onSingleTap(){
this.props.close && this.props.close();
}
onPressMove(evt){
const { current } = this.state;
this.endAnimation();
if( !this.focused ){
if((current === 0 && evt.deltaX > 0) || (current === this.arrLength - 1 && evt.deltaX < 0)){
this.list.translateX += evt.deltaX / 3;
}else{
this.list.translateX += evt.deltaX;
}
}
evt.preventDefault();
}
onSwipe(evt){
const { direction } = evt;
let { current } = this.state;
if( this.focused ){
return false;
}
switch(direction) {
case 'Left':
current < this.arrLength-1 && ++current && this.bindStyle(current);
break;
case 'Right':
current > 0 && current-- && this.bindStyle(current);
break;
}
this.changeIndex(current)
}
onPicPressMove(evt) {
const { deltaX, deltaY } = evt,
isLongPic = this.ob.getAttribute('long'),
{ scaleX, width } = this.ob;
if(this.ob.scaleX <= 1 || evt.touches.length > 1){
return;
}
if(this.ob && this.checkBoundary(deltaX, deltaY)){
!isLongPic && (this.ob.translateX += deltaX);
this.ob.translateY += deltaY;
if(isLongPic && scaleX * width === this.screenWidth){
this.focused = false;
}else{
this.focused = true;
}
}else {
this.focused = false;
}
// console.log('translate ',this.ob.translateX, this.ob.translateY);
}
onMultipointStart(){
this.initScale = this.ob.scaleX;
}
onPinch(evt){
if( this.props.disablePinch || this.ob.getAttribute('long')){
return false;
}
this.ob.style.webkitTransition = 'cubic-bezier(.25,.01,.25,1)'
const { originX, originY } = this.ob,
originX2 = evt.center.x - this.screenWidth/2 - document.body.scrollLeft,
originY2 = evt.center.y - this.screenHeight/2 - document.body.scrollTop;
this.ob.originX = originX2;
this.ob.originY = originY2;
this.ob.translateX = this.ob.translateX + (originX2 - originX) * this.ob.scaleX;
this.ob.translateY = this.ob.translateY + (originY2 - originY) * this.ob.scaleY;
this.ob.scaleX = this.ob.scaleY = this.initScale * evt.scale;
}
onRotate(evt){
if( !this.props.enableRotate || this.ob.getAttribute('rate') >= 3.5){
return false;
}
this.ob.style.webkitTransition = 'cubic-bezier(.25,.01,.25,1)'
this.ob.rotateZ += evt.angle;
}
onLongTap(){
this.props.longTap && this.props.longTap();
}
onMultipointEnd(evt){
// translate to normal
this.changeIndex(this.state.current);
if(!this.ob){
return;
}
this.ob.style.webkitTransition = '300ms ease';
const { maxScale } = this.props,
isLongPic = this.ob.getAttribute('long');
// scale to normal
if (this.ob.scaleX < 1) {
this.restore(false);
}
if (this.ob.scaleX > maxScale && !isLongPic){
this.setScale(maxScale);
}
// rotate to normal
let rotation = this.ob.rotateZ % 360,
rate = this.ob.getAttribute('rate');
if(rotation < 0){
rotation = 360 + rotation;
}
this.ob.rotateZ = rotation;
if (rotation > 0 && rotation < 45) {
this.ob.rotateZ = 0;
} else if (rotation >= 315) {
this.ob.rotateZ = 360;
} else if (rotation >= 45 && rotation < 135) {
this.ob.rotateZ = 90;
this.setScale(rate);
} else if (rotation >= 135 && rotation < 225) {
this.ob.rotateZ = 180;
} else if (rotation >= 225 && rotation < 315) {
this.ob.rotateZ = 270;
this.setScale(rate);
}
}
onDoubleTap(evt){
if( this.props.disableDoubleTap ){
return false;
}
const { origin } = evt,
originX = origin[0] - this.screenWidth/2 - document.body.scrollLeft,
originY = origin[1] - this.screenHeight/2 - document.body.scrollTop,
isLongPic = this.ob.getAttribute('long');
if(this.ob.scaleX === 1){
!isLongPic && (this.ob.translateX = this.ob.originX = originX);
!isLongPic && (this.ob.translateY = this.ob.originY = originY);
this.setScale(isLongPic ? this.screenWidth / this.ob.width : this.props.maxScale);
}else{
this.ob.translateX = this.ob.originX;
this.ob.translateY = this.ob.originY;
this.setScale(1);
}
// console.log('origin',this.ob.originX, this.ob.originY);
}
bindStyle(current) {
this.setState({ current }, () => {
this.ob && this.restore();
this.ob = document.getElementById(`view${current}`);
if(this.ob && !this.ob.scaleX){
Transform(this.ob)
}
// ease hide page number
const page = this.refs.page;
if(page){
page.classList.remove('hide');
setTimeout(()=>{
page.classList.add('hide');
}, 2000);
}
})
}
changeIndex(current, ease=true) {
ease && (this.list.style.webkitTransition = '300ms ease');
this.list.translateX = -current*(this.screenWidth + this.props.gap);
this.props.changeIndex && this.props.changeIndex(current);
}
setScale(size) {
this.ob.style.webkitTransition = '300ms ease-in-out';
this.ob.scaleX = this.ob.scaleY = size;
}
restore(rotate=true) {
this.ob.translateX = this.ob.translateY = 0;
!!rotate && (this.ob.rotateZ = 0);
this.ob.scaleX = this.ob.scaleY = 1;
this.ob.originX = this.ob.originY = 0;
}
endAnimation() {
this.list.style.webkitTransition = '0';
this.ob && this.ob.style && (this.ob.style.webkitTransition = '0');
}
checkBoundary(deltaX = 0, deltaY = 0) {
// console.log(this.ob.width, this.ob.height);
const { scaleX, translateX, translateY, originX, originY, width, height } = this.ob,
rate = this.ob.getAttribute('rate');
if(scaleX !== 1 || scaleX !== rate){
// include long picture
const rangeLeft = (scaleX - 1) * (width / 2 + originX) + originX,
rangeRight = -(scaleX - 1) * (width / 2 - originX) + originX,
rangeUp = (scaleX - 1) * (height / 2 + originY) + originY,
rangeDown = -(scaleX - 1) * (height / 2 - originY) + originY;
// console.log(rangeLeft, rangeRight, rangeUp, rangeDown);
if(translateX + deltaX <= rangeLeft
&& translateX + deltaX >= rangeRight
&& translateY + deltaY <= rangeUp
&& translateY + deltaY >= rangeDown ) {
return true;
}
}
return false;
}
}
export const SingleImgView = new Singleton(ImageView)
export default ImageView