首页 > web前端 > js教程 > 正文

利用MUI useScrollTrigger实现粘性组件在父容器底部自动隐藏

碧海醫心
发布: 2025-11-23 12:16:15
原创
448人浏览过

利用MUI useScrollTrigger实现粘性组件在父容器底部自动隐藏

本文详细介绍了如何在material-ui (mui) 应用中,利用`usescrolltrigger`钩子结合react的`useref`和`useeffect`,实现一个粘性组件在其父容器滚动到底部时自动隐藏的交互效果。通过动态设置`usescrolltrigger`的目标元素和滚动阈值,开发者可以精确控制粘性元素的显示与隐藏逻辑,从而优化用户体验,避免粘性元素遮挡底部内容。

MUI中粘性定位(position="sticky")基础

Material-UI (MUI) 提供了强大的布局组件,其中Box组件通过position="sticky"属性可以轻松实现粘性定位效果。当一个元素被设置为position="sticky"时,它在正常流中表现为相对定位,但在滚动到特定阈值时会“粘”在屏幕的某个位置(如顶部、底部),直到其父容器的边界将其“推”走。

通常,我们会将一个Box元素设置为position="sticky"和bottom={0},使其在滚动时粘在父容器的底部。然而,在某些场景下,我们希望当用户滚动到父容器的底部时,这个粘性元素能够自动隐藏,以避免遮挡父容器的最终内容或提供更流畅的交互体验。

挑战:在父容器滚动到底部时隐藏粘性元素

直接使用position="sticky"无法原生实现“在父容器底部时隐藏”的逻辑。MUI的useScrollTrigger钩子是一个强大的工具,它通常用于监听窗口的滚动事件,以触发如App Bar的提升效果或“返回顶部”按钮的显示。然而,其默认行为是监听全局window对象的滚动。我们的挑战在于如何让useScrollTrigger监听特定的父容器,并根据该父容器的滚动位置来控制粘性元素的可见性。

解决方案:useScrollTrigger与React.useRef的结合

要解决这个问题,我们需要将useScrollTrigger的目标(target)属性指向我们的可滚动父容器。这可以通过React的useRef钩子来获取DOM元素的引用,并结合React.useState和React.useEffect来动态设置useScrollTrigger的target。

1. 准备父容器与粘性元素

首先,构建一个包含可滚动内容的父容器Box和一个粘性子Box。父容器需要设置overflow: 'auto'或overflow: 'scroll'使其可滚动。

import React from 'react';
import { Box, useScrollTrigger } from '@mui/material';

export default function StickyDivControl() {
  const parentRef = React.useRef(null); // 创建一个ref来引用父容器

  // ... 后续逻辑

  return (
    <Box
      sx={{
        width: 400,
        height: 300, // 设置一个固定高度,使其可滚动
        overflow: 'auto',
        border: '1px solid #ccc',
        borderRadius: '4px',
      }}
      ref={parentRef} // 将ref绑定到父容器
    >
      <ul>
        {/* 生成大量内容使父容器可滚动 */}
        {Array.from({ length: 100 }, (_, index) => (
          <li key={index}>{`Text ${index + 1}`}</li>
        ))}
      </ul>
      {/* 粘性元素,初始定位在底部 */}
      <Box
        position="sticky"
        bottom={0}
        bgcolor="white"
        p={2}
        boxShadow={2}
        zIndex={100}
      >
        这是一个粘性元素
      </Box>
    </Box>
  );
}
登录后复制

2. 获取父容器引用并动态设置useScrollTrigger的目标

useRef在组件初次渲染时,其.current属性可能为null。而useScrollTrigger的target属性期望一个DOM元素。因此,我们需要在组件挂载后,确保ref.current已经指向了DOM元素时,再将其传递给useScrollTrigger。这可以通过useState和useEffect实现。

import React from 'react';
import { Box, useScrollTrigger } from '@mui/material';

export default function StickyDivControl() {
  const parentRef = React.useRef(null);
  const [scrollTargetNode, setScrollTargetNode] = React.useState(undefined);

  // 在组件挂载后,将ref.current赋值给state,作为useScrollTrigger的target
  React.useEffect(() => {
    setScrollTargetNode(parentRef.current);
  }, []); // 空依赖数组确保只在组件挂载时运行一次

  // 配置useScrollTrigger
  // target: 监听的滚动元素
  // threshold: 滚动多少距离后触发(例如,从底部向上滚动100px时显示)
  const showSticky = useScrollTrigger({
    target: scrollTargetNode, // 使用state中保存的DOM节点
    disableHysteresis: true, // 禁用滞后,滚动一超过阈值就触发
    threshold: 100, // 滚动距离父容器底部100px时触发
  });

  // ... 渲染逻辑
}
登录后复制

这里threshold: 100的含义是:当滚动位置超过其父容器的顶部(或从顶部开始计算)100像素时,showSticky变为true。为了实现“在父容器底部时隐藏”,我们需要调整threshold的逻辑,或者更直接地,判断父容器是否滚动到了底部。

