浮动底部按钮的实现,跟随内容移动与固定

发布时间 2023-06-16 16:12:21作者: 前端小白的江湖路

背景

未超过一屏时,底部按钮跟随内容

超过一屏时,底部按钮固定在底部

 

原理

我们需要同时监听DOM变动函数以及窗口大小变化,大于一屏时,添加底部按钮fixed布局

小于一屏时移除

实现

import React, { useState, useRef, useEffect, useCallback } from "react";
import throttle from "lodash/throttle";
import cx from "classnames";
import "./index.scss";

interface IProps {
  content: React.ReactNode;
  footer: React.ReactNode;
  containerClassName?: string;
  contentClassName?: string;
  footerClassName?: string;
  throttleTime?: number;
}

const FloatFooter: React.FC<IProps> = (props) => {
  const {
    content,
    footer,
    containerClassName,
    contentClassName,
    footerClassName,
    throttleTime = 500,
  } = props;

  const containerRef = useRef<HTMLDivElement>(null);
  const contentRef = useRef<HTMLDivElement>(null);
  const footerRef = useRef<HTMLDivElement>(null);
  const [fixed, setFixed] = useState<boolean>(false);
  const [footerHeight, setFooterHeight] = useState<number>(0);

  const handleHeightChange = useCallback(
    throttle(
      () => {
        const contentElement = contentRef.current;
        const footerElement = footerRef.current;
        if (!contentElement || !footerElement) return;
        const contentElementHeight = contentElement.clientHeight;
        const viewportHeight = document.documentElement.clientHeight;
        if (contentElementHeight > viewportHeight) {
          setFixed(true);
          setFooterHeight(footerElement.clientHeight);
        } else {
          setFixed(false);
          setFooterHeight(0);
        }
      },
      throttleTime,
      { trailing: false }
    ),
    []
  );

  useEffect(() => {
    handleHeightChange();
    window.addEventListener("resize", handleHeightChange);

    const observer = new MutationObserver(handleHeightChange);
    const observerConfig = { childList: true, subtree: true };
    if (containerRef.current) {
      observer.observe(containerRef.current, observerConfig);
    }

    return () => {
      window.removeEventListener("resize", handleHeightChange);
      observer.disconnect();
    };
  }, []);

  return (
    <article
      className={cx("i-container", containerClassName)}
      ref={containerRef}
    >
      <section className={cx("i-content", contentClassName)} ref={contentRef}>
        {content}
        {fixed && <div style={{ height: `${footerHeight}px` }}></div>}
      </section>

      <footer
        className={cx("i-footer", footerClassName, { "i-fixed-footer": fixed })}
        ref={footerRef}
      >
        {footer}
      </footer>
    </article>
  );
};

export default FloatFooter;
.i-fixed-footer {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
}