124 lines
3.8 KiB
Svelte
124 lines
3.8 KiB
Svelte
<script lang="ts">
|
|
import { onMount } from 'svelte'
|
|
import { writable } from 'svelte/store'
|
|
import EmblaCarousel, { EmblaCarouselType } from 'embla-carousel'
|
|
// Components
|
|
import Image from '$components/atoms/Image.svelte'
|
|
|
|
export let slides: any
|
|
|
|
let arrowEl: HTMLElement
|
|
let carouselEl: HTMLElement
|
|
let carousel: EmblaCarouselType
|
|
let currentSlide = 0
|
|
let arrowDirection: string = null
|
|
$: isFirstSlide = currentSlide === 0
|
|
$: isLastSlide = currentSlide === slides.length - 1
|
|
|
|
|
|
/** Navigate to specific slide */
|
|
const goToSlide = (index: number = 0) => {
|
|
carousel.scrollTo(index)
|
|
}
|
|
|
|
/** Move and change arrow direction when moving */
|
|
const arrowPosition = writable({ x: 0, y: 0 })
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
/** Move arrow and define direction on mousemove */
|
|
const handleArrowMove = (event: MouseEvent) => {
|
|
const { left, top, width } = carouselEl.getBoundingClientRect()
|
|
const offsetX = event.clientX - left
|
|
const offsetY = event.clientY - top
|
|
|
|
// Define direction
|
|
if (isFirstSlide) {
|
|
arrowDirection = 'next'
|
|
} else if (isLastSlide) {
|
|
arrowDirection = 'prev'
|
|
} else {
|
|
arrowDirection = offsetX < Math.round(width / 2) ? 'prev' : 'next'
|
|
}
|
|
|
|
// Move arrow
|
|
arrowPosition.set({
|
|
x: offsetX - 12,
|
|
y: offsetY - 56,
|
|
})
|
|
}
|
|
|
|
/** Go to prev or next slide depending on direction */
|
|
const handleArrowClick = () => {
|
|
if (!carousel.clickAllowed()) return
|
|
|
|
// Click only if carousel if being dragged
|
|
if (arrowDirection === 'prev') {
|
|
carousel.scrollPrev()
|
|
} else {
|
|
carousel.scrollNext()
|
|
}
|
|
}
|
|
|
|
|
|
onMount(() => {
|
|
// Init carousel
|
|
carousel = EmblaCarousel(carouselEl, {
|
|
loop: false
|
|
})
|
|
carousel.on('select', () => {
|
|
currentSlide = carousel.selectedScrollSnap()
|
|
})
|
|
|
|
|
|
// Destroy
|
|
return () => {
|
|
carousel.destroy()
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<div class="carousel {$$props.class ? $$props.class : ''}">
|
|
{#if slides.length}
|
|
<div class="carousel__viewport" bind:this={carouselEl}
|
|
on:mousemove={handleArrowMove}
|
|
on:click={handleArrowClick}
|
|
>
|
|
<div class="carousel__slides">
|
|
{#each slides as { id, alt }}
|
|
<Image
|
|
class="carousel__slide"
|
|
id={id}
|
|
sizeKey="product"
|
|
sizes={{
|
|
small: { width: 300 },
|
|
medium: { width: 550 },
|
|
large: { width: 800 },
|
|
}}
|
|
ratio={1.5}
|
|
alt={alt}
|
|
/>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
|
|
<ul class="carousel__dots">
|
|
{#each slides as _, index}
|
|
<li class:is-active={index === currentSlide}>
|
|
<button on:click={() => goToSlide(index)} aria-label="Go to slide #{index + 1}" />
|
|
</li>
|
|
{/each}
|
|
</ul>
|
|
|
|
<span class="carousel__arrow" bind:this={arrowEl}
|
|
style="--x: {$arrowPosition.x}px; --y: {$arrowPosition.y}px;"
|
|
class:is-flipped={arrowDirection === 'prev' && !isFirstSlide || isLastSlide}
|
|
>
|
|
<svg width="29" height="32" viewBox="0 0 29 32" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.82 28.275a2.182 2.182 0 0 0 3.086 3.086l13.818-13.818a2.182 2.182 0 0 0 0-3.086L13.906.64a2.182 2.182 0 1 0-3.085 3.086l10.093 10.093H2.182a2.182 2.182 0 1 0 0 4.364h18.732L10.821 28.275Z" />
|
|
</svg>
|
|
</span>
|
|
{/if}
|
|
</div> |