Framer X » Animation » Example animations » 18. Scroll: Progress

18. Scroll: Progress

This example uses the useMotionValue() and useTransform() hooks to change the width of the bar at the bottom.

Open Framer Motion version in CodeSandbox

Code Component

const items = [1, 2, 3, 4, 5, 6]
const height = 70
const padding = 10
const scrollSize = 150

export function CC18ScrollProgress() {
    const itemsHeightTotal = items.length * height
    const totalPadding = (items.length - 1) * padding
    const contentHeight = itemsHeightTotal + totalPadding

    const scrollDistance = -contentHeight + scrollSize

    const scrollY = useMotionValue(0)
    const width = useTransform(
        scrollY,
        [0, scrollDistance],
        ["calc(0% - 0px)", "calc(100% - 40px)"]
    )

    return (
        <div>
            <Scroll
                // Visual & layout
                width={150}
                height={150}
                radius={30}
                center
                // Scrolling
                contentOffsetY={scrollY}
            >
                {items.map(index => {
                    return (
                        <Frame
                            // Visual & layout
                            width={scrollSize}
                            height={height}
                            radius={20}
                            backgroundColor="#fff"
                            top={(height + padding) * (index - 1)}
                            // Required by React
                            key={index}
                        />
                    )
                })}
            </Scroll>
            <Frame
                width={width}
                height={6}
                radius={3}
                backgroundColor="#fff"
                bottom={20}
                left={20}
            />
        </div>
    )
}

Framer Motion

const items = [0, 1, 2, 3, 4]
const height = 70
const padding = 10
const size = 150

export function FM18ScrollProgress() {
    const scrollY = useMotionValue(0)

    const width = useTransform(
        scrollY,
        [0, -getHeight(items) + size],
        ["calc(0% - 0px)", "calc(100% - 40px)"]
    )

    return (
        <Center>
            <motion.div
                style={{
                    width: 150,
                    height: 150,
                    borderRadius: 30,
                    overflow: "hidden",
                    position: "relative",
                    transform: "translateZ(0)",
                    cursor: "grab",
                }}
                whileTap={{ cursor: "grabbing" }}
            >
                <motion.div
                    style={{
                        width: 150,
                        height: getHeight(items),
                        y: scrollY,
                    }}
                    drag="y"
                    dragConstraints={{
                        top: -getHeight(items) + size,
                        bottom: 0,
                    }}
                >
                    {items.map(index => {
                        return (
                            <motion.div
                                style={{
                                    width: 150,
                                    height: height,
                                    borderRadius: 20,
                                    backgroundColor: "#fff",
                                    position: "absolute",
                                    top: (height + padding) * index,
                                }}
                                key={index}
                            />
                        )
                    })}
                </motion.div>
            </motion.div>
            <motion.div
                style={{
                    width: width,
                    height: 6,
                    borderRadius: 3,
                    backgroundColor: "#fff",
                    position: "absolute",
                    bottom: 20,
                    left: 20,
                }}
            />
        </Center>
    )
}

function getHeight(items) {
    const totalHeight = items.length * height
    const totalPadding = (items.length - 1) * padding
    const totalScroll = totalHeight + totalPadding
    return totalScroll
}

Overrides

const appState = Data({
    widthBar: "calc(0% - 0px)",
})

export function Progress_bar(): Override {
    return {
        width: appState.widthBar,
    }
}

export function Scroll(props): Override {
    const contentHeight = props.children[0].props.children[0].props.height
    const scrollSize = props.height as number
    const scrollDistance = -contentHeight + scrollSize

    const scrollY = useMotionValue(0)
    const width = useTransform(
        scrollY,
        [0, scrollDistance],
        ["calc(0% - 40px)", "calc(100% - 40px)"]
    )
    return {
        contentOffsetY: scrollY,
        onScroll() {
            appState.widthBar = width.get()
        },
    }
}

Leave a Reply