17. Page: Indicators
(You can also tap the indicators to directly go to a page.)
Code component
We use React’s useState()
hook to keep track of the current page.
const pages = [1, 2, 3, 4, 5]
const indicatorSize = 10
const indicatorPadding = 10
const indicatorWidth = pages.length * indicatorSize
const indicatorPaddingTotal = (pages.length - 1) * indicatorPadding
const indicatorWidthTotal = indicatorWidth + indicatorPaddingTotal
const indicatorAlpha = 0.3
export default function CC_17_Page_Indicators(props) {
const [current, setCurrent] = useState(0)
return (
<div
style={{
width: 400,
height: 400,
...props.style,
display: "flex",
placeItems: "center",
placeContent: "center",
}}
>
<Page
width={150}
height={150}
radius={30}
currentPage={current}
onChangePage={(current, previous) => setCurrent(current)}
>
{pages.map((index) => {
return (
<div
style={{
width: 150,
height: 150,
borderRadius: 30,
backgroundColor: "#fff",
}}
/>
)
})}
</Page>
{pages.map((index) => {
return (
<motion.div
style={{
width: indicatorSize,
height: indicatorSize,
borderRadius: "50%",
backgroundColor: "#fff",
position: "absolute",
top: "calc(50% + 100px)",
left: `calc(50% + ${index - 1} * ${
indicatorSize + indicatorPadding
}px)`,
x: -indicatorWidthTotal / 2,
}}
animate={{
opacity: current === index - 1 ? 1 : indicatorAlpha,
}}
onTap={() => setCurrent(index - 1)}
/>
)
})}
</div>
)
}
Pro tip: When using the <Page>
component in a React project (like in the CodeSandbox above), set the box-sizing
of all elements to border-box
. Like this, for instance:
* {
box-sizing: border-box;
}
<Page>
box-sizing
, CSS universal selector (*
)
Code overrides
Here I used Framer’s createStore()
to communicate between the overrides.
The Indicators()
override is attached to the parent frame of the indicators. It maps through its children
(the five indicators) and clones them, giving them each an animate
to the correct opacity
and an onTap()
event so that you can tap them to change the page.
const useStore = createStore({ currentIndicator: 0 })
export function Indicators(Component): ComponentType<any> {
return (props) => {
const { children, ...rest } = props
const [store, setStore] = useStore()
return (
<Component
{...rest}
children={children.map((indicator, index) => {
let opacity = 0.3
if (index === store.currentIndicator) {
opacity = 1
}
return cloneElement(indicator, {
animate: { opacity: opacity },
onTap: () => setStore({ currentIndicator: index }),
})
})}
/>
)
}
}
export function Page(Component): ComponentType {
return (props) => {
const [store, setStore] = useStore()
return (
<Component
{...props}
currentPage={store.currentIndicator}
onChangePage={(current, previous) =>
setStore({ currentIndicator: current })
}
/>
)
}
}
15 comments on “17. Page: Indicators”
Leave a Reply
You must be logged in to post a comment.
Please take a look, https://framer.com/share/g9SyCPIVasosUGPblSXC/vS9mxUz1B
Try to show the button when page component is tapped and remain hidden when page is swiped.
How do you tell between swipe and tap gesture?
Sorry, I can’t look at the code of a Preview (
/share/
URL).(Tried to duplicate it, but
g9SyCPIVasosUGPblSXC
doesn’t seem to be the project ID.)Please try this
https://framer.com/projects/props-control–bVeOYMfcndo0DxLjQ2za-2k7tY
It does work. I can’t scroll until I first press down and release (
onTap()
is called when you release). Then, I can scroll.But, it’s important to note that
onTap()
is only called when you press down and release on the same spot. So when I press down somewhere without scrolling, it locks again. When I press another time, it unlocks again.Anyway, It’s not exactly clear to me what you’re trying to do, but to track scroll gestures you could use
onScrollStart()
andonScrollEnd()
(also:onScroll()
, but that one gets called continuously).Sorry, wrong link 😂.
I mistyped the link for another question.
https://framer.com/projects/swipe-tap–tNXkkFLY9bYV3uR66dTd-dp2zS
Try to achieve:
During swiping, button is hidden. When scroll got tapped, show button.
I would try to add an
isSwiping
state (in Data) that gets set totrue
ononScrollStart()
and back tofalse
ononScrollEnd()
. (I think those events also work in a Page.)This way, you could let the button appear only when
isSwiping
isfalse
. So only when the user taps, and not at the end of a swipe gesture.Hi, I seem to be having a few issues trying to follow the Override example. I’ll attach the image. With I could attach a few of them 🙂 Thanks for your help!
– The map error : “Property ‘map’ does not exist on type ‘ReactNode’. Property ‘map’ does not exist on type ‘string’.(2339)”
– The cloneElement: “‘cloneElement’ cannot be used as a value because it was imported using ‘import type’.(1361)”
The map error: That’s just TypeScript (probably) not realizing that
children
is an array and that we want to use themap()
function on it. The override will work anyway, but if you want, you can silence this error by putting a// @ts-ignore
above it (see the example project).The cloneElement error: Like it says, you imported
cloneElement
as a TypeScripttype
object, probably together withComponentType
like this:You should import it like a normal object (separately), like this:
Hey there. Is there a way to turn off page animations?
Not when swiping, but you can set
animateCurrentPageUpdate
tofalse
and page with buttons.Here’s an example:
Example project: https://framer.com/projects/new?duplicate=vMpLuGiPe16YO7tYtFWh
Changing the
currentPage
like that only seems to work well with legacy overrides (didn’t work withcreateStore()
).You can also make it impossible to swipe by setting
dragEnabled
tofalse
.Thanks a bunch! I have no idea how you discover this stuff. I can’t find it in the docs anywhere.
His Tes,
How would one go about adding a Tap/Click event to the Small Page Indicators to change the corresponding page? // Appreciate the knowledge …
That isn’t too hard. The indicators need an event that changes the current indicator (the
current
state, orcurrentIndicator
in the data store) and we also need to pass that value to the Page component’scurrentPage
property, so that it reacts to changes.It actually makes sense to add this, so I updated the example.
That’s great Tes .. that is simple // Appreciated
I mean, the override type indicator works on the framer canvas. But it doesn’t work on framer web.