Preload splash screen in Framer (code component)
Published: 2024-01-19 14:36:38

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](https://youtu.be/ptmbqPXKLFc), or [this one](https://youtu.be/Ts8mA9_uwlM)). But what they recommend is to use a predefined delay for these splash screens (what?).

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gh2eiku8e6up5ic4eer7.png)

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
```tsx
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 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.