Hook realizes zooming in on elements

foreword

I saw an interesting thing while reading an article recently, but I have never tried it before. It is about using hooks to achieve some animation effects, which is very interesting, so try it too. 元素的放大浏览效果It can be used to browse pictures or graphic elements.

text

Let's see the effect first

Animation 22.gif

A total of one point element is moved to the middle of the screen and zoomed in. Here we use transform, using transformthe attribute to transform the element can trigger GPU acceleration; transformthe transformation of the element will not change the position of other elements, so it will not affect the layout of the document flow.

design ideas

  1. Use useRefto bind the elements we need to browse
  2. Bind styles to elements through setState
  3. transformXDetermine the zoom ratio of elements and transformYpictures by the page offset of the element, page size, element size, etc.
  4. For the processing of various situations, such as page size changes, the situation where the page has scroll bars, and the situation when elements are positioned, there may be many more.

Next, analyze the general idea in blocks from the shallower to the deeper (there is a complete code behind)

Overlay processing when zooming in

1.pngHere we should pay attention not to use a hook to create a covering layer. We can judge whether there is an existing covering layer in the page during initialization. We only need to control the 有的话直接使用,没有的话再进行创建display and hiding.

1.png

Determine the magnification

1.pngWe only need to use the aspect ratio of the browser to check the width and height of the element. It should be noted that要取小的值,取大的值元素会超出页面

Calculate movement distance

1.png

It can be divided into two cases, the scroll bar appears on the page and the scroll bar does not appear, but the core distance calculation formula remains unchanged. The following figure is also easy to understand why. It is worth noting that when you zoom in and browse, the transformX = screenWidth / 2 - (left + width / 2)page The left and top of the size change cannot be calculated at this moment, but the left and top of the element when entering the browsing state should be used, because the value that needs to be calculated transformXis the value that needs to be moved before entering the browsing state. transformYThe same is true.

image.png

1.png

Element Positioning Issues

This is relatively simple. The value of the element itself positionis related to whether the position of the element is released or not when it enters the browsing state. Except for fixed positioning, the rest use the default value relative.

1.png

Complete code and examples

Complete hook code

import React, { useState, useEffect, useRef, useCallback } from 'react'

enum Position {
  Static = "static",
  Relative = "relative",
  Absolute = "absolute",
  Fixed = "fixed",
  Sticky = "sticky"
}

