Preload splash screen in Framer (code component)

topcat

Misha

Posted on January 19, 2024

Preload splash screen in Framer (code component)

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?).

Image description

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: [],
}
Enter fullscreen mode Exit fullscreen mode

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 of ControlType.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 single ControlType.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!

💖 💪 🙅 🚩
topcat
Misha

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