1. 程式人生 > >react實現圖片預覽元件

react實現圖片預覽元件

功能主要包括:下載圖片、等比縮放、旋轉、全屏拖拽

用法:

import ImgPreview from '@/components/ImgPreview'
{/* 圖片預覽元件 */}
<ImgPreview
  visible={previewVisible}  // 是否可見
  onClose={this.closePreview} // 關閉事件
  src={licenceUrl} // 圖片url
  picKey={currentKey} // 下載需要的key,根據自己需要決定
  isAlwaysCenterZoom={false} // 是否總是中心縮放,預設false,若為true,每次縮放圖片都先將圖片重置回螢幕中間
  isAlwaysShowRatioTips={false} // 是否總提示縮放倍數資訊,預設false,只在點選按鈕時提示,若為true,每次縮放圖片都會提示
/>

ImgPreview.js

// message縮放倍數提示,基於antd實現

import './style.less'
import React from 'react'
import config from '@/config'
import {message} from 'antd'

export default class ImgPreview extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      screenHeight: 0,
      screenWidth: 0,
      ratio: 1,
      angle: 0,
      defaultWidth: 'auto',
      defaultHeight: 'auto',
      imgSrc: '',
      posTop: 0,
      posLeft: 0,
      isAlwaysCenterZoom: false, // 是否總是中心縮放
      isAlwaysShowRatioTips: false, // 是否總是顯示縮放倍數資訊,預設點選按鈕縮放時才顯示
      flags: false,
      isDraged: false,
      position: {
        x: 0,
        y: 0
      },
      nx: '',
      ny: '',
      dx: '',
      dy: '',
      xPum: '',
      yPum: ''
    }
    this.percent = 100
  }

  componentDidMount() {
    this.setState({
      screenWidth: window.screen.availWidth,
      screenHeight: window.screen.availHeight,
      ratio: 1,
      angle: 0
    }, () => {
      this.getImgSize()
    })
  }

  componentWillReceiveProps (nextProps) {
    this.setState({
      imgSrc: nextProps.src,
      isAlwaysCenterZoom: nextProps.isAlwaysCenterZoom,
      isAlwaysShowRatioTips: nextProps.isAlwaysShowRatioTips
    }, () => {
      this.getImgSize()
    })
  }

  // 獲取預覽圖片的預設寬高和位置
  getImgSize = () => {
    let {ratio, isDraged, isAlwaysCenterZoom} = this.state
    let posTop = 0
    let posLeft = 0
    // 圖片原始寬高
    let originWidth = this.originImgEl.width
    let originHeight = this.originImgEl.height
    // 預設最大寬高
    let maxDefaultWidth = 540
    let maxDefaultHeight = 320
    // 預設展示寬高
    let defaultWidth = 0
    let defaultHeight = 0
    if (originWidth > maxDefaultWidth || originHeight > maxDefaultHeight) {
      if (originWidth / originHeight > maxDefaultWidth / maxDefaultHeight) {
        defaultWidth = maxDefaultWidth
        defaultHeight = Math.round(originHeight * (maxDefaultHeight / maxDefaultWidth))
        posTop = (defaultHeight * ratio / 2) * -1
        posLeft = (defaultWidth * ratio / 2) * -1
      } else {
        defaultWidth = Math.round(maxDefaultHeight * (originWidth / originHeight))
        defaultHeight = maxDefaultHeight
        posTop = (defaultHeight * ratio / 2) * -1
        posLeft = (defaultWidth * ratio / 2) * -1
      }
    } else {
      defaultWidth = originWidth
      defaultHeight = originHeight
      posTop = (defaultWidth * ratio / 2) * -1
      posLeft = (defaultHeight * ratio / 2) * -1
    }

    if (isAlwaysCenterZoom) {
      this.setState({
        posTop: posTop,
        posLeft: posLeft,
        defaultWidth: defaultWidth * ratio,
        defaultHeight: defaultHeight * ratio
      })
    } else {
      // 若拖拽改變過位置,則在縮放操作時不改變當前位置
      if (isDraged) {
        this.setState({
          defaultWidth: defaultWidth * ratio,
          defaultHeight: defaultHeight * ratio
        })
      } else {
        this.setState({
          posTop: posTop,
          posLeft: posLeft,
          defaultWidth: defaultWidth * ratio,
          defaultHeight: defaultHeight * ratio
        })
      }
    }
  }

  // 下載
  download = () => {
    window.open(config.apiHost + '/downloadFromOss?key=' + this.props.picKey)
  }

  // 放大
  scaleBig = (type = 'click') => {
    let {ratio, isAlwaysShowRatioTips} = this.state
    ratio += 0.15
    this.percent += 15
    this.setState({
      ratio: ratio
    }, () => {
      this.getImgSize()
    })
    if (isAlwaysShowRatioTips) {
      message.info(`縮放比例:${this.percent}%`, 0.2)
    } else {
      if (type === 'click') {
        message.info(`縮放比例:${this.percent}%`, 0.2)
      }
    }
  }

  // 縮小
  scaleSmall = (type = 'click') => {
    let {ratio, isAlwaysShowRatioTips} = this.state
    ratio -= 0.15
    if (ratio <= 0.1) {
      ratio = 0.1
    }
    if (this.percent - 15 > 0) {
      this.percent -= 15
    }
    this.setState({
      ratio: ratio
    }, () => {
      this.getImgSize()
    })
    if (isAlwaysShowRatioTips) {
      message.info(`縮放比例:${this.percent}%`, 0.2)
    } else {
      if (type === 'click') {
        message.info(`縮放比例:${this.percent}%`, 0.2)
      }
    }
  }

  // 滾輪縮放
  wheelScale = (e) => {
    e.preventDefault()
    if (e.deltaY > 0) {
      this.scaleBig('wheel')
    } else {
      this.scaleSmall('wheel')
    }
  }

  // 旋轉
  retate = () => {
    let {angle} = this.state
    angle += 90
    this.setState({
      angle: angle
    })
  }

  // 按下獲取當前資料
  mouseDown = (event) => {
    let touch
    if (event.touches) {
      touch = event.touches[0]
    } else {
      touch = event
    }
    let position = {
      x: touch.clientX,
      y: touch.clientY
    }
    this.setState({
      flags: true,
      position: position,
      dx: this.imgEl.offsetLeft,
      dy: this.imgEl.offsetTop
    })
  }

  mouseMove = (event) => {
    let {dx, dy, position, flags} = this.state
    if (flags) {
      event.preventDefault()
      let touch
      if (event.touches) {
        touch = event.touches[0]
      } else {
        touch = event
      }
      this.setState({
        isDraged: true,
        nx: touch.clientX - position.x,
        ny: touch.clientY - position.y,
        xPum: dx + touch.clientX - position.x,
        yPum: dy + touch.clientY - position.y
      }, () => {
        this.imgEl.style.left = this.state.xPum + 'px'
        this.imgEl.style.top = this.state.yPum + 'px'
      })
    }
  }

  mouseUp = () => {
    this.setState({
      flags: false
    })
  }

  mouseOut = () => {
    this.setState({
      flags: false
    })
  }

  // 關閉預覽
  closePreview = () => {
    let {onClose} = this.props
    this.setState({
      ratio: 1,
      angle: 0,
      defaultWidth: 'auto',
      defaultHeight: 'auto',
      imgSrc: '',
      posTop: 0,
      posLeft: 0,
      flags: false,
      isDraged: false,
      position: {
        x: 0,
        y: 0
      },
      nx: '',
      ny: '',
      dx: '',
      dy: '',
      xPum: '',
      yPum: ''
    }, () => {
      this.getImgSize()
      this.percent = 100
      onClose()
    })
  }

  render() {
    let {screenWidth, screenHeight, posLeft, posTop, angle, imgSrc} = this.state
    let {visible} = this.props
    return (
      <div className={'preview-wrapper' + (visible ? ' show' : ' hide')} style={{width: screenWidth, height: screenHeight}}>
        <i onClick={() => {this.closePreview()}} className='iconfont icon-icon-test31'></i>
        <div className='img-container'>
          <img className='image'
            width={this.state.defaultWidth}
            height={this.state.defaultHeight}
            onWheel={this.wheelScale}
            style={{transform: `rotate(${angle}deg)`, top: posTop, left: posLeft}}
            onMouseDown={this.mouseDown}
            onMouseMove={this.mouseMove}
            onMouseUp={this.mouseUp}
            onMouseOut={this.mouseOut}
            draggable='false'
            src={imgSrc} ref={(img) => {this.imgEl = img}} alt="預覽圖片"/>
        </div>
        <img className='origin-image' src={imgSrc} ref={(originImg) => {this.originImgEl = originImg}} alt="預覽圖片"/>
        <div className='operate-con'>
          <div onClick={this.download} className='operate-btn'>
            <i className='iconfont icon-icon-test10'></i>
            <span>下載</span>
          </div>
          <div onClick={() => {this.scaleBig('click')}} className='operate-btn'>
            <i className='iconfont icon-icon-test33'></i>
            <span>放大</span>
          </div>
          <div onClick={() => {this.scaleSmall('click')}} className='operate-btn'>
            <i className='iconfont icon-icon-test35'></i>
            <span>縮小</span>
          </div>
          <div onClick={this.retate} className='operate-btn'>
            <i className='iconfont icon-icon-test34'></i>
            <span>旋轉</span>
          </div>
        </div>
      </div>
    )
  }
}