export default function useMoveScreen() {
  /** 是否是放大浏览状态*/
  const isScreen = useRef(false)
  /** 放大浏览元素*/
  const [domStyle, setDomStyle] = useState<React.CSSProperties>({});
  const initDomInfo = useRef({
    height: 0,
    width: 0
  })
  const position = useRef<Position>(Position.Relative)
  const frameDom = useRef<any>(null)
  const coveringAgentDom = useRef<null | Element>(null)

  /** 放大浏览之前元素的页面中的left和top*/
  const lastOffset = useRef({
    top: 0,
    left: 0
  })

  useEffect(() => {
    if (frameDom.current === null) {
      return
    }
    onInitDom()

    //判断是否创建遮盖层
    let coveringAgentElement = window.document.querySelector('.coveringAgent')
    if (coveringAgentElement) {
      coveringAgentDom.current = coveringAgentElement as HTMLDivElement;
    } else {
      let element = createCoveringAgent()
      coveringAgentDom.current = element;
      window.document.body.appendChild(element)
    }

    //设置定位
    let domPosition = getComputedStyle(frameDom.current).position
    if (domPosition === Position.Fixed) {
      position.current = Position.Fixed
    }

    frameDom.current.style.transition = "all .2s linear";
    frameDom.current.addEventListener("click", moveToScreen);
    window.addEventListener('resize', changeSize);
    return () => {
      window.removeEventListener('resize', changeSize);
      (frameDom.current as HTMLDivElement).removeEventListener("click", moveToScreen);
    }
  }, [])

  /** 初始化dom信息*/
  const onInitDom = useCallback(() => {
    let boundingClientRect = frameDom.current.getBoundingClientRect();
    initDomInfo.current.height = boundingClientRect.height
    initDomInfo.current.width = boundingClientRect.width
  }, [])

  /** 点击Dom放大浏览 */
  const moveToScreen = useCallback(() => {
    let enlargedScale = getEnlargedScale();
    let { transformX, transformY } = getDisplaceDistance()
    if (isScreen.current === false) {
      (coveringAgentDom.current as HTMLDivElement).style.display = 'block';

      const { x, y } = frameDom.current.getBoundingClientRect();
      lastOffset.current.left = x;
      lastOffset.current.top = y;

      document.documentElement.style.overflow = "hidden";
      setDomStyle({
        position: position.current,
        transform: `translate(${transformX}px,${transformY}px) scale(${enlargedScale},${enlargedScale})`,
        zIndex: 99,
      });
      isScreen.current = true
    } else {
      document.documentElement.style.overflow = "auto";
      (coveringAgentDom.current as HTMLDivElement).style.display = 'none';
      setDomStyle({
        transform: `translate(0px,0px) scale(1,1)`,
      });
      isScreen.current = false
    }
  }, [])

  /** 计算放大比例*/
  const getEnlargedScale = useCallback(() => {
    const { height, width } = initDomInfo.current
    const screenWidth = window.innerWidth;
    const screenHeight = window.innerHeight;
    let heightRatio = screenHeight / height;
    let widthRatio = screenWidth / width;

    return heightRatio > widthRatio ? widthRatio * 0.8 : heightRatio * 0.8;
  }, [])

  /** 获取移动距离 */
  const getDisplaceDistance = useCallback((isSizeChange: boolean = false) => {
    if (frameDom.current === null) {
      return {
        transformX: 0,
        transformY: 0
      }
    }

    const { left, top } = frameDom.current.getBoundingClientRect();
    const { width, height } = initDomInfo.current;
    const screenWidth = window.innerWidth;
    const screenHeight = window.innerHeight;

    let transformX = screenWidth / 2 - (left + width / 2)
    let transformY = screenHeight / 2 - (top + height / 2)

    //left和top要使用进入浏览状态时的left和top
    if (isSizeChange === true) {
      transformX = screenWidth / 2 - (lastOffset.current.left + width / 2)
      transformY = screenHeight / 2 - (lastOffset.current.top + height / 2)
    }

    return {
      transformX,
      transformY
    }
  }, [])

  /** 创建遮盖层*/
  const createCoveringAgent = useCallback(() => {
    const element = document.createElement('div')
    element.className = 'coveringAgent'
    element.style.position = 'fixed'
    element.style.zIndex = '98'
    element.style.top = '0'
    element.style.left = '0'
    element.style.height = '100vh'
    element.style.width = '100vw'
    element.style.backdropFilter = 'blur(10px)'
    element.style.color = '#fff'
    element.style.boxShadow = '0 0 30px 10px rgba(0, 0, 0, .3)'
    element.style.display = 'none'
    return element
  }, [])


  /** 浏览器尺寸发生变化*/
  const changeSize = useCallback(() => {
    if (!isScreen.current) {
      return
    }
    let { transformX, transformY } = getDisplaceDistance(true)
    let enlargedScale = getEnlargedScale();
    setDomStyle({
      position: position.current,
      zIndex: 99,
      transform: `translate(${transformX}px,${transformY}px) scale(${enlargedScale},${enlargedScale})`,
    });
  }, [])

  return {
    frameDom,
    domStyle
  }
}

use

function Home(props: any) {
  const { frameDom, domStyle } = useMoveScreen()
  return (
    <div>
      <img src={img1} ref={frameDom} style={domStyle} />
    </div>
  )
}

end

If you are interested, you can try it

Guess you like

Origin juejin.im/post/7250375035634221115