在手机端做个滚动效果

用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>
  );
};
相关推荐
QQ1__8115175155 小时前
Spring boot名城小区物业管理系统信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】
前端·vue.js·spring boot
钛态5 小时前
前端微前端架构:大项目的救命稻草还是自找麻烦?
前端·vue·react·web
一粒黑子5 小时前
【实战解析】阿里开源 PageAgent:纯前端 GUI Agent,一行JS让网页支持自然语言操控
前端·javascript·开源
独角鲸网络安全实验室5 小时前
2026微信小程序抓包全解析:从实操落地到合规风控,解锁前端调试新范式
前端·微信小程序·小程序·抓包·系统代理绕过·https证书严格校验·进程隔离
紫微AI5 小时前
前端文本测量成了卡死一切创新的最后瓶颈,pretext实现突破了
前端·人工智能·typescript
GISer_Jing5 小时前
AI前端(From豆包)
前端·aigc·ai编程
IT枫斗者5 小时前
前端部署后如何判断“页面是不是最新”?一套可落地的版本检测方案(适配 Vite/Vue/React/任意 SPA)
前端·javascript·vue.js·react.js·架构·bug
测试修炼手册5 小时前
[测试技术] 深入理解 JSON Web Token (JWT)
前端·json
AI老李5 小时前
2026 年 Web 前端开发的 8 个趋势!
前端
里欧跑得慢5 小时前
15. Web可访问性最佳实践:让每个用户都能平等访问
前端·css·flutter·web