react实现图片预览组件

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Zckguiying/article/details/85309720

功能主要包括:下载图片、等比缩放、旋转、全屏拖拽

用法:

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;
}

猜你喜欢

转载自blog.csdn.net/Zckguiying/article/details/85309720