Skip to main content

How it works

Useful when something lands in the wrong place, or when you want to know whether the library will cope with a layout it has not seen before.

Measuring

Each step measures its target with measureInWindow. That is the API that replaced findNodeHandle, which most of the older tour libraries still use even though it is deprecated. The result comes back in window coordinates.

Android reports 0x0 for a view that has not been through layout yet, so a zero sized measurement counts as "not ready" and gets retried a few times before the tour gives up on that step.

The hole

The dim is one SVG path filled with the even odd rule: the screen rect, then a rounded rect subpath over the target. Even odd makes the inner shape punch through, which is how the hole gets real rounded corners.

It used to be a view with a huge border and a hollow middle. That works and needs no SVG, but on iOS any prop update on a bordered view makes the platform regenerate a border image on the main thread. During a transition that was 52% of main thread CPU, measured with Instruments.

The ring and the halo

The ring sits padding px outside the hole, so the band between them stays dimmed. That band is what your eye reads as a halo, and the glow around the ring adds to it.

Placement

The card goes wherever there is room:

target center above the middle -> card below
target center below the middle -> card above

The middle here is the overlay's middle, not the window's. The overlay measures its own box, so a provider inside a sheet or under a header still decides correctly.

Horizontally the card is centered on the target and clamped to the screen margins. When the clamp pulls the card away from the target, the arrow slides so it keeps pointing at the thing being described.

:::note One deliberate difference The design this came from centers the card on the screen, and its arrow only lines up because every target in it happens to be centered. A library cannot count on that. :::

Touch

Touch is handled separately from the visuals, because the two want different shapes.

By default one layer covers the whole screen and a tap advances. With allowTargetInteraction you get four strips around the hole instead, so the target stays tappable. The scrim itself never captures touches, it only paints.

Animation

Everything runs on Reanimated shared values, so transitions stay on the UI thread.

Between steps the hole and ring slide to the new geometry over 550ms with an ease out expo curve, and the card slides with them. The card also replays its entrance, the rise and settle but not the fade. Fading out while crossing the screen looks like a blink instead of movement.

Closing fades the scrim over 340ms while the card drops and shrinks over 300ms. Only then does the overlay unmount.