Targeting an element (or parent) using Framer Motion

18 Views Asked by At

I discovered a code by Nandi Muzsik (https://framer.university/resources/image-sequence-player-override-in-framer) allowing you to use image sequencing animation on you sites. (Code below).

How it works:

You create a component, then paste this custom code and when you scroll it changes variants based on the scroll and you have a nice animation.

My problem:

I discovered, that if I place the component inside a frame 2x it's height and set the component to "Sticky" it does not track the scroll until it's "not sticky anymore".

I tried to modify the code and delete target: ref, so now it tracks scrolling of the window. It fixed the problem, but created a new one - I want to track the scroll progress just for the container, which holds the component. And I don't know how to target it nor use it as a ref.

Any ideas?

import React, { useState, ComponentType, useRef, useEffect } from "react"
import { useScroll, useMotionValueEvent } from "framer-motion"

const VARIANT_LENGTH = 61

export const withScrollVariants = (Component): ComponentType => {
    const variants = Array.from(
        { length: VARIANT_LENGTH },
        (_, i) => `Variant ${i}`
    )

    return (props) => {
        const [variantIndex, setVariantIndex] = useState(0)
        const [imagesLoaded, setImagesLoaded] = useState(false)
        const ref = useRef(null)
        const { scrollYProgress } = useScroll({
            target: ref,
            offset: ["start 0", "end 0"],
        })

        useEffect(() => {
            const imageSources = []
            document.querySelectorAll("img").forEach((imgElement) => {
                imageSources.push(imgElement.src)
            })

            const imagePromises = imageSources.map((src) => {
                return new Promise((resolve, reject) => {
                    const img = new Image()
                    img.onload = () => resolve(src)
                    img.onerror = () => reject(src)
                    img.src = src
                })
            })

            Promise.all(imagePromises)
                .then(() => setImagesLoaded(true))
                .catch(() => setImagesLoaded(false))
        }, [])

        useEffect(() => {
            const newVariantIndex =
                Math.round(scrollYProgress.current * (VARIANT_LENGTH - 2)) + 1

            setVariantIndex(newVariantIndex)
        }, [scrollYProgress.current])

        useMotionValueEvent(scrollYProgress, "change", (currentProgress) => {
            const newVariantIndex =
                Math.round(scrollYProgress.current * (VARIANT_LENGTH - 2)) + 1

            if (newVariantIndex !== variantIndex && imagesLoaded) {
                setVariantIndex(newVariantIndex)
            }
        })

        return (
            <>
                <div style={{ display: "none" }}>
                    {variants.map((variant) => (
                        <Component key={variant} {...props} variant={variant} />
                    ))}
                </div>
                <Component
                    {...props}
                    variant={variants[variantIndex]}
                    ref={ref}
                />
            </>
        )
    }
}
0

There are 0 best solutions below