然而,useScrollTrigger的threshold默认是基于从顶部滚动的距离。如果我们想在滚动到父容器底部时隐藏,那么useScrollTrigger可能不是最直观的工具,因为它主要用于监听从顶部开始的滚动距离。

绘蛙-多图成片
绘蛙-多图成片

绘蛙新推出的AI图生视频工具

绘蛙-多图成片 133
查看详情 绘蛙-多图成片

更准确的思路是: showSticky为true时,表示用户已经向下滚动了一段距离(超过threshold)。如果我们想在父容器滚动到底部时隐藏粘性元素,那么showSticky应该控制粘性元素的显示,即当showSticky为true时显示,当showSticky为false时(在顶部或者在底部)隐藏。

让我们重新思考threshold的设定。如果threshold设为100,意味着当从父容器顶部向下滚动超过100px时,showSticky变为true。这可以用来控制当用户开始滚动时显示粘性元素,而在顶部时隐藏。

为了实现“在父容器底部时隐藏”,我们可以利用useScrollTrigger的返回值来控制粘性元素的条件渲染或者样式

当showSticky为true时,粘性元素显示。当showSticky为false时,粘性元素隐藏。 那么,threshold的设置就决定了何时showSticky变为true。如果我们将threshold设置为一个较小的值(例如1),那么只要用户开始滚动,showSticky就变为true。当用户滚动到顶部时,showSticky变为false。

但我们想要的是:当滚动到父容器底部时,粘性元素隐藏。 useScrollTrigger本身没有直接提供“滚动到元素底部”的判断。我们需要结合scrollTargetNode的scrollHeight、clientHeight和scrollTop属性来手动判断是否到达底部。

优化方案:结合useScrollTrigger和手动滚动判断

import React from 'react';
import { Box, useScrollTrigger } from '@mui/material';

export default function StickyDivControl() {
  const parentRef = React.useRef(null);
  const [scrollTargetNode, setScrollTargetNode] = React.useState(undefined);
  const [isAtBottom, setIsAtBottom] = React.useState(false);

  React.useEffect(() => {
    setScrollTargetNode(parentRef.current);
  }, []);

  // useScrollTrigger在这里可以用于判断是否在顶部
  const isScrollingDown = useScrollTrigger({
    target: scrollTargetNode,
    disableHysteresis: true,
    threshold: 1, // 只要滚动超过1px就触发
  });

  // 监听父容器的滚动事件来判断是否到达底部
  React.useEffect(() => {
    const parentElement = parentRef.current;
    if (!parentElement) return;

    const handleScroll = () => {
      const { scrollTop, scrollHeight, clientHeight } = parentElement;
      // 当滚动到底部时,scrollTop + clientHeight === scrollHeight
      // 留一点裕量,防止浮点数误差
      const bottomReached = scrollTop + clientHeight >= scrollHeight - 5;
      setIsAtBottom(bottomReached);
    };

    parentElement.addEventListener('scroll', handleScroll);
    // 初始加载时也检查一次
    handleScroll(); 

    return () => {
      parentElement.removeEventListener('scroll', handleScroll);
    };
  }, [scrollTargetNode]); // 依赖scrollTargetNode,确保在节点可用时才添加监听器

  // 粘性元素是否应该显示:
  // 1. 正在向下滚动 (isScrollingDown为true)
  // 2. 并且没有滚动到父容器底部 (isAtBottom为false)
  const shouldShowSticky = isScrollingDown && !isAtBottom;

  return (
    <Box
      sx={{
        width: 400,
        height: 500, // 增加高度以便更明显地滚动
        overflow: 'auto',
        border: '1px solid #ccc',
        borderRadius: '4px',
      }}
      ref={parentRef}
    >
      <ul>
        {Array.from({ length: 100 }, (_, index) => (
          <li key={index}>{`Text ${index + 1}`}</li>
        ))}
      </ul>
      {/* 根据shouldShowSticky的值条件渲染粘性元素 */}
      {shouldShowSticky && (
        <Box
          position="sticky"
          bottom={0}
          bgcolor="white"
          p={2}
          boxShadow={2}
          zIndex={100}
        >
          当父容器滚动到底部时隐藏我
        </Box>
      )}
    </Box>
  );
}
登录后复制

在这个优化方案中:

  • useScrollTrigger用于判断用户是否已经开始向下滚动(即不在顶部)。
  • 一个独立的useEffect监听父容器的scroll事件,并计算scrollTop + clientHeight是否接近scrollHeight来判断是否到达底部。
  • shouldShowSticky结合这两个状态:只有当用户正在滚动(非顶部)并且没有到达底部时,粘性元素才显示。

完整代码示例

下面是一个完整的React组件示例,展示了如何实现当父容器滚动到底部时,粘性元素自动隐藏的功能。

import React from 'react';
import { Box, useScrollTrigger, Typography } from '@mui/material';

