hook实现元素放大浏览
前言
最近在看文章的时候看到一个有趣的东西,之前都没有尝试过。讲的是使用hook实现一些动画效果,感觉很有趣,所以也来试试。元素的放大浏览效果
可以用来浏览图片或者是图文元素。
正文
先来看看效果
一共就一个点元素移动到屏幕中间并放大这里我们使用transform
,使用 transform
属性进行元素的变换可以触发 GPU 加速;transform
对元素进行的变换不会改变其他元素的位置,因此不会影响文档流的布局。
设计的思路
- 使用
useRef
来绑定我们需要进行浏览的元素 - 通过setState给元素绑定样式
- 通过元素的页面偏移量、页面大小、元素大小等来确定元素
transformX
和transformY
和图片的缩放比例 - 对于各种情况的处理,比如页面大小变化、页面有滚动条的情况、元素有定位时候的情况,可能还有很多。
接下来由浅入深来分块分析一下大体思路(后边有完整代码)
放大浏览时的遮盖层处理
这里我们要注意不能使用一次hook就创建一个遮盖层,我们可以在初始化的时候判断页面中是否存在一个已经存在的遮盖层,有的话直接使用,没有的话再进行创建
,我们只需要控制显示和隐藏就可以了。
确定放大比例
我们只需要用浏览器的宽高比一下元素的宽高,需要注意的是要取小的值,取大的值元素会超出页面
计算移动距离
分为两种情况,页面出现了滚动条和没有出现滚动条,但是最核心的距离计算公式是不变的,下面这个图也很容易看懂了为什么transformX = screenWidth / 2 - (left + width / 2)
,值得注意的是在放大浏览时,页面尺寸发生变化left和top不能用这个时刻计算出的,而是要使用进入浏览状态时元素的left和top,因为需要计算transformX
的值是最开始还没进入浏览状态时要移动值。transformY
也是一样道理。
元素定位问题
这个比较简单,元素本身的position
值关系到进入浏览状态时元素位置释放不释放问题,除了固定定位,剩下的使用默认值relative
就行。
完整代码和实例
完整的hook代码
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
}
}
使用
function Home(props: any) {
const { frameDom, domStyle } = useMoveScreen()
return (
<div>
<img src={img1} ref={frameDom} style={domStyle} />
</div>
)
}
结尾
感兴趣的可以去试试
转载自:https://juejin.cn/post/7250375035634221115