Last active
May 15, 2023 08:42
-
-
Save endymion1818/8119f7af21db1f62d9119581fc3a8d19 to your computer and use it in GitHub Desktop.
Carousel
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
document.body.addEventListener('load', function () { | |
/** | |
* | |
* @param {HTMLElement} selector | |
* @returns a ✨ new carousel ✨ | |
*/ | |
function createCarousel( | |
selector, | |
autoRotationInterval = 3_000, | |
onPrevCallback = null, | |
onNextCallback = null, | |
onIndicatorClickCallback = null | |
) { | |
// nope | |
if (typeof window === 'undefined') { | |
return; | |
} | |
// also nope | |
if(!selector) { | |
return; | |
} | |
// get some things we're going to need later | |
const carouselItems = selector.querySelectorAll('[data-carousel-item]'); | |
const carouselItemsArray = Array.from(carouselItems); | |
const indicatorTemplate = selector.querySelector('#carousel-indicator'); | |
// define interval timer so we can clear it later | |
let intervalInstance = null; | |
/** | |
* HELPER FUNCTIONS | |
*/ | |
/** | |
* Gets the currently active slide | |
* @returns {HTMLElement} item | |
*/ | |
function getActiveItem() { | |
return selector.querySelector('[data-carousel-item="active"]'); | |
} | |
/** | |
* | |
* gets the position of the item in the array | |
* @param {HTMLElement} item | |
* @returns {HTMLElement} item | |
*/ | |
function getPositionOfItem(item) { | |
return carouselItemsArray.findIndex((carouselItem) => { | |
return carouselItem === item; | |
}); | |
} | |
/** | |
* | |
* Sets the carousel to the next slide | |
* @param {HTMLElement} item | |
* @returns {void} | |
*/ | |
function setItemAsActive(item) { | |
item.setAttribute('data-carousel-item', 'active'); | |
item.classList.remove('tw-opacity-0'); | |
item.classList.add('tw-opacity-100'); | |
// update the indicators if available | |
const currentItemIndex = getPositionOfItem(item); | |
const indicators = selector.querySelectorAll('[data-carousel-indicator]'); | |
indicators.length > 0 && Array.from(indicators).map((indicator, index) => { | |
if (index === currentItemIndex) { | |
indicator.setAttribute('aria-current', 'true'); | |
indicator.querySelector('svg').classList.add('tw-text-primary-600'); | |
indicator.querySelector('svg').classList.remove('tw-text-white'); | |
} else { | |
indicator.querySelector('svg').classList.add('tw-text-white'); | |
indicator.setAttribute('aria-current', 'false'); | |
indicator.querySelector('svg').classList.remove('tw-text-primary-600'); | |
indicator.querySelector('svg').classList.add('tw-text-white'); | |
} | |
}); | |
} | |
/** | |
* | |
* @param {HTMLElement} item | |
* @returns null | |
*/ | |
function setItemAsInactive(item) { | |
item.setAttribute('data-carousel-item', ''); | |
item.classList.add('tw-opacity-0'); | |
item.classList.remove('tw-opacity-100'); | |
} | |
/** | |
* ACTIONS | |
*/ | |
/** | |
* Set an interval to cycle through the carousel items | |
* @returns {void} | |
*/ | |
function cycle() { | |
if(autoRotationInterval <= 0) { | |
return | |
} | |
intervalInstance = window.setInterval(() => { | |
next(); | |
}, 3_000); | |
} | |
/** | |
* Clears the cycling interval | |
* @returns {void} | |
*/ | |
function pause() { | |
clearInterval(intervalInstance); | |
} | |
/** | |
* Slides to the next position | |
* | |
* @param {number} nextItem | |
* @returns {void} | |
*/ | |
function slideTo(nextItem) { | |
const activeItem = getActiveItem(); | |
setItemAsInactive(activeItem); | |
setItemAsActive(nextItem); | |
pause(); | |
cycle(); | |
} | |
/** | |
* Based on the currently active item it will go to the next position | |
* @returns {void} | |
*/ | |
function next() { | |
onNextCallback && onNextCallback(); | |
let nextItem = null; | |
const activeItem = getActiveItem(); | |
const activeItemPosition = getPositionOfItem(activeItem); | |
if (activeItemPosition === carouselItems.length - 1) { | |
// if it is the last item, set first item as next | |
nextItem = carouselItems[0]; | |
} else { | |
nextItem = carouselItems[activeItemPosition + 1]; | |
} | |
slideTo(nextItem); | |
} | |
/** | |
* Based on the currently active item it will go to the previous position | |
* @returns {void} | |
*/ | |
function prev() { | |
onPrevCallback && onPrevCallback(); | |
let prevItem = null; | |
const activeItem = getActiveItem(); | |
const activeItemPosition = getPositionOfItem(activeItem); | |
if (activeItemPosition === 0) { | |
prevItem = carouselItems[carouselItems.length -1]; | |
} else { | |
prevItem = carouselItems[activeItemPosition - 1]; | |
} | |
slideTo(prevItem); | |
} | |
/** | |
* INIT FUNCTIONS | |
*/ | |
/** | |
* Create the indicators for the carousel | |
* @returns {void} | |
*/ | |
function createIndicators() { | |
const indicatorContainer = selector.querySelector('#indicator-container'); | |
carouselItemsArray.map((item, index) => { | |
const indicator = indicatorTemplate.content.firstElementChild.cloneNode(true); | |
indicator.setAttribute('data-carousel-indicator', index); | |
indicator.setAttribute('aria-label', `Slide ${index + 1}`); | |
indicatorContainer.appendChild(indicator); | |
const activeItem = getActiveItem(); | |
const activeItemIndex = getPositionOfItem(activeItem); | |
if(index === activeItemIndex) { | |
indicator.setAttribute('aria-current', 'true'); | |
indicator.querySelector('svg').classList.add('tw-text-primary-600'); | |
indicator.querySelector('svg').classList.remove('tw-text-white'); | |
} | |
indicator.addEventListener('click', () => { | |
onIndicatorClickCallback && onIndicatorClickCallback(); | |
slideTo(item); | |
}); | |
}); | |
} | |
/** | |
* Function to initialise carousel | |
* @returns {void} | |
*/ | |
function init() { | |
const activeItem = getActiveItem(); | |
const items = Array.from(carouselItems) | |
items.map(item => { | |
item.classList.add( | |
'tw-absolute', | |
'tw-inset-0', | |
'tw-transition-transform', | |
'tw-transform' | |
) | |
}); | |
/** | |
* if no active item is set then first position is default | |
*/ | |
if(activeItem) { | |
slideTo(activeItem); | |
} else { | |
slideTo(0) | |
} | |
/** | |
* Add event listeners to the buttons if they exist | |
*/ | |
const nextButton = selector.querySelector('[data-carousel-next]'); | |
nextButton && nextButton.addEventListener('click', () => { | |
next(); | |
}); | |
const prevButton = selector.querySelector('[data-carousel-prev]'); | |
prevButton && prevButton.addEventListener('click', () => { | |
prev(); | |
}); | |
} | |
// initialise the carousel | |
init(); | |
// if we have an indicator template, create the indicators | |
indicatorTemplate && createIndicators(); | |
}; | |
/** | |
* Initialise carousels | |
*/ | |
const allCarousels = document.querySelectorAll('[data-carousel]') | |
allCarousels.forEach((carouselElement) => { | |
createCarousel(carouselElement); | |
}); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div id="animation-carousel" class="tw-relative tw-w-full tw-h-36 md:tw-h-96 tw-my-8" data-carousel> | |
<!-- Carousel wrapper --> | |
<div class="tw-relative tw-overflow-hidden tw-rounded-lg tw-h-36 md:tw-h-96"> | |
<!-- Carousel indicators, if desired --> | |
<div id="indicator-container" class="tw-absolute tw-z-30 tw-flex tw-space-x-3 tw--translate-x-1/2 tw-bottom-5 tw-left-1/2"> | |
<template id="carousel-indicator"> | |
<button type="button" class="tw-w-3 tw-h-3 tw-rounded-full tw-border tw-border-white" aria-current="false" aria-label="Slide 1" data-carousel-slide-to="0"> | |
<svg viewBox="0 0 100 100" class="tw-text-white" xmlns="http://www.w3.org/2000/svg"> | |
<circle cx="50" cy="50" r="50" fill="currentColor" stroke="white" stroke-width="3"/> | |
</svg> | |
</button> | |
</template> | |
</div> | |
<!-- Items --> | |
<div class="tw-opacity-0 tw-transition tw-duration-150 tw-ease-in-out tw-h-36 md:tw-h-96" data-carousel-item="active"> | |
<img src="https://placehold.co/600x400/orange/blue" alt="..." class="tw-absolute tw-block tw-w-full tw--translate-x-1/2 tw--translate-y-1/2 tw-top-1/2 tw-left-1/2 tw-h-36 md:tw-h-96"> | |
<div class="tw-absolute tw-inset-0 tw-flex"> | |
<blockquote class="tw-m-auto tw-z-40 tw-font-bold tw-text-4xl tw-max-w-xl tw-text-center tw-text-zinc-50"> | |
The | |
<span class="tw-text-primary-400">more I learn</span>, the more I realise how much I don't know | |
<p class="tw-mt-6 tw-text-sm tw-font-normal tw-italic"> | |
Albert Einstein | |
</p> | |
</blockquote> | |
</div> | |
</div> | |
<div class="tw-opacity-0 tw-transition tw-duration-150 tw-ease-in-out tw-h-36 md:tw-h-96" data-carousel-item> | |
<img src="https://placehold.co/600x400/orange/white" alt="..." class="tw-absolute tw-block tw-w-full tw--translate-x-1/2 tw--translate-y-1/2 tw-top-1/2 tw-left-1/2 tw-h-36 md:tw-h-96"> | |
</div> | |
<div class="tw-opacity-0 tw-transition tw-duration-150 tw-ease-in-out tw-h-36 md:tw-h-96" data-carousel-item> | |
<img src="https://placehold.co/600x400/red/white" alt="..." class="tw-absolute tw-block tw-w-full tw--translate-x-1/2 tw--translate-y-1/2 tw-top-1/2 tw-left-1/2 tw-h-36 md:tw-h-96"> | |
</div> | |
</div> | |
<!-- Slider controls, if desired --> | |
<button type="button" class="tw-absolute tw-top-0 tw-left-0 tw-z-30 tw-flex tw-items-center tw-justify-center tw-h-full tw-px-4 tw-cursor-pointer tw-group focus:tw-outline-none" data-carousel-prev> | |
<span class="tw-inline-flex tw-items-center tw-justify-center tw-w-8 tw-h-8 tw-rounded-full sm:tw-w-10 sm:tw-h-10 tw-bg-white/30 tw-dark:bg-gray-800/30 group-hover:tw-bg-white/50 dark:group-hover:tw-bg-gray-800/60 group-focus:tw-ring-4 group-focus:tw-ring-white dark:group-focus:tw-ring-gray-800/70 group-focus:tw-outline-none tw-transition-all tw-ease-in-out"> | |
<svg aria-hidden="true" class="tw-w-5 tw-h-5 tw-text-zinc-400 hover:tw-text-zinc-800 sm:tw-w-6 sm:tw-h-6 tw-dark:text-gray-800 tw-transition-all tw-ease-in-out" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg> | |
<span class="tw-sr-only">Previous</span> | |
</span> | |
</button> | |
<button type="button" class="tw-absolute tw-top-0 tw-right-0 tw-z-30 tw-flex tw-items-center tw-justify-center tw-h-full tw-px-4 tw-cursor-pointer tw-group focus:tw-outline-none" data-carousel-next> | |
<span class="tw-inline-flex tw-items-center tw-justify-center tw-w-8 tw-h-8 tw-rounded-full sm:tw-w-10 sm:tw-h-10 bg-white/30 dark:tw-bg-gray-800/30 group-hover:tw-bg-white/50 dark:tw-group-hover:tw-bg-gray-800/60 group-focus:tw-ring-4 group-focus:tw-ring-white dark:tw-group-focus:tw-ring-gray-800/70 group-focus:tw-outline-none tw-transition-all tw-ease-in-out"> | |
<svg aria-hidden="true" class="tw-w-5 tw-h-5 tw-text-zinc-400 hover:tw-text-zinc-800 sm:tw-w-6 sm:tw-h-6 dark:tw-text-gray-800 tw-transition-all tw-ease-in-out" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg> | |
<span class="tw-sr-only">Next</span> | |
</span> | |
</button> | |
</div> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment