Files
housesof/src/components/organisms/Carousel.svelte
Félix Péault cdabe6935b 🔥 Huge style refactoring by using SvelteKit built-in style tag
It's been tricky but got there finally! Hello `:global`
- Avoid using one global CSS file containing everything
- Import the component SCSS file in a script tag from the component file to allow style scoping and including it only when used
2022-06-22 23:26:00 +02:00

129 lines
3.7 KiB
Svelte

<style lang="scss">
@import "../../style/organisms/carousel";
</style>
<script lang="ts">
import { onMount } from 'svelte'
import { writable } from 'svelte/store'
import EmblaCarousel, { type 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
// Define direction
if (isFirstSlide) {
arrowDirection = 'next'
} else if (isLastSlide) {
arrowDirection = 'prev'
}
// 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 ?? ''}">
{#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">
<use xlink:href="#arrow" />
</svg>
</span>
{/if}
</div>