218 lines
5.0 KiB
Svelte
218 lines
5.0 KiB
Svelte
<script>
|
|
// TODO: rename image carousel to just carousel
|
|
// TODO: seems CarouselChild component can be removed
|
|
// TODO: subscribe on mount and unsubscribe on destroy to
|
|
// $store.currentItemIndex to avoid multiple subscriptions
|
|
import { onMount } from 'svelte'
|
|
import { store } from '../store'
|
|
import {
|
|
getPageIndex,
|
|
getPagesCount,
|
|
getSlidesToShowTail,
|
|
getSlideSize,
|
|
getIsNotCompletePage
|
|
} from '../utils/size'
|
|
import Dots from '../Dots/Dots.svelte'
|
|
import Arrow from '../Arrow/Arrow.svelte'
|
|
|
|
/**
|
|
* Enable Next/Prev arrows
|
|
*/
|
|
export let arrows = true
|
|
|
|
/**
|
|
* Infinite looping
|
|
*/
|
|
export let infinite = true
|
|
|
|
/**
|
|
* Number of slides to show at a time
|
|
*/
|
|
export let slidesToShow = 1
|
|
|
|
/**
|
|
* Page to start on
|
|
*/
|
|
export let initialPage = 1
|
|
|
|
/**
|
|
* Transition speed (ms)
|
|
*/
|
|
export let speed = 500
|
|
|
|
/**
|
|
* Enables auto play of slides
|
|
*/
|
|
export let autoplay = false
|
|
|
|
/**
|
|
* Auto play change interval
|
|
*/
|
|
export let autoplaySpeed = 3000
|
|
|
|
/**
|
|
* Auto play change direction ('next', 'prev')
|
|
*/
|
|
export let autoplayDirection = 'next'
|
|
|
|
/**
|
|
* Current page indicator dots
|
|
*/
|
|
export let dots = true
|
|
|
|
let pagesCount = 0
|
|
let contentContainerWidth = 0
|
|
let offset
|
|
let contentContainerElement
|
|
let innerContentContainerElement
|
|
|
|
function applySlideSizes() {
|
|
const children = innerContentContainerElement.children
|
|
contentContainerWidth = contentContainerElement.clientWidth
|
|
|
|
const slidesCount = children.length
|
|
pagesCount = getPagesCount({ slidesCount, slidesToShow })
|
|
const slidesToShowTail = getSlidesToShowTail({ pagesCount, slidesToShow, slidesCount })
|
|
|
|
for (let slideIndex=0; slideIndex<children.length; slideIndex++) {
|
|
const pageIndex = getPageIndex({ slideIndex, slidesToShow })
|
|
const isNotCompletePage = getIsNotCompletePage({ pageIndex, pagesCount })
|
|
const slideSizePx = getSlideSize({ isNotCompletePage, contentContainerWidth, slidesToShow, slidesToShowTail })
|
|
children[slideIndex].style.minWidth = `${slideSizePx}px`
|
|
children[slideIndex].style.maxWidth = `${slideSizePx}px`
|
|
}
|
|
showPage(initialPage)
|
|
}
|
|
|
|
function applyAutoplay() {
|
|
const autoplayDirectionFnDescription = {
|
|
'next': showNextPage,
|
|
'prev': showPrevPage
|
|
}
|
|
let interval
|
|
if (autoplay) {
|
|
interval = setInterval(() => {
|
|
autoplayDirectionFnDescription[autoplayDirection]()
|
|
}, autoplaySpeed)
|
|
}
|
|
return {
|
|
teardownAutoplay: () => {
|
|
interval && clearInterval(interval)
|
|
}
|
|
}
|
|
}
|
|
|
|
onMount(() => {
|
|
store.reset() // to init after hot reload
|
|
applySlideSizes()
|
|
|
|
const { teardownAutoplay } = applyAutoplay()
|
|
|
|
window.addEventListener('resize', applySlideSizes)
|
|
return () => {
|
|
window.removeEventListener('resize', applySlideSizes)
|
|
teardownAutoplay()
|
|
}
|
|
})
|
|
|
|
function handlePageChange(event) {
|
|
showPage(event.detail)
|
|
}
|
|
|
|
function applyOffset() {
|
|
offset = -$store.currentItemIndex * contentContainerWidth
|
|
}
|
|
|
|
function showPage(pageIndex) {
|
|
store.moveToPage({ pageIndex, pagesCount })
|
|
applyOffset()
|
|
}
|
|
function showPrevPage() {
|
|
store.prev({ infinite, pagesCount })
|
|
applyOffset()
|
|
}
|
|
function showNextPage() {
|
|
store.next({ infinite, pagesCount })
|
|
applyOffset()
|
|
}
|
|
</script>
|
|
|
|
<div class="main-container">
|
|
<div class="carousel-container">
|
|
{#if arrows}
|
|
<slot name="prev" {showPrevPage}>
|
|
<div class="side-container">
|
|
<Arrow direction="prev" on:click={showPrevPage} />
|
|
</div>
|
|
</slot>
|
|
{/if}
|
|
<div
|
|
class="content-container"
|
|
bind:this={contentContainerElement}
|
|
>
|
|
<div
|
|
style="
|
|
transform: translateX({offset}px);
|
|
transition-duration: {speed}ms;
|
|
"
|
|
bind:this={innerContentContainerElement}
|
|
>
|
|
<slot></slot>
|
|
</div>
|
|
</div>
|
|
{#if arrows}
|
|
<slot name="next" {showNextPage}>
|
|
<div class="side-container">
|
|
<Arrow direction="next" on:click={showNextPage} />
|
|
</div>
|
|
</slot>
|
|
{/if}
|
|
</div>
|
|
{#if dots}
|
|
<slot
|
|
name="dots"
|
|
currentPage={$store.currentItemIndex}
|
|
{pagesCount}
|
|
{showPage}
|
|
>
|
|
<Dots
|
|
{pagesCount}
|
|
currentPageIndex={$store.currentItemIndex}
|
|
on:pageChange={handlePageChange}
|
|
></Dots>
|
|
</slot>
|
|
{/if}
|
|
</div>
|
|
|
|
<style>
|
|
.main-container {
|
|
display: flex;
|
|
width: 100%;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
.carousel-container {
|
|
display: flex;
|
|
width: 100%;
|
|
}
|
|
.content-container {
|
|
flex: 1;
|
|
display: flex;
|
|
overflow: hidden;
|
|
box-sizing: border-box;
|
|
}
|
|
.content-container > div {
|
|
width: 100%;
|
|
display: flex; /* to put child elements in one row */
|
|
transition-timing-function: ease-in-out;
|
|
transition-property: transform;
|
|
}
|
|
.side-container {
|
|
height: 100%;
|
|
padding: 5px;
|
|
box-sizing: border-box;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
</style> |