用react开发一个antd mobile组件,页面上有多个高度不等的div元素,每个div底部都有一个「核对」按钮。当div的高度大于屏幕高度的时候,就要将这个div底部的「核对」按钮锁定在页面底部,方便用户操作。当用户滚动页面,这个div的底部进入屏幕的时候,就要取消「核对」按钮的锁定,要它回归原处,跟随这个div进行滚动。当第二个div进入屏幕50%的时候,在此将它的「核对」按钮锁定在页面上。你帮我用监听滚动的方式实现这个功能,所有的代码都写在一个文件里面,组件的名字叫:MaterialVerificationSon
如果出现按钮取消锁定有抖动,是样式问题,用 transform: isFixed ? 'translateX(-2px)' : 'none',可以解决
js
import { Button } from 'antd-mobile';
import React, { useEffect, useRef, useState } from 'react';
// import { useLocation } from '@tanstack/react-location';
export const MaterialVerificationSon: React.FC = () => {
// 示例数据:包含不同高度的div内容
const [items] = useState([
{ id: 1, content: '这是第一个材料项,高度适中', height: 780 },
{ id: 2, content: '这是第二个材料项,高度较高,内容较多,需要滚动查看', height: 800 },
{ id: 3, content: '这是第三个材料项,高度非常高,内容非常多,需要大量滚动', height: 1200 },
{ id: 4, content: '这是第四个材料项,高度适中', height: 400 },
{ id: 5, content: '这是第五个材料项,高度较高', height: 900 },
]);
const contentRef = useRef<HTMLDivElement>(null);
// 存储每个元素的ref
const itemRefs = useRef<Array<HTMLDivElement | null>>([]);
// 存储每个按钮的锁定状态
const [lockedButtons, setLockedButtons] = useState<Record<number, boolean>>({});
// 获取屏幕高度
const getScreenHeight = () => window.innerHeight;
// 检查元素是否在视口中
const isElementInViewport = (element: HTMLElement) => {
const rect = element.getBoundingClientRect();
return rect.top < getScreenHeight() && rect.bottom > 0;
};
// 检查元素底部是否在视口内
const isElementBottomInViewport = (element: HTMLElement) => {
const rect = element.getBoundingClientRect();
const elementBottom = rect.bottom;
return elementBottom <= getScreenHeight() && elementBottom > 0;
};
// 检查元素是否至少有50%在视口内
const isElementHalfVisible = (element: HTMLElement) => {
const rect = element.getBoundingClientRect();
const elementHeight = rect.height;
const visibleHeight = Math.min(Math.max(getScreenHeight() - rect.top, 0), elementHeight);
return visibleHeight / elementHeight >= 0.5;
};
// 检查元素是否过高(超过屏幕高度)
const isElementTooTall = (element: HTMLElement) => {
return element.scrollHeight > getScreenHeight();
};
// 处理滚动事件
const handleScroll = () => {
const newLockedButtons: Record<number, boolean> = {};
items.forEach((item, index) => {
const element = itemRefs.current[index];
if (!element) return;
const elementTooTall = isElementTooTall(element); //是否大于屏幕高度
const elementInViewport = isElementInViewport(element); //是不是在视口里面
const elementBottomInViewport = isElementBottomInViewport(element); //检查元素底部是否在视口内
const elementHalfVisible = isElementHalfVisible(element); //视口50%
console.log(elementInViewport, index);
if (elementInViewport && elementTooTall && elementHalfVisible) {
newLockedButtons[item.id] = true;
}
if (elementBottomInViewport) {
newLockedButtons[item.id] = false;
}
});
setLockedButtons(newLockedButtons);
};
console.log(lockedButtons, 9999);
// 添加滚动事件监听器
useEffect(() => {
if (contentRef.current) {
contentRef.current.addEventListener('scroll', handleScroll);
// 初始执行一次
handleScroll();
}
return () => {
if (contentRef.current) {
contentRef.current.removeEventListener('scroll', handleScroll);
}
};
}, []);
// 核对按钮点击处理
const handleVerify = (id: number) => {
console.log(`核对按钮被点击,ID: ${id}`);
// 这里可以添加具体的核对逻辑
};
return (
<div style={{ padding: '16px', height: '100vh', overflowY: 'auto' }} ref={contentRef}>
<h2>材料核对页面</h2>
{items.map((item, index) => (
<div
key={item.id}
ref={el => (itemRefs.current[index] = el)}
style={{
border: '1px solid #ddd',
borderRadius: '8px',
padding: '16px',
marginBottom: '16px',
overflowY: 'auto',
}}
>
<h3>材料项 {item.id}</h3>
<p>{item.content}</p>
<p>高度: {item.height}px</p>
<p style={{ height: `${item.height}px` }}>
这是一个示例内容,实际应用中这里会有具体的材料信息。
</p>
{/* 核对按钮 */}
<div
style={{
position: lockedButtons[item.id] ? 'fixed' : 'static',
bottom: lockedButtons[item.id] ? '20px' : 'auto',
left: lockedButtons[item.id] ? '50%' : 'auto',
transform: lockedButtons[item.id] ? 'translateX(-50%)' : 'none',
zIndex: 1000,
width: '100%',
display: 'flex',
justifyContent: 'center',
marginTop: '16px',
}}
>
<Button
color="primary"
size="large"
onClick={() => handleVerify(item.id)}
style={{
width: '80%',
maxWidth: '300px',
}}
>
核对1111{index}
</Button>
</div>
</div>
))}
</div>
);
};