在手机端做个滚动效果

用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>
  );
};
相关推荐
木斯佳20 小时前
前端八股文面经大全:26届秋招滴滴校招前端一面面经-事件循环题解析
前端·状态模式
光影少年20 小时前
react状态管理都有哪些及优缺点和应用场景
前端·react.js·前端框架
saber_andlibert1 天前
TCMalloc底层实现
java·前端·网络
逍遥德1 天前
如何学编程之01.理论篇.如何通过阅读代码来提高自己的编程能力?
前端·后端·程序人生·重构·软件构建·代码规范
冻感糕人~1 天前
【珍藏必备】ReAct框架实战指南:从零开始构建AI智能体,让大模型学会思考与行动
java·前端·人工智能·react.js·大模型·就业·大模型学习
程序员agions1 天前
2026年,“配置工程师“终于死绝了
前端·程序人生
alice--小文子1 天前
cursor-mcp工具使用
java·服务器·前端
晚霞的不甘1 天前
揭秘 CANN 内存管理:如何让大模型在小设备上“轻装上阵”?
前端·数据库·经验分享·flutter·3d
小迷糊的学习记录1 天前
0.1 + 0.2 不等于 0.3
前端·javascript·面试
梦帮科技1 天前
Node.js配置生成器CLI工具开发实战
前端·人工智能·windows·前端框架·node.js·json