React查看大图支持滚轮放大、缩小

748 阅读1分钟

使用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
    // eslint-disable-next-line no-unused-expressions
    invertOnMac && navigator.platform.match(/mac/i) && (sY *= -1, pY *= -1) // invert direction on MacOS
    return pY
  }
  const pan = (e) => {
    // eslint-disable-next-line one-var
    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
    }

    // elm.style.backgroundPositionX = (panX * 100) + '%'
    // elm.style.backgroundPositionY = (panY * 100) + '%'
    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 // snap to 100%
    // elm.classList.toggle('zoomed-in', s > 100)
    // elm.classList.toggle('zoomed-out', s < 100)
    // elm.style.backgroundSize = s + '%'

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