style.less

.preview-wrapper{
  position: fixed;
  top: 0;
  left: 0;
  background: rgba(0,0,0,0.6);
  z-index: 999;
  display: flex;
  flex-direction: column;
  align-items: center;
  .icon-icon-test31{
    position: fixed;
    top: 60px;
    right: 60px;
    color: #ffffff;
    transform:rotate(45deg);
    font-size: 40px;
    margin-right: 0;
  }
  .img-container{
    width: 1px;
    height: 1px;
    position: fixed;
    top: 50%;
    left: 50%;
    .image{
      position: absolute;
      cursor: pointer;
    }
  }
  .origin-image{
    position: relative;
    z-index: -1;
    visibility: hidden;
  }
  .operate-con{
    position: fixed;
    bottom:10%;
    left:0;
    right: 0;
    margin: 0 auto;
    width: 400px;
    height: 70px;
    background-color: #37474f;
    border-radius: 100px;
    opacity: 0.8;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 15px 55px;
    box-sizing: border-box;
    .operate-btn{
      width: 40px;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      .iconfont{
        color: #ffffff;
        font-size: 15px;
        margin-right: 0;
      }
      span{
        color: white;
        font-size: 12px;
        margin-top: 15px;
      }
    }
  }
}
.preview-wrapper.show{
  display: flex;
}
.preview-wrapper.hide{
  display: none;
}