使用useZoomPan
import { useEffect, useRef, useState } from 'react'
export default function useZoomPan (ref, {
minScale = 75,
maxScale = 300,
snap = 9,
invertOnMac = false
} = {}) {
const currentScale = useRef(100)
const [backgroundSize, setBackgroundSize] = useState('')
const [backgroundPositionX, setBackgroundPositionX] = useState('')
const [backgroundPositionY, setBackgroundPositionY] = useState('')
const normalizeWheel = (ev) => {
var sY = 0
if ('detail' in ev) sY = ev.detail
if ('wheelDelta' in ev) sY = -ev.wheelDelta / 120
if ('wheelDeltaY' in ev) sY = -ev.wheelDeltaY / 120
var pY = 10 * sY
if ('deltaY' in ev) pY = ev.deltaY
if (pY && ev.deltaMode) pY *= (ev.deltaMode === 1) ? 40 : 800
if (pY && !sY) sY = pY < 1 ? -1 : 1
invertOnMac && navigator.platform.match(/mac/i) && (sY *= -1, pY *= -1)
return pY
}
const pan = (e) => {
var panX, panY, pad = 20
if (currentScale.current >= 100) {
panX = (e.offsetX - pad) / (e.target.clientWidth - (pad * 2))
panY = (e.offsetY - pad) / (e.target.clientHeight - (pad * 2))
} else {
panX = 0.5
panY = 0.5
}
setBackgroundPositionX((panX * 100) + '%')
setBackgroundPositionY((panY * 100) + '%')
}
const zoom = (e) => {
e.preventDefault()
var y = normalizeWheel(e)
var step = y * (maxScale / 500)
currentScale.current += step * -1
if (currentScale.current > maxScale) currentScale.current = maxScale
if (currentScale.current < minScale) currentScale.current = minScale
var s = Math.abs(100 - currentScale.current) > snap ? currentScale.current : 100
setBackgroundSize(s + '%')
}
useEffect(() => {
const { current: elm } = ref
elm.addEventListener('wheel', zoom)
elm.addEventListener('mousemove', pan)
return () => {
elm.removeEventListener('wheel', zoom)
elm.removeEventListener('mousemove', pan)
}
}, [ref])
return { backgroundSize, backgroundPositionX, backgroundPositionY }
}
调用地方
import React, { useRef } from 'react'
import useZoomPan from '@utils/useZoomPan'
import './index.scss'
export default function ContentImagePreview ({ visible, src, onClose }) {
const cover = useRef()
const { backgroundSize, backgroundPositionX, backgroundPositionY } = useZoomPan(cover)
return (
<div className='content-image-preview' style={{ display: visible ? 'block' : 'none' }}>
<div className="content-image-preview__content">
<div ref={cover} className="content-image-preview__content__cover"
style={{ backgroundImage: `url(${src})`, transform: `scale(${parseFloat(backgroundSize) / 100})`, backgroundPositionX, backgroundPositionY }}>
</div>
</div>
<div className="content-image-preview__close" onClick={onClose}>
<img src='https://flashmallfs.qiwangcheng.com/upload/202304/2023041309393650845.png' />
</div>
</div>
)
}
css代码
.content-image-preview {
background: rgba(0,0,0,.7);
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 2001;
&__close {
position: absolute;
top: 31px;
right: 31px;
width: 32px;
height: 32px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
border: none;
font-size: 16px;
border-radius: 100%;
color: #666;
}
&__content {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
height: 100%;
&__cover {
position: relative;
width: 600px;
height: 600px;
background: no-repeat 50%/contain;
}
}
}