在手机端做个滚动效果

用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>
  );
};
相关推荐
be or not to be20 小时前
CSS 背景(background)系列属性
前端·css·css3
webkubor20 小时前
🧠 2025:AI 写代码越来越强,但我的项目返工却更多了
前端·机器学习·ai编程
Sun_小杰杰哇20 小时前
Dayjs常用操作使用
开发语言·前端·javascript·typescript·vue·reactjs·anti-design-vue
戴维南21 小时前
TypeScript 在项目中的实际解决的问题
前端
晴殇i21 小时前
package.json 中的 dependencies 与 devDependencies:深度解析
前端·设计模式·前端框架
pas13621 小时前
25-mini-vue fragment & Text
前端·javascript·vue.js
何贤21 小时前
2025 年终回顾:25 岁,从“混吃等死”到别人眼中的“技术专家”
前端·程序员·年终总结
冴羽21 小时前
CSS 新特性!瀑布流布局的终极解决方案
前端·javascript·css
满天星辰21 小时前
Vue 响应式原理深度解析
前端·vue.js