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 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