Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

using framer motion progress bar to track the y scroll on a react page that has internal overflow y scroll components

I am trying to figure out how to build a framer motion progress bar that will manage the scroll-down progress of a page comprising components. some components have viewport height settings, with y overflow scroll behaviour. I want the progress bar to calculate the overall progress down the page, including the progress against the included components (assuming they would be scrolled down before moving to the next section).

This is the page.

There is currently no animation on the scroll bar; it just shows as 100% on page load and does not move.

'use client';

import React, { useState, useEffect, useRef } from 'react';
import { motion, useScroll, useTransform } from 'framer-motion';

import Section1 from './Section1';
import Section2 from './Section2';
import Section3 from './Section3';
import Section4 from './Section4';
import Section5 from './Section5';
import Section6 from './Section6';
import Section7 from './Section7';
import Section8 from './Section8';
import Section9 from './Section9';
import Section10 from './Section10';
import Section11 from './Section11'; 
import Section12 from './Section12';
import Section13 from './Section13';
import Section14 from './Section14';
import Section15 from './Section15'

import Qanda from './Qanda';
import Tldr from './Tldr';
import Landscape from './Landscape';

import Navigation from '../../_components/Navigation';
import { usePageTitle } from '../../_hooks/usePageTitle';


const ScrollProgressBar = () => {
  const { scrollYProgress } = useScroll(); // Track scroll progress
  const scaleX = useTransform(scrollYProgress, [0, 1], [0, 1]); // Transform scroll progress to scale

  return (
    <motion.div
      style={{
        position: 'fixed',
        top: 0,
        left: 0,
        height: '4px',
        width: '100%',
        backgroundColor: 'red',
        scaleX, // Apply the scale based on scroll progress
        transformOrigin: '0%', // Scale from the left
        zIndex: 1000, // Ensure it's above other content
      }}
    />
  );
};

export default function CasePage() {
    const title = usePageTitle('DONOGHUE v STEVENSON');
    const [activeSection, setActiveSection] = useState(1);
    const sectionsRef = useRef<Array<HTMLDivElement | null>>([]);

    const scrollableRef = useRef(null);

    useEffect(() => {
      const handleScroll = () => {
        const scrollPosition = window.scrollY + window.innerHeight / 2;
  
        for (let i = sectionsRef.current.length - 1; i >= 0; i--) {
          const section = sectionsRef.current[i];
          if (section && section.offsetTop <= scrollPosition) {
            setActiveSection(i + 1);
            break;
          }
        }
      };
  
      window.addEventListener('scroll', handleScroll);
      return () => window.removeEventListener('scroll', handleScroll);
    }, []);
  
    const scrollToSection = (sectionId: string) => {
      const element = document.getElementById(sectionId);
      if (element) {
        element.scrollIntoView({ behavior: 'smooth' });
      }
    };

  
    return (
      
      

        <div className="relative w-full bg-parchment">
            <ScrollProgressBar />

            <Navigation title={title} scrollToSection={scrollToSection} />
        
            <div ref={el => { sectionsRef.current[0] = el; }} id="section1" className="section min-h-screen">
              <Section1 scrollToSection={scrollToSection} />
            </div>
          
          <div ref={el => { sectionsRef.current[1] = el; }} id="section2" className="section min-h-screen">
            <Section2 />
          </div>

          <div ref={el => { sectionsRef.current[2] = el; }} id="tldr" className="section">
            <Tldr />
          </div>

          <div ref={el => { sectionsRef.current[3] = el; }} id="landscape" className="section">
            <Landscape />
          </div>

          <div ref={el => { sectionsRef.current[4] = el; }} id="section3" className="section">
            <Section3 />
          </div>


          <div ref={el => { sectionsRef.current[5] = el; }} id="section4" className="section min-h-screen">
              <Section4 />
          </div>

          <div ref={el => { sectionsRef.current[6] = el; }} id="section5" className="section z-2">
            <Section5 />
          </div>

          <div ref={el => { sectionsRef.current[7] = el; }} id="section6" className="section min-h-screen">
            <Section6 />
          </div>
          <div ref={el => { sectionsRef.current[8] = el; }} id="section7" className="section min-h-screen">
            <Section7 />
          </div>

          <div ref={el => { sectionsRef.current[9] = el; }} id="section8" className="section min-h-screen">
            <Section8 />
          </div>

          <div ref={el => { sectionsRef.current[10] = el; }} id="section15" className="section min-h-screen z-2">
            <Section15 />
          </div>

          <div ref={el => { sectionsRef.current[11] = el; }} id="section9" className="section min-h-screen">
            <Section9 />
          </div>
          <div ref={el => { sectionsRef.current[12] = el; }} id="section10" className="section min-h-screen">
            <Section10 />
          </div>

          <div ref={el => { sectionsRef.current[13] = el; }} id="section11" className="section min-h-screen">
            <Section11 />
          </div>

          <div ref={el => { sectionsRef.current[14] = el; }} id="section12" className="section min-h-screen">
            <Section12 />
          </div>

          <div ref={el => { sectionsRef.current[15] = el; }} id="section13" className="section min-h-screen">
            <Section13 />
          </div>

          

          <div ref={el => { sectionsRef.current[16] = el; }} id="qanda" className="section min-h-screen">
            <Qanda />
          </div>
          <div ref={el => { sectionsRef.current[17] = el; }} id="section14" className="section min-h-screen">
            <Section14 />
          </div>
        </div>
  );
}

