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.
I tried creating an example based on your question. Is this what you want?
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;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With