Advent of SvelteKit 2022

Renderless North Pole distance

NorthPoleDistance is a renderless component, meaning it only performs some logic and passes props to its slot. The consumer of the component can read those props inside the slot (with let:prop) and decide what to do with them.

Example 1

Waiting for location data...

Example 2

Waiting for location data...

Challenge

Source code

+page.svelte

<script>
	import NorthPoleDistance from './NorthPoleDistance.svelte';
</script>

<h1>Renderless North Pole distance</h1>
<p>
	<code>NorthPoleDistance</code> is a renderless component, meaning it only performs some logic and passes
	props to its slot. The consumer of the component can read those props inside the slot (with let:prop)
	and decide what to do with them.
</p>
<div class="flow container">
	<h2>Example 1</h2>
	<NorthPoleDistance let:distance let:toggleUnit let:unit>
		<p>You are currently: {distance} {unit}s away from the North Pole.</p>
		<button class="button-green" on:click={toggleUnit}>Toggle</button>
	</NorthPoleDistance>
	<h2>Example 2</h2>
	<NorthPoleDistance let:distance let:toggleUnit let:unit>
		<div class="example-2 flow">
			<p>Current distance from the North Pole</p>
			<p class="distance">{distance} {unit}</p>
			<button on:click={toggleUnit} class="button-red">
				Switch to {unit === 'km' ? 'miles' : 'km'}
			</button>
		</div>
	</NorthPoleDistance>
</div>

<style>
	.container {
		width: 100%;
		max-width: var(--size-content-2);
	}
	button {
		appearance: none;
		border: none;
		background: none;
		cursor: pointer;
		transition: transform 0.2s ease;
		color: white;
		padding: 0.5rem 1rem;
		border-radius: var(--radius-2);
	}

	.button-green {
		background: var(--green-7);
	}

	.button-red {
		background: var(--red-6);
	}

	button:hover {
		filter: brightness(0.95);
	}

	button:active {
		transform: scale(0.95);
	}

	.example-2 {
		text-align: center;
		border: 2px solid black;
		border-radius: var(--radius-3);
		padding: 1rem;
	}

	.distance {
		font-size: var(--font-size-4);
	}
</style>

NorthPoleDistance.svelte

<script lang="ts">
	import { getDistanceKm, getDistanceMiles } from './distance';
	import { geolocation } from './geolocation';

	$: distance =
		unit === 'km'
			? getDistanceKm($geolocation.latitude, $geolocation.longitude)
			: getDistanceMiles($geolocation.latitude, $geolocation.longitude);

	let unit: 'km' | 'mile' = 'mile';

	function toggleUnit() {
		if (unit === 'km') {
			unit = 'mile';
		} else {
			unit = 'km';
		}
	}
</script>

{#if isNaN(distance)}
	<p>Waiting for location data...</p>
{:else}
	<slot {distance} {toggleUnit} {unit} />
{/if}

distance.ts

// this file has helpers to get the distance from a given latitude-longtitude to the north pole

// source: https://stackoverflow.com/questions/27928/calculate-distance-between-two-latitude-longitude-points-haversine-formula

export function getDistanceKm(lat: number, lon: number) {
	var R = 6371; // Radius of the earth in km
	var dLat = deg2rad(90 - lat); // deg2rad below
	var dLon = deg2rad(135 - lon);
	var a =
		Math.sin(dLat / 2) * Math.sin(dLat / 2) +
		Math.cos(deg2rad(lat)) * Math.cos(deg2rad(90)) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
	var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
	var d = R * c; // Distance in km
	return Math.round(d);
}

export function getDistanceMiles(lat: number, lon: number) {
	return Math.round(getDistanceKm(lat, lon) * 0.621371);
}

export function deg2rad(deg: number) {
	return deg * (Math.PI / 180);
}

geolocation.ts

import { browser } from '$app/environment';
import { readable } from 'svelte/store';

export const geolocation = readable<GeolocationCoordinates>(
	{
		accuracy: 0,
		latitude: Infinity,
		longitude: Infinity,
		altitude: null,
		altitudeAccuracy: null,
		heading: null,
		speed: null
	},
	(set) => {
		if (browser) {
			let watcher = navigator.geolocation.watchPosition((pos) => set(pos.coords));

			return () => {
				navigator.geolocation.clearWatch(watcher);
			};
		}
	}
);