Is it possible to implement a framer motion progress bar that tracks the progress of scrolling down on a page that is set to viewport height with overflow y scroll, and which includes components that have the same internal setting (for example, the section has very long text and includes a height viewport setting with overflow y scroll? when the user scrolls down into section 4, the s4 scroll-down behavior takes over and does not move back to the page until the end of s4 is reached.

like image 984
Mel Avatar asked Oct 29 '25 00:10

Mel


1 Answers

I tried creating an example based on your question. Is this what you want?

Codesandbox Demo

import React, { CSSProperties } from "react";
import ScrollProgressBar from "./Progress";

const Section = ({ style }: { style: CSSProperties }) => (
  <div style={{ ...style, opacity: 0.2 }} />
);
const App = () => {
  return (
    <div>
      <ScrollProgressBar />
      <Section style={{ height: "10vh", backgroundColor: "red" }} />
      <Section style={{ height: "10vh", backgroundColor: "red" }} />
      <Section
        style={{
          height: "250vh",

          overflowY: "scroll",
          backgroundColor: "blue",
        }}
      />
      <Section
        style={{
          height: "20vh",
          backgroundColor: "green",
        }}
      />
    </div>
  );
};

export default App;
import React, { useEffect, useState } from "react";
import { motion } from "framer-motion";

const ScrollProgressBar = () => {
  const [scrollYProgress, setScrollYProgress] = useState(0);

  const handleScroll = () => {
    const scrollTop = window.scrollY || document.documentElement.scrollTop;
    const scrollHeight =
      document.documentElement.scrollHeight -
      document.documentElement.clientHeight;
    const scrollProgress = scrollTop / scrollHeight;

    setScrollYProgress(scrollProgress);
  };

  useEffect(() => {
    window.addEventListener("scroll", handleScroll);
    return () => {
      window.removeEventListener("scroll", handleScroll);
    };
  }, []);

  return (
    <motion.div
      style={{
        position: "fixed",
        top: 0,
        left: 0,
        right: 0,
        height: "4px",
        backgroundColor: "red",
        transformOrigin: "0%",
      }}
      initial={{ scaleX: 0 }}
      animate={{ scaleX: scrollYProgress }}
      transition={{ ease: "linear" }}
    />
  );
};

export default ScrollProgressBar;
like image 128
docker compose Avatar answered Oct 31 '25 14:10

docker compose



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!