Animation » Example Animations » 16. Scroll: Refresh

16. Scroll: Refresh

The useTransform() hook is used to transform the vertical scroll distance to the small circle’s scale and opacity.

Try pulling down.

Code component

We have a scrollY Motion value that we pass to the draggable div’s y.

Now, this Motion value will update when you drag so that we can utilize useTransform() to create two other Motion values that change the scale and opacity of the small circle.

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

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

    return (
        <div>
            <motion.div
                style={{
                    width: 40,
                    height: 40,
                    borderRadius: "50%",
                    backgroundColor: "#fff",
                    position: "absolute",
                    top: 120,
                    left: "50%",
                    marginLeft: -20,
                    scale,
                    opacity,
                }}
            />
            <motion.div
                style={{
                    width: size,
                    height: size,
                    borderRadius: 30,
                    overflow: "hidden",
                    position: "relative",
                    transform: "translateZ(0)",
                    cursor: "grab",
                }}
                whileTap={{ cursor: "grabbing" }}
            >
                <motion.div
                    style={{
                        width: size,
                        height: getHeight(items),
                        y: scrollY,
                    }}
                    drag="y"
                    dragConstraints={{
                        top: -getHeight(items) + size,
                        bottom: 0,
                    }}
                >
                    {items.map((index) => {
                        return (
                            <div
                                style={{
                                    width: size,
                                    height: height,
                                    borderRadius: 20,
                                    backgroundColor: "#fff",
                                    marginBottom:
                                        index !== items.length - 1 ? 10 : 0,
                                }}
                            />
                        )
                    })}
                </motion.div>
            </motion.div>
        </div>
    )
}

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

Code overrides

You can only use useMotionValue() inside an override function (or code component). That’s inconvenient when you need to share the value between overrides, so here I used the non-hook version: motionValue().

Here, we use a prototyping Scroll  component, so we pass the scrollY Motion value to its contentOffsetY property. (There’s also contentOffsetX for scrolling horizontally.)

const scrollY = motionValue(0)

export function Scroll(Component): ComponentType {
    return (props) => {
        return <Component {...props} contentOffsetY={scrollY} />
    }
}

In the override for the circle, we then take scrollY and use it to make both its scale and opacity go from 0 to 1 when you pull the scroll down.

export function Circle(Component): ComponentType {
    return (props) => {
        const { style, ...rest } = props

        const scale = useTransform(scrollY, [0, 100], [0, 1])
        const opacity = useTransform(scrollY, [0, 100], [0, 1])

        return <Component {...rest} style={{ ...style, scale, opacity }} />
    }
}

A non-native scroll uses negative values when you scroll up and positive values when you pull it down. That’s why we use a range of [0, 100].

Native scroll: no overdag

We can’t make a version with a native scroll of this prototype because this one doesn’t have overdrag. So you can’t scroll beyond the edge of its content.


Join the Framer book mailing list    ( ± 6 emails/year )

GDPR

We use Mailchimp as our marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing per their Privacy Policy and Terms.



2 comments on “16. Scroll: Refresh”

  • danbillingham says:

    Hej – I am having some trouble with the scroll refresh example.

    Property ‘style’ does not exist on type ‘{ children?: ReactNode;}’

    Any thoughts.

    • Tes Mat says:

      That’s just a TypeScript error. It’ll probably still work.

      To get rid of the error: Give your override a type of <any> (like I did in the example project):

      export function Circle(Component): ComponentType<any> {
          …

      Or put a // @ts-ignore before the line that gives the error

      export function Circle(Component): ComponentType {
          return (props) => {
              // @ts-ignore
              const { style, ...rest } = props
      
              …

Leave a Reply