svelte-webview-navigator Svelte Themes

Svelte Webview Navigator

A Svelte navigator for WebView-based apps that emulates the Stack navigation behavior of native Apps.

svelte-webview-navigator

A Svelte navigator for WebView-based apps that emulates the Stack navigation behavior of native Apps.

You can use this navigator in Capacitor.js, Cordova and other JavaScript WebView adapters.

Note: although it's possible, it's not recommended to use this navigator as a router in a Web App, because the Web UX differs from the App UX, for example Apps don't really have links, only special entry points, Web Apps and Websites don't use a stack-based history navigation, and so on.

NPM Package

npm install svelte-webview-navigator

Documentation

Quick start

Make sure your <html>, <body> and all the elements wrapping the navigator component have height: 100%.

Add this <meta> tag to your html <head> to prevent unwanted pinch-to-zoom (maximum-scale) and to support the notch or similar things covering the screen (viewport-fit):

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover" />

Add this CSS block to your styles:

:root {
    --safe-area-inset-top: env(safe-area-inset-top);
    --safe-area-inset-right: env(safe-area-inset-right);
    --safe-area-inset-bottom: env(safe-area-inset-bottom);
    --safe-area-inset-left: env(safe-area-inset-left);
}

Now that the environment is set up, you can import the StackNavigator component and place it in your main component:

<script lang="ts">
    import {StackNavigator} from 'svelte-webview-navigator';
    import Home from './Home.svelte';
</script>

<StackNavigator main={Home} />

The Home component will be the first one mounted by the navigator and the one the user will get back to when swiping back or pressing the hardware back button.

Views

By default the content in your page is displayed as-is in a container with overflow: hidden and height: 100%. This means that if the page content is taller than the screen height it will get clipped and that if there is a notch it can cover parts of the content.

This navigator provides 3 wrappers for your views/pages: SafeAreaView, ScrollView, SafeAreaScrollView.

When you create a new page in your app you will probably need to use one of those to make sure that the page content is correctly displayed.

SafeAreaView and SafeAreaScrollView add padding at the top, right, bottom and left sides of the page to make sure that its content is never behind a notch. If there is no notch they have no effect.

wrapper content scrollable content behind the notch
none no yes
ScrollView yes yes
SafeAreaView no no
SafeAreaScrollView yes no

By calling useNavigation inside a page (a component rendered by the navigator, e.g. Home in the example above) you get access to a handful of functions and stores that can be used to navigate and get the current status of the navigator.

As an example, here is a page with a back button:

<script lang="ts">
    const {goBack, canGoBack} = useNavigation();
</script>

<button on:click={() => {
    if ($canGoBack)
        goBack();
    } else {
        alert('The stack contains only the current page, cannot go back')
    }
}}>Back</button>

To navigate forward you can call either navigate or openModal. They both accept the same parameters (a component or a component and its props), but the presentation is different.

<script lang="ts">
    import AnotherPage from './AnotherPage.svelte';

    const {navigate} = useNavigation();
</script>

<button
    on:click={() => {
        navigate(AnotherPage);
        // or openModal(AnotherPage);
    }}>Link</button
>
<button
    on:click={() => {
        navigate(AnotherPage, {someProp: 42});
        // or openModal(AnotherPage, {someProp: 42});
    }}>Link with props</button
>

Here is a complete reference of the functions and stores provided by useNavigation:

name type description
navigate function Opens a new page, pushing it to the top of the stack and pausing the current one.
openModal function Same as navigate, but presents the new page as a modal.
goBack function Goes to the previous page. If the stack contains only one page this function will throw.
canGoBack store Store that contains true if goBack can be invoked, false otherwise.
navigating store Store that contains true if the Navigator is handling a navigation request, false otherwise.
swiping store Store that contains true if a swipe gesture is in progress.

All navigation functions return a Promise that resolves once the navigation is complete (i.e. the transition has ended), or rejects if there was an error (e.g. goBack was called even if canGoBack was false).

The StackNavigator component has also a on:error event to which you can attach a listener that will receive all the errors that can occur during navigation.

Returning values to previous views

Adjacent stack elements can communicate by combining onResume and goBack components. For example, you could have a component that opens another one as a modal and when that modal closes you want to return a value representing some sort of user choice. To do that, on the first component you would register an onResume callback that accepts a parameter, while on the second component (the one showed as a modal) you would call goBack passing a value to it.

Here is the same example as code:

<script lang="ts">
    import SomeModal from './SomeModal.svelte';
    import {useNavigation} from 'svelte-webview-navigator';
    const {openModal, onResume} = useNavigation();

    onResume((returnValue?: string) => {
        alert('resumed with value ' + returnValue);
    });
</script>

