20. Scroll: Refresh
The useTransform()
hook is used to change the scroll distance to the scale
and opacity
of the small circle.
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,
}
}