Advanced
The examples below use
useWalkthroughStepfor clarity, but every option also works with theWalkthroughStepcomponent from the Usage guide. They share the same configuration.
Tuning the mask with layoutAdjustments
Sometimes the measured view doesn't quite cover what you want to highlight.
layoutAdjustments lets you pad, shrink, or shift the mask:
addPadding: expand the mask on all sidesaddX/addY/addWidth/addHeight: shift or resize individual edgesx/y/width/height: override the mask position or size entirelyminX/minY/maxX/maxY: clamp the mask within bounds
const { onLayout } = useWalkthroughStep({
number: 2,
contentComponent: ButtonOverlay,
layoutAdjustments: {
addPadding: 8,
},
});
The adjusted rectangle is available on the step as computedMask, which is what
the displayer renders and what your content component should position against.
Programmatic control
Every function on the context (start, stop, next, previous, goTo,
updateStep, registerStep) can be triggered from any component inside the
provider.
Navigating functions follow a simple lifecycle: start only runs while the
walkthrough is inactive, whereas next, previous and goTo only run
while it is active. Calling them out of state is a no-op, so you don't need
to guard them yourself.
Via useWalkthrough()
import { useWalkthrough } from "rn-interactive-walkthrough";
function SettingsScreen() {
const { goTo, stop } = useWalkthrough();
return (
<View>
<Button title="Skip tour" onPress={stop} />
<Button title="Jump to step 3" onPress={() => goTo(3)} />
</View>
);
}
Step lifecycle callbacks
onStart and onFinish fire when a step becomes active and when the walkthrough
moves past it. Each receives a WalkthroughCallback with the timestamp.
onBackground fires if the app goes to the background while the step is active.
const { onLayout } = useWalkthroughStep({
number: 2,
contentComponent: NearbyUsersTooltip,
onStart: ({ time }) => {
console.log("Step 2 started at", time);
},
onFinish: ({ time }) => {
console.log("Step 2 finished at", time);
},
onBackground: () => {
console.log("App went to the background during step 2");
},
});
Updating steps
updateStep(identifier, partialStep) lets you modify a registered step at
runtime, useful when the position of a highlighted element changes or you want
to toggle interactivity on a mask.
const { updateStep, steps } = useWalkthrough();
const searchStep = steps.find((s) => s.identifier === "search");
updateStep("search", {
computedMask: { ...searchStep?.computedMask, allowInteraction: false },
});
Global appearance
backdropColor and animationDuration can be set as props on
WalkthroughProvider:
<WalkthroughProvider
backdropColor="rgba(0, 0, 0, 0.85)"
animationDuration={600}
>
<MyAwesomeApp />
</WalkthroughProvider>
Pass debug to the provider to render colored outlines around every mask and
log walkthrough transitions to the console, which is very handy while building a
tour.
Custom animations
Transitions are built on react-native-reanimated. The provider accepts an
animations prop where every section is optional, so you can override just the
pieces you care about:
import { Easing } from "react-native-reanimated";
<WalkthroughProvider
animations={{
backdrop: {
easing: Easing.inOut(Easing.quad),
},
content: {
entering: FadeInDown.duration(300),
exiting: FadeOut.duration(150),
},
}}
>
<MyAwesomeApp />
</WalkthroughProvider>;
backdrop.entering/backdrop.exiting: enter/exits for the backdrop pressablesbackdrop.easing: easing curve for the mask morph between stepscontent.entering/content.exiting/content.layout: layout animations for the step content container
Pulsing mask
Once a step is visible and has finished animating into place, the mask can
pulse (scale up and down repeatedly) to draw the user's eye to the highlighted
action. It is disabled by default, and is switched on through a single pulse
object on the provider, so the whole behaviour lives in one place:
<WalkthroughProvider
pulse={{
enabled: true,
delay: 600,
duration: 500,
scale: 1.08,
easing: Easing.inOut(Easing.sin),
}}
>
<MyAwesomeApp />
</WalkthroughProvider>
Enabled from scratch, it uses delay: 400, duration: 400, scale: 1.05,
with a fast ease-out. The pulse starts only after the mask morph has settled
(delay counts from that moment), so it never fights the transition between
steps. It restarts cleanly each time a step becomes active, including when you
navigate backwards.
Options:
enabled: turn the pulse on or off (defaults tofalse)delay: how long to wait (in ms) after the mask settles before the first beatduration: how long (in ms) one bigger-to-smaller (or smaller-to-bigger) transition takesscale: the peak scale of the beat, relative to the mask's base size. Use a value above1to grow the mask, below1to shrink it;1disables the motioneasing: the easing curve applied to each beat
The mask is aligned to whole pixels, so during a beat a small target can appear to move in 1px steps; at rest it always sits exactly where the morph left it.
Per-step overrides
A single step can override just some pulse options; the provider value fields
you don't touch still win from the defaults. The step's pulse is merged over
the provider's, per key:
const { onLayout } = useWalkthroughStep({
number: 2,
contentComponent: ButtonOverlay,
pulse: { enabled: true, scale: 1.12 },
});
Targets in scrollable containers
If a highlighted view sits inside a ScrollView, its on-screen position changes
as the user scrolls. The library doesn't auto-scroll targets into view. Instead,
an interactive tour guides the user to reach a target themselves, and you can
keep the mask glued to a target while the user drags by re-measuring from the
scroll events. measureMask() measures the live window position, so it stays
accurate no matter how far you scroll:
function ScrollableScreen() {
const { currentStep } = useWalkthrough();
const scrollRef = useRef<ScrollView>(null);
const { onLayout } = useWalkthroughStep({
number: 2,
layoutLock: true,
contentComponent: ButtonOverlay,
});
return (
<ScrollView
ref={scrollRef}
onScroll={() => currentStep?.measureMask()}
onScrollEndDrag={() => currentStep?.measureMask()}
onMomentumScrollEnd={() => currentStep?.measureMask()}
scrollEventThrottle={16}
>
{/* ...long content... */}
<View onLayout={onLayout}>{/* highlighted target */}</View>
</ScrollView>
);
}
The onScroll handler keeps the mask glued to the target while the user
scrolls, and the scroll-end handlers snap it to its final position once the
scroll settles. layoutLock stops onLayout from re-measuring with a stale
position.
Per-step animation duration
The mask morphs between steps over the provider's animationDuration (default
300ms). You can tune it per step without a global change:
useWalkthroughStep({
number: 2,
animationDuration: 150,
});
The backdrop fade still uses the provider-wide duration.