export default function StickyDivWithDynamicHide() {
  const parentRef = React.useRef(null);
  const [scrollTargetNode, setScrollTargetNode] = React.useState(undefined);
  const [isAtBottom, setIsAtBottom] = React.useState(false);

  // 1. 在组件挂载后,将ref.current赋值给state,作为useScrollTrigger的target
  React.useEffect(() => {
    setScrollTargetNode(parentRef.current);
  }, []);

  // 2. 使用useScrollTrigger判断是否已经开始向下滚动(离开顶部)
  // threshold: 1 表示只要滚动超过1px就触发,即离开顶部
  const isScrollingDown = useScrollTrigger({
    target: scrollTargetNode,
    disableHysteresis: true, // 禁用滞后,滚动一超过阈值就触发
    threshold: 1, 
  });

  // 3. 监听父容器的滚动事件,判断是否到达底部
  React.useEffect(() => {
    const parentElement = parentRef.current;
    if (!parentElement) return;

    const handleScroll = () => {
      const { scrollTop, scrollHeight, clientHeight } = parentElement;
      // 判断是否到达底部,留一点误差裕量
      const bottomReached = scrollTop + clientHeight >= scrollHeight - 5;
      setIsAtBottom(bottomReached);
    };

    parentElement.addEventListener('scroll', handleScroll);
    // 初始加载时也检查一次,以防内容不足无法滚动
    handleScroll(); 

    return () => {
      parentElement.removeEventListener('scroll', handleScroll);
    };
  }, [scrollTargetNode]); // 依赖scrollTargetNode,确保在节点可用时才添加监听器

  // 4. 决定粘性元素是否显示:
  // 只有当用户正在向下滚动(离开顶部)并且没有到达父容器底部时,才显示粘性元素。
  const shouldShowSticky = isScrollingDown && !isAtBottom;

  return (
    <Box
      sx={{
        width: 400,
        height: 500, // 设置一个固定高度,使其可滚动
        overflow: 'auto',
        border: '1px solid #ccc',
        borderRadius: '4px',
        p: 2,
      }}
      ref={parentRef} // 将ref绑定到父容器
    >
      <Typography variant="h6" gutterBottom>滚动内容区域</Typography>
      <Typography variant="body2" color="text.secondary">
        请向下滚动,观察底部粘性元素的行为。
      </Typography>
      <ul>
        {/* 生成大量内容使父容器可滚动 */}
        {Array.from({ length: 100 }, (_, index) => (
          <li key={index}>{`列表项 ${index + 1}`}</li>
        ))}
      </ul>
      <Typography variant="h6" mt={4}>区域底部内容</Typography>
      <Typography variant="body2" color="text.secondary">
        这是父容器底部的额外内容,当滚动到这里时,粘性元素会隐藏。
      </Typography>

      {/* 根据shouldShowSticky的值条件渲染粘性元素 */}
      {shouldShowSticky && (
        <Box
          position="sticky"
          bottom={0}
          left={0} // 确保粘性元素在父容器内水平对齐
          right={0} // 确保粘性元素在父容器内水平对齐
          bgcolor="white"
          p={2}
          boxShadow={3} // 增加阴影效果
          zIndex={100}
          textAlign="center"
          sx={{ borderTop: '1px solid #eee' }} // 增加顶部边框
        >
          <Typography variant="body1" color="primary">
            我是一个粘性元素,滚动到底部时会消失!
          </Typography>
        </Box>
      )}
    </Box>
  );
}
登录后复制

关键点与注意事项

  1. useRef与useState结合使用: useScrollTrigger的target属性需要一个DOM元素。由于ref.current在组件挂载前为null,直接传递会报错。通过useState保存ref.current并在useEffect中更新,可以确保在useScrollTrigger使用时target已是有效的DOM节点。
  2. threshold参数: 在本例中,useScrollTrigger的threshold: 1用于判断用户是否已经离开了滚动容器的顶部。如果你希望粘性元素在滚动了一定距离后才显示,可以调整这个值。
  3. 手动滚动判断: useScrollTrigger主要关注从顶部开始的滚动距离。要判断是否到达容器底部,需要手动监听scroll事件,并计算scrollTop + clientHeight >= scrollHeight。这里加入了- 5的裕量,是为了处理不同浏览器或设备上可能存在的浮点数计算误差。
  4. disableHysteresis: 设置为true可以使useScrollTrigger在滚动超过threshold时立即触发,没有延迟或“滞后”效果,使得交互更即时。
  5. 条件渲染: 使用shouldShowSticky && (...)进行条件渲染,比通过CSS display: none或visibility: hidden隐藏元素更彻底,因为它直接控制了DOM节点的存在与否。这对于不需要在DOM中保留的元素来说,通常是更优的选择。
  6. zIndex: 确保粘性元素的zIndex足够高,以防止被其他内容覆盖。
  7. 父容器样式: 父容器必须有固定的height和overflow: 'auto'或overflow: 'scroll',才能使其内部内容可滚动。

总结

通过巧妙地结合MUI的useScrollTrigger、React的useRef和useEffect,并辅以对DOM滚动属性的监听,我们可以精确控制粘性元素在父容器内的显示与隐藏逻辑。这种方法不仅解决了特定场景下的交互需求,也展示了React Hooks在处理复杂UI状态和DOM交互时的强大灵活性。理解并运用这些技术,能够帮助开发者创建更加动态和用户友好的MUI应用。

以上就是利用MUI useScrollTrigger实现粘性组件在父容器底部自动隐藏的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号