Animation
Apple Vision Pro Scroll Animation
This is the scroll animation from the Apple Vision Pro site, recreated in Framer. Feel free to remix the project and check out how it's built.
Created by
About the resource
The scroll animation effect leverages a mix of native Framer effects and some code overrides.
Elements of Vision Pro are moved using scroll transforms, triggered by scroll sections.
The video plays on scroll due to a scroll scrub code override applied to it.
I also employed sticky positioning, ensuring that the Vision Pro headset stays at the top of the viewport during the animation. An additional code override on the sticky container assures that the top value remains at 50vh, meaning the section halts in the center of the viewport.
You can copy both overrides from below.
Code override for the scroll scrub video effect
Go ahead and copy the code, then create a new override in your project to start from scratch.
You can edit the "startY" and "distance" variables to alter when the video starts playing and the duration it takes to play the entire video.
About the resource
The scroll animation effect leverages a mix of native Framer effects and some code overrides.
Elements of Vision Pro are moved using scroll transforms, triggered by scroll sections.
The video plays on scroll due to a scroll scrub code override applied to it.
I also employed sticky positioning, ensuring that the Vision Pro headset stays at the top of the viewport during the animation. An additional code override on the sticky container assures that the top value remains at 50vh, meaning the section halts in the center of the viewport.
You can copy both overrides from below.
Code override for the scroll scrub video effect
Go ahead and copy the code, then create a new override in your project to start from scratch.
You can edit the "startY" and "distance" variables to alter when the video starts playing and the duration it takes to play the entire video.
About the resource
The scroll animation effect leverages a mix of native Framer effects and some code overrides.
Elements of Vision Pro are moved using scroll transforms, triggered by scroll sections.
The video plays on scroll due to a scroll scrub code override applied to it.
I also employed sticky positioning, ensuring that the Vision Pro headset stays at the top of the viewport during the animation. An additional code override on the sticky container assures that the top value remains at 50vh, meaning the section halts in the center of the viewport.
You can copy both overrides from below.
Code override for the scroll scrub video effect
Go ahead and copy the code, then create a new override in your project to start from scratch.
You can edit the "startY" and "distance" variables to alter when the video starts playing and the duration it takes to play the entire video.
import type { ComponentType } from "react"
import { useState, useEffect } from "react"
import type { MotionValue, Transition } from "framer-motion"
import { useViewportScroll, useTransform } from "framer-motion"
import { gsap } from "gsap"
export function withScrolledProgress(Component): ComponentType {
const startY = 1000
const distance = 1500
const endY = startY + distance
return (props) => {
const { scrollY } = useViewportScroll()
const progress = useTransform(scrollY, [startY, endY], [0, 1])
useEffect(() => {
const video = document.getElementById("video") as HTMLVideoElement
gsap.to(video, {
scrollTrigger: {
trigger: ".scroll-container",
start: "top top",
end: "bottom bottom",
scrub: 1,
markers: true,
},
keyframes: [
{ progress: 0 },
{ progress: 0.1 },
{ progress: 0.2 },
{ progress: 0.3 },
{ progress: 0.4 },
{ progress: 0.5 },
{ progress: 0.6 },
{ progress: 0.7 },
{ progress: 0.8 },
{ progress: 0.9 },
{ progress: 1 },
],
ease: "linear",
duration: 10,
})
return () => {
gsap.killTweensOf(video)
}
}, [scrollY, distance])
return <Component {...props} progress={progress} />
}
}
import type { ComponentType } from "react"
import { useState, useEffect } from "react"
import type { MotionValue, Transition } from "framer-motion"
import { useViewportScroll, useTransform } from "framer-motion"
import { gsap } from "gsap"
export function withScrolledProgress(Component): ComponentType {
const startY = 1000
const distance = 1500
const endY = startY + distance
return (props) => {
const { scrollY } = useViewportScroll()
const progress = useTransform(scrollY, [startY, endY], [0, 1])
useEffect(() => {
const video = document.getElementById("video") as HTMLVideoElement
gsap.to(video, {
scrollTrigger: {
trigger: ".scroll-container",
start: "top top",
end: "bottom bottom",
scrub: 1,
markers: true,
},
keyframes: [
{ progress: 0 },
{ progress: 0.1 },
{ progress: 0.2 },
{ progress: 0.3 },
{ progress: 0.4 },
{ progress: 0.5 },
{ progress: 0.6 },
{ progress: 0.7 },
{ progress: 0.8 },
{ progress: 0.9 },
{ progress: 1 },
],
ease: "linear",
duration: 10,
})
return () => {
gsap.killTweensOf(video)
}
}, [scrollY, distance])
return <Component {...props} progress={progress} />
}
}
import type { ComponentType } from "react"
import { useState, useEffect } from "react"
import type { MotionValue, Transition } from "framer-motion"
import { useViewportScroll, useTransform } from "framer-motion"
import { gsap } from "gsap"
export function withScrolledProgress(Component): ComponentType {
const startY = 1000
const distance = 1500
const endY = startY + distance
return (props) => {
const { scrollY } = useViewportScroll()
const progress = useTransform(scrollY, [startY, endY], [0, 1])
useEffect(() => {
const video = document.getElementById("video") as HTMLVideoElement
gsap.to(video, {
scrollTrigger: {
trigger: ".scroll-container",
start: "top top",
end: "bottom bottom",
scrub: 1,
markers: true,
},
keyframes: [
{ progress: 0 },
{ progress: 0.1 },
{ progress: 0.2 },
{ progress: 0.3 },
{ progress: 0.4 },
{ progress: 0.5 },
{ progress: 0.6 },
{ progress: 0.7 },
{ progress: 0.8 },
{ progress: 0.9 },
{ progress: 1 },
],
ease: "linear",
duration: 10,
})
return () => {
gsap.killTweensOf(video)
}
}, [scrollY, distance])
return <Component {...props} progress={progress} />
}
}
Code override for sticky container
Go ahead and copy the code, then create a new override in your project to start from scratch.
This code basically makes the frame stop with a 50vh (50% of the given viewport) distance from the top of the viewport.
Code override for sticky container
Go ahead and copy the code, then create a new override in your project to start from scratch.
This code basically makes the frame stop with a 50vh (50% of the given viewport) distance from the top of the viewport.
Code override for sticky container
Go ahead and copy the code, then create a new override in your project to start from scratch.
This code basically makes the frame stop with a 50vh (50% of the given viewport) distance from the top of the viewport.
import type { ComponentType } from "react"
export function withStickyTop(Component): ComponentType {
return (props) => {
return (
<Component {...props} style={{ position: "sticky", top: "50vh" }} />
)
}
}
import type { ComponentType } from "react"
export function withStickyTop(Component): ComponentType {
return (props) => {
return (
<Component {...props} style={{ position: "sticky", top: "50vh" }} />
)
}
}
import type { ComponentType } from "react"
export function withStickyTop(Component): ComponentType {
return (props) => {
return (
<Component {...props} style={{ position: "sticky", top: "50vh" }} />
)
}
}