// Primary page
<button on:click={() => openModal(SomeModal)}>open modal</button>
<script lang="ts">
    import {useNavigation} from 'svelte-webview-navigator';
    const {goBack} = useNavigation();
</script>

// SomeModal.svelte
<button on:click={() => goBack('hello!')}>close modal</button>

Hardware back button

To handle the hardware back button in your framework of choice you can manually call the goBack function from the router context. As an example, if you are using Capacitor.js (or Ionic) you can install the @capacitor/app plugin (details in the docs here):

<script lang="ts">
    import {get} from 'svelte/store';
    import {App} from '@capacitor/app';
    import type {StackNavigatorContext} from 'svelte-webview-navigator';
    import {StackNavigator} from 'svelte-webview-navigator';
    import Home from './Home.svelte';
    import {onMount} from 'svelte';

    let navigatorContext: StackNavigatorContext | undefined;
    function handleBackButton() {
        if (!navigatorContext) {
            return;
        }
        if (get(navigatorContext.canGoBack)) {
            navigatorContext.goBack();
        } else {
            App.exitApp();
        }
    }
    onMount(() => {
        const listener = App.addListener('backButton', handleBackButton);
        return () => {
            listener.then((l) => l.remove());
        };
    });
</script>

<StackNavigator main={Home} bind:context={navigatorContext} />

Customizations

Colors

You can customize the appearance of the navigator by setting the following CSS variables:

variable description default
--svelte-stack-nav-page-background The background of the current page. #fff
--svelte-stack-nav-modal-offset-top The distance from the safe area top border and the modal top border. 30px
--svelte-stack-nav-modal-border-radius The border radius of the modal window. 30px
--svelte-stack-nav-modal-landscape-width The width of the modal window when the device is in landscape mode. 68%
--svelte-stack-nav-overlay-background The page overlay color. This overlay is used to darken the content of a page when transitioning to another one. rgba(0, 0, 0, 0.2)

You can pass those variables directly to the StackNavigator style property (e.g. <StackNavigator style="--svelte-stack-nav-page-background: red" ... />) or you can set them in your stylesheet (e.g. :root { --svelte-stack-nav-page-background: red }).

As with all CSS variables, you can customize them based on the preferred color scheme of the user using the following media query:

@media (prefers-color-scheme: dark) {
    :root {
        --svelte-stack-nav-page-background: black;
    }
}

Page transitions

You can customize the transition between pages by creating your transition functions and passing them to the navigator transitions property or by passing a premade transition object:

<script lang="ts">
    import {StackNavigator, premadeTransitions} from 'svelte-webview-navigator';
    import Home from './Home.svelte';
</script>

<StackNavigator transitions={premadeTransitions.slideRotate} main={Home} />

You can also customize the easing and duration of the transition by passing the transitionEasing and transitionDuration properties. The easing is can be imported from svelte/easing, while the duration is a number in milliseconds.

prop description default
transitions An object containing a transition for the page in front of the user and the page behind it. premadeTransitions.slide
transitionEasing An easing function u = f(t) where both t and u are numbers from 0 to 1. expoInOut
transitionDuration The duration of the transition in milliseconds. 600

If you want to create your custom transition you can use the following template:

const myTransition = {
    front: (t: number): string => `opacity: ${t}; transform: translateX(${10 * (1 - t)}%)`,
    back: (t: number): string => `opacity: ${1 - t}; transform: translateX(${-10 * t}%)`
};

front describes the transition of the current page, while back is the page behind it. t is 0 when a new page has just been pushed onto the stack and the transition is about to start, while it is 1 when the transition has ended and the navigation has completed. When the user goes to the previous page t goes from 1 to 0 to reverse the original transition.

Swipe gestures

The StackNavigator supports the swipe back and swipe down gestures out of the box. If you want to tailor the swipe interaction you can tweak the following properties:

prop description default
enableSwipeGestures Whether the swipe gestures are enabled or disabled. true
swipeGestureSensitiveAreaWidth Portion of the left-most part of the screen that should be listening for swipe gestures. 0.05 (5%)
swipeGestureSensitiveAreaHeight Portion of the upper-most part of the screen that should be listening for swipe gestures. 1 (100%)
swipeGestureSpeedThresholdPixelsPerMillisecond Number of pixels per millisecond that should trigger a call to the goBack function 0.1
swipeGestureThreshold When the user drags the current page crossing this threshold, goBack is called regardless of the speed of the gesture. 0.4 (40%)
swipeGestureMaxSpeedSamples Number of samples used to determine the speed of the swipe gesture. 100
swipeGestureMinDistance Minimum distance that the finger has to travel to activate the swipe gesture. 50

Top categories

Loading Svelte Themes