Preload splash screen in Framer (code component)
Misha
Posted on January 19, 2024
Coming to Framer as a developer was weird: on one hand, simple things are done fast and easily, but when it came to complex ones, like creating a loading splash screen, it felt surprisingly challenging.
I was very disappointed by the fact that Framer is used for building visually rich websites with lots of media content like video backgrounds and yet the problem of showing a loading screen is left unsolved.
It was funny to learn that there are tutorials teaching how to build an animated loading screen (like this one, or this one). But what they recommend is to use a predefined delay for these splash screens (what?).
This means that every time a user opens a page, they will see this animation for a fixed period of time. Even if the page content is cached or wasn't loaded, the loading animation will always play for, let's say, 5 seconds. Crazy 🤯
I wasn't going to give up, and I came up with an honest loading screen solution. It's not ideal but it seems like it's the only one!
The code
import { useEffect, useState } from "react"
import { addPropertyControls, ControlType } from "framer"
import { motion } from "framer-motion"
/**
* These annotations control how your component sizes
* Learn more: https://www.framer.com/developers/#code-components-auto-sizing
*
* @framerSupportedLayoutWidth auto
* @framerSupportedLayoutHeight auto
*/
export default function SplashScreen(props) {
const { children, images } = props
const [imagesLoaded, setImagesLoaded] = useState(false)
useEffect(() => {
console.log("preloading images:", images)
const loadImages = async () => {
const total = images.length
let loaded = 0
// toggle body scroll off
document.body.style.overflow = "hidden"
const promises = images.map((src) => {
return new Promise<void>((resolve, reject) => {
const img = new Image()
img.src = src
img.fetchPriority = "high"
img.onload = () => {
loaded += 1
console.log(
`Preloaded ${loaded}/${total} images (${src})`
)
resolve()
}
img.onerror = () => {
console.error(`Couldn't preload image ${src}`)
resolve()
}
})
})
try {
await Promise.all(promises)
} catch (error) {
console.error("Error loading images:", error)
}
console.log("finished preloading")
// toggle body scroll back on
document.body.style.overflow = ""
setImagesLoaded(true)
}
loadImages()
}, [])
return (
<motion.div
style={{
position: "fixed",
zIndex: 1000,
inset: 0,
display: "flex",
height: "100vh",
width: "100vw",
justifyContent: "center",
alignItems: "center",
background: "white",
pointerEvents: imagesLoaded ? "none" : "all",
}}
animate={{ opacity: imagesLoaded ? 0 : 1 }}
transition={{ duration: 0.5 }}
/>
)
}
addPropertyControls(SplashScreen, {
images: {
type: ControlType.Array,
control: {
type: ControlType.Image,
},
},
})
SplashScreen.defaultProps = {
images: [],
}
It's a pretty simple component and it doesn't include any progress indicator, but you can easily add it yourself by setting the preloaded images count to state and adding some animation to it.
Here's how you use it: place it as the top layer of your page and list the images to preload in the component’s controls.
You may ask why it only supports images. There are two reasons for this:
- Framer doesn't support
ControlType.File
as an item ofControlType.Image
- If you're preloading images and videos, just images are enough most of the time. Why? Because you can set a video's first frame as a
poster
, and it will be displayed as a still image until it's ready to play. Otherwise, you can always add a singleControlType.File
for every file you have on the page.
I hope you find this solution useful. If you have any questions or suggestions, feel free to share them in the comments.
Update: I've decided to post the code that I use on my production project. In this variation I use string input instead of image input because I am preloading videos as well. I hope that helps, happy hacking!
Posted on January 19, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 29, 2024