Animation
Frame.io Website Animation in Framer
About the resource
I've set up this interaction so that I've made a component with 54 variants. Each variant is a segment of the image sequence that, when played, displays a laptop closing animation.
To switch the component's variants from "Variant 1" to "Variant 2", and then all the way up to "Variant 54" as you scroll through the page, you gotta use a code override. You can check it out below.
Code override for cycling through component's variants on scroll
The VARIANT_LENGTH is important - it should be equal to the total number of variants your component has. The code here uses 54 so remember to adjust that based on your own component's number of variants.
When you scroll down on the web page, the component progressively shows different variants as the component comes into view.
You can even change when the animation starts and ends by tweaking two values named "start 0.7" and "end 0.85".
Go ahead and copy the code, then start creating the override in your Framer project from ground zero.
About the resource
I've set up this interaction so that I've made a component with 54 variants. Each variant is a segment of the image sequence that, when played, displays a laptop closing animation.
To switch the component's variants from "Variant 1" to "Variant 2", and then all the way up to "Variant 54" as you scroll through the page, you gotta use a code override. You can check it out below.
Code override for cycling through component's variants on scroll
The VARIANT_LENGTH is important - it should be equal to the total number of variants your component has. The code here uses 54 so remember to adjust that based on your own component's number of variants.
When you scroll down on the web page, the component progressively shows different variants as the component comes into view.
You can even change when the animation starts and ends by tweaking two values named "start 0.7" and "end 0.85".
Go ahead and copy the code, then start creating the override in your Framer project from ground zero.
About the resource
I've set up this interaction so that I've made a component with 54 variants. Each variant is a segment of the image sequence that, when played, displays a laptop closing animation.
To switch the component's variants from "Variant 1" to "Variant 2", and then all the way up to "Variant 54" as you scroll through the page, you gotta use a code override. You can check it out below.
Code override for cycling through component's variants on scroll
The VARIANT_LENGTH is important - it should be equal to the total number of variants your component has. The code here uses 54 so remember to adjust that based on your own component's number of variants.
When you scroll down on the web page, the component progressively shows different variants as the component comes into view.
You can even change when the animation starts and ends by tweaking two values named "start 0.7" and "end 0.85".
Go ahead and copy the code, then start creating the override in your Framer project from ground zero.
import React, { useState, ComponentType, useRef, useEffect } from "react"
import { useScroll, useMotionValueEvent } from "framer-motion"
const VARIANT_LENGTH = 54
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.7", "end 0.85"],
})
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}
/>
</>
)
}
}
import React, { useState, ComponentType, useRef, useEffect } from "react"
import { useScroll, useMotionValueEvent } from "framer-motion"
const VARIANT_LENGTH = 54
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.7", "end 0.85"],
})
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}
/>
</>
)
}
}
import React, { useState, ComponentType, useRef, useEffect } from "react"
import { useScroll, useMotionValueEvent } from "framer-motion"
const VARIANT_LENGTH = 54
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.7", "end 0.85"],
})
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}
/>
</>
)
}
}