Skip to content

Instantly share code, notes, and snippets.

@mmintel
Created March 1, 2023 07:35
Show Gist options
  • Save mmintel/69c15d3b5bd11ed7eb36e25046bf0451 to your computer and use it in GitHub Desktop.
Save mmintel/69c15d3b5bd11ed7eb36e25046bf0451 to your computer and use it in GitHub Desktop.
React scroll based reveal
import { motion, useInView, useScroll, useTransform } from "framer-motion";
import { useElementViewportPosition } from "hooks/use-element-viewport-position";
import { useRef, useEffect, useState } from "react";
export const ScrollReveal = ({ offset = 0, once = true, children }) => {
const [initiallyVisible, setInitiallyVisible] = useState(false);
const [ended, setEnded] = useState(false);
const ref = useRef(null);
const isInView = useInView(ref);
const { position } = useElementViewportPosition(ref, offset);
const { scrollYProgress } = useScroll();
const opacity = useTransform(scrollYProgress, position, [0, 1]);
const y = useTransform(scrollYProgress, position, [50, 0]);
opacity.on("change", (value) => {
if (value === 1) {
setEnded(true);
}
});
useEffect(() => {
if (isInView && scrollYProgress.get() < 0.1) {
setInitiallyVisible(true);
opacity.set(1);
y.set(0);
}
}, [isInView, setEnded, scrollYProgress]);
useEffect(() => {
if (ended || initiallyVisible) {
scrollYProgress.destroy();
opacity.destroy();
y.destroy();
}
}, [ended, initiallyVisible]);
if (initiallyVisible)
return (
<motion.div
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
>
{children}
</motion.div>
);
return (
<motion.div
ref={ref}
style={{
opacity: (once && ended) || initiallyVisible ? 1 : opacity,
y: (once && ended) || initiallyVisible ? 0 : y,
}}
>
{children}
</motion.div>
);
};
import { useState, useEffect } from "react";
export function useElementViewportPosition(ref, offset = 0) {
const [position, setPosition] = useState([0, 0]);
useEffect(() => {
if (!ref || !ref.current) return;
const update = () => {
const pageHeight = document.body.scrollHeight;
const start = ref.current.offsetTop;
const end = start + ref.current.offsetHeight;
setPosition([(start + offset) / pageHeight, (end + offset) / pageHeight]);
};
update();
addEventListener("resize", update);
return () => {
removeEventListener("resize", update);
};
}, []);
return { position };
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment