react實現圖片預覽元件
阿新 • • 發佈:2019-01-06
功能主要包括:下載圖片、等比縮放、旋轉、全屏拖拽
用法:
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; }