16. Scroll: Refresh
The useTransform()
hook is used to transform the vertical scroll distance to the small circle’s scale
and opacity
.
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.