✨ Use smooth scroll function to navigate to anchor
Using a eased RAF function to scroll to a specific target Avoid using `scrollIntoView` or smooth behavior as it doesn't work on Safari and others.
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { cartId } from '$utils/stores/shop'
|
import { cartId } from '$utils/stores/shop'
|
||||||
import { addToCart } from '$utils/functions/shop'
|
import { addToCart } from '$utils/functions/shop'
|
||||||
|
import { smoothScroll } from '$utils/functions'
|
||||||
// Components
|
// Components
|
||||||
import Button from '$components/atoms/Button.svelte'
|
import Button from '$components/atoms/Button.svelte'
|
||||||
import Image from '$components/atoms/Image.svelte'
|
import Image from '$components/atoms/Image.svelte'
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
|
|
||||||
<div class="poster">
|
<div class="poster">
|
||||||
{#if image}
|
{#if image}
|
||||||
<a href="/shop/poster-{location.slug}#poster" sveltekit:noscroll sveltekit:prefetch>
|
<a href="/shop/poster-{location.slug}" on:click={() => smoothScroll('poster', false)} sveltekit:noscroll sveltekit:prefetch>
|
||||||
<Image
|
<Image
|
||||||
id={image.id}
|
id={image.id}
|
||||||
sizeKey="product"
|
sizeKey="product"
|
||||||
@@ -33,15 +34,16 @@
|
|||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<Button
|
<Button
|
||||||
size="xsmall"
|
size="xsmall"
|
||||||
url="/shop/poster-{location.slug}#poster"
|
url="/shop/poster-{location.slug}"
|
||||||
text="View"
|
text="View"
|
||||||
|
on:click={() => setTimeout(() => smoothScroll('poster', false), 1000)}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
tag="button"
|
tag="button"
|
||||||
size="xsmall"
|
size="xsmall"
|
||||||
on:click={() => addToCart($cartId, product)}
|
|
||||||
text="Add to cart"
|
text="Add to cart"
|
||||||
color="pink"
|
color="pink"
|
||||||
|
on:click={() => addToCart($cartId, product)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
import { goto } from '$app/navigation'
|
import { goto } from '$app/navigation'
|
||||||
import { getContext } from 'svelte'
|
import { getContext } from 'svelte'
|
||||||
import { shopCurrentProductSlug } from '$utils/stores/shop'
|
import { shopCurrentProductSlug } from '$utils/stores/shop'
|
||||||
|
import { smoothScroll } from '$utils/functions'
|
||||||
|
|
||||||
export let isOver: boolean = false
|
export let isOver: boolean = false
|
||||||
|
|
||||||
@@ -22,6 +23,8 @@
|
|||||||
const quickLocationChange = ({ target: { value }}: any) => {
|
const quickLocationChange = ({ target: { value }}: any) => {
|
||||||
const newPath = `/shop/poster-${value}`
|
const newPath = `/shop/poster-${value}`
|
||||||
goto(newPath, { replaceState: true, noscroll: true, keepfocus: true })
|
goto(newPath, { replaceState: true, noscroll: true, keepfocus: true })
|
||||||
|
// Scroll to anchor
|
||||||
|
setTimeout(() => smoothScroll('poster'), 1000)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import { getContext, onMount } from 'svelte'
|
import { getContext, onMount } from 'svelte'
|
||||||
import anime, { type AnimeTimelineInstance } from 'animejs'
|
import anime, { type AnimeTimelineInstance } from 'animejs'
|
||||||
import { cartOpen } from '$utils/stores/shop'
|
import { cartOpen } from '$utils/stores/shop'
|
||||||
|
import { smoothScroll } from '$utils/functions'
|
||||||
// Components
|
// Components
|
||||||
import Image from '$components/atoms/Image.svelte'
|
import Image from '$components/atoms/Image.svelte'
|
||||||
import ButtonCart from '$components/atoms/ButtonCart.svelte'
|
import ButtonCart from '$components/atoms/ButtonCart.svelte'
|
||||||
@@ -106,7 +107,7 @@
|
|||||||
<ul>
|
<ul>
|
||||||
{#each shopLocations as { name, slug }}
|
{#each shopLocations as { name, slug }}
|
||||||
<li class:is-active={product && slug === product.location.slug}>
|
<li class:is-active={product && slug === product.location.slug}>
|
||||||
<a href="/shop/poster-{slug}" sveltekit:prefetch sveltekit:noscroll>
|
<a href="/shop/poster-{slug}" on:click={() => smoothScroll('poster')} sveltekit:prefetch sveltekit:noscroll>
|
||||||
{name}
|
{name}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
import { getContext, onMount } from 'svelte'
|
import { getContext, onMount } from 'svelte'
|
||||||
import anime, { type AnimeTimelineInstance } from 'animejs'
|
import anime, { type AnimeTimelineInstance } from 'animejs'
|
||||||
import { DELAY } from '$utils/contants'
|
import { DELAY } from '$utils/contants'
|
||||||
import { sleep } from '$utils/functions'
|
import { sleep, smoothScroll } from '$utils/functions'
|
||||||
import { reveal, fade as animeFade } from '$animations/index'
|
import { reveal, fade as animeFade } from '$animations/index'
|
||||||
// Components
|
// Components
|
||||||
import Metas from '$components/Metas.svelte'
|
import Metas from '$components/Metas.svelte'
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
{settings.description}
|
{settings.description}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Button text="Explore locations" url="#locations">
|
<Button text="Explore locations" on:click={() => smoothScroll('locations')}>
|
||||||
<IconEarth animate={true} />
|
<IconEarth animate={true} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -111,10 +111,11 @@
|
|||||||
<ListCTAs>
|
<ListCTAs>
|
||||||
<li>
|
<li>
|
||||||
<BoxCTA
|
<BoxCTA
|
||||||
url="{$page.url.pathname}#locations"
|
url="{$page.url.pathname}"
|
||||||
icon="globe"
|
icon="globe"
|
||||||
label="Discover locations"
|
label="Discover locations"
|
||||||
alt="Globe"
|
alt="Globe"
|
||||||
|
on:click={() => smoothScroll('locations')}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
9
src/utils/functions/easing.ts
Normal file
9
src/utils/functions/easing.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Ease: In Out Quart
|
||||||
|
*/
|
||||||
|
export const easeInOutQuart = (t: number, b: number, c: number, d: number) => {
|
||||||
|
t /= d/2
|
||||||
|
if (t < 1) return c/2 * t * t * t * t + b
|
||||||
|
t -= 2
|
||||||
|
return -c / 2 * (t * t * t * t - 2) + b
|
||||||
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { easeInOutQuart } from './easing'
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Throttle function
|
* Throttle function
|
||||||
*/
|
*/
|
||||||
@@ -149,4 +152,51 @@ export const scrollToTop = (delay?: number) => {
|
|||||||
} else {
|
} else {
|
||||||
scroll()
|
scroll()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smooth Scroll to an element
|
||||||
|
* @description Promised based
|
||||||
|
* @url https://www.youtube.com/watch?v=oUSvlrDTLi4
|
||||||
|
*/
|
||||||
|
const smoothScrollPromise = (target: HTMLElement, duration: number = 1600): Promise<void> => {
|
||||||
|
const position = target.getBoundingClientRect().top + 1
|
||||||
|
const startPosition = window.scrollY
|
||||||
|
const distance = position - startPosition
|
||||||
|
let startTime: number = null
|
||||||
|
|
||||||
|
// Return Promise
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (!(target instanceof Element)) throw new TypeError('Argument 1 must be an Element')
|
||||||
|
if (typeof window === 'undefined') return
|
||||||
|
|
||||||
|
// Scroll to animation
|
||||||
|
const animation = (currentTime: number) => {
|
||||||
|
if (startTime === null) startTime = currentTime
|
||||||
|
const timeElapsed = currentTime - startTime
|
||||||
|
// Create easing value
|
||||||
|
const easedYPosition = easeInOutQuart(timeElapsed, startPosition, distance, duration)
|
||||||
|
// Scroll to Y position
|
||||||
|
window.scrollTo(0, easedYPosition)
|
||||||
|
// Loop or end animation
|
||||||
|
if (timeElapsed < duration) {
|
||||||
|
requestAnimationFrame(animation)
|
||||||
|
} else {
|
||||||
|
return resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(animation)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export const smoothScroll = async (hash: string, changeHash: boolean = true, callback?: Function) => {
|
||||||
|
const target = document.getElementById(hash)
|
||||||
|
|
||||||
|
smoothScrollPromise(target).then(() => {
|
||||||
|
if (changeHash) {
|
||||||
|
location.hash = hash
|
||||||
|
}
|
||||||
|
callback && callback()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user