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
A total of one point element is moved to the middle of the screen and zoomed in. Here we use transform
, using transform
the attribute to transform the element can trigger GPU acceleration; transform
the 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
- Use
useRef
to bind the elements we need to browse - Bind styles to elements through setState
transformX
Determine the zoom ratio of elements andtransformY
pictures by the page offset of the element, page size, element size, etc.- 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
Here 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.
Determine the magnification
We 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
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 transformX
is the value that needs to be moved before entering the browsing state. transformY
The same is true.
Element Positioning Issues
This is relatively simple. The value of the element itself position
is 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
.
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