Framer X » Animation » Example Animations » 20. Scroll: Refresh

20. Scroll: Refresh

The useTransform() hook is used to change the scroll distance to the scale and opacity of the small circle.

Open the 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 CC20ScrollRefresh() {
    const scrollY = useMotionValue(0)
    const scale = useTransform(scrollY, [0, 100], [0, 1])
    const opacity = useTransform(scrollY, [0, 100], [0, 1])

    return (
        <div>
            <Frame
                // Visual & layout
                size={40}
                radius={20}
                backgroundColor="#fff"
                center="x"
                top={120}
                // Transformation
                scale={scale}
                opacity={opacity}
            />
            <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>
        </div>
    )
}

Framer Motion

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

export function FM20ScrollRefresh() {
    const scrollY = useMotionValue(0)
    const scale = useTransform(scrollY, [0, 100], [0, 1])
    const opacity = useTransform(scrollY, [0, 100], [0, 1])

    return (
        <Center>
            <motion.div
                style={{
                    width: 40,
                    height: 40,
                    borderRadius: 20,
                    backgroundColor: "#fff",
                    position: "absolute",
                    top: 120,
                    left: "50%",
                    marginLeft: -20,
                    scale: scale,
                    opacity: opacity,
                }}
            />
            <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>
        </Center>
    )
}

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

Overrides

The useMotionValue() hook can only be used inside an override function. That’s not really convenient when you have to share the value between overrides, so here I used the non-hook version: motionValue().

const scrollY = motionValue(0)

export function Circle(): Override {
    const scale = useTransform(scrollY, [0, 100], [0, 1])
    const opacity = useTransform(scrollY, [0, 100], [0, 1])

    return {
        scale: scale,
        opacity: opacity,
    }
}

export function Scroll(): Override {
    return {
        contentOffsetY: scrollY,
    }
}

Leave a Reply