Skip to content

Instantly share code, notes, and snippets.

@endymion1818
Last active May 1, 2025 14:02
Show Gist options
  • Select an option

  • Save endymion1818/8119f7af21db1f62d9119581fc3a8d19 to your computer and use it in GitHub Desktop.

Select an option

Save endymion1818/8119f7af21db1f62d9119581fc3a8d19 to your computer and use it in GitHub Desktop.

Revisions

  1. endymion1818 revised this gist May 1, 2025. 2 changed files with 309 additions and 174 deletions.
    436 changes: 309 additions & 127 deletions carousel.js
    Original file line number Diff line number Diff line change
    @@ -1,89 +1,207 @@
    document.body.addEventListener('load', function () {
    function getHiddenElementHeight(element) {
    // Save the original display style
    const originalDisplay = element.style.display;

    // Temporarily show the element
    element.style.display = 'block';

    // Get the height
    const height = element.offsetHeight;

    // Revert to the original display style
    element.style.display = originalDisplay;

    return height;
    }

    function fadeIn(element) {
    let opacity = 0;
    element.style.opacity = opacity;
    element.style.display = 'block';

    const interval = setInterval(() => {
    opacity += 0.05;
    if (opacity >= 1) {
    opacity = 1;
    clearInterval(interval);
    }
    element.style.opacity = opacity;
    }, 50);
    }

    function fadeOut(element) {
    let opacity = 1;
    element.style.opacity = opacity;

    const interval = setInterval(() => {
    opacity -= 0.05;
    if (opacity <= 0) {
    opacity = 0;
    element.style.display = 'none';
    clearInterval(interval);
    }
    element.style.opacity = opacity;
    }, 50);
    }

    /**
    * observe changes in the height of carousel items
    * @param {HTMLElement} carouselElement
    * @returns {void}
    */
    function observeCarouselHeightChanges(carouselElement) {
    if (!carouselElement) {
    return;
    }
    let proposedCarouselHeight = 0;

    // Create a ResizeObserver instance
    const resizeObserver = new ResizeObserver(entries => {
    let maxHeight = 0;
    for (let entry of entries) {
    const liElement = /** @type {HTMLElement} */ (entry.target);

    const children = Array.from(liElement.children);
    children.forEach(child => {
    const height = child.offsetHeight;
    if (height > maxHeight) {
    maxHeight = height;
    }
    });
    }

    if (maxHeight > proposedCarouselHeight) {
    proposedCarouselHeight = maxHeight;
    requestAnimationFrame(() => {
    carouselElement.style.height = `${proposedCarouselHeight}px`;
    });
    }
    });

    // Select all <li> elements within the carousel
    const liElements = carouselElement?.querySelectorAll('li');

    // Observe each <li> element
    liElements.forEach(li => {
    resizeObserver.observe(li);
    });

    // Initial height setting
    carouselElement.style.height = `${proposedCarouselHeight}px`;
    }

    /**
    *
    * @param {HTMLElement} selector
    * @param {HTMLElement} carouselElement
    * @returns a ✨ new carousel ✨
    * @notes
    *
    * The carousel has some parameters which can be modified by adding classes to the carousel element
    *
    * 1. Autorotation speed. Add a class of `js-autoplayspeed-<number>` where `<number>` is the speed in milliseconds to adjust the default rotation speed
    * 3. Show indicators. Add a class of `js-show-indicators` to show indicators
    * 4. Show navigators. Add a class of `js-show-navigators` to show navigators
    * 5. Do not autorotate. Add a class of `js-no-autorotate` to prevent the carousel from rotating
    *
    */
    function createCarousel(
    selector,
    autoRotationInterval = 3_000,
    onPrevCallback = null,
    onNextCallback = null,
    onIndicatorClickCallback = null
    ) {
    carouselElement,
    ) {
    // nope
    if (typeof window === 'undefined') {
    return;
    }
    // also nope
    if(!selector) {
    if (!carouselElement) {
    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');

    /** @type { HTMLCollection | null} */
    const carouselItems = carouselElement.children;

    // Check if carousel has more than one item
    if (!carouselItems || carouselItems.length <= 1) {
    console.info("Carousel doesn't have more than one item, so it will not be activated.");
    return; // Exit if there's only one or no items
    }

    let autoRotationInterval = carouselElement.className.match(/js-no-autorotate/) ? null : 3_000;

    if (carouselElement.className.match(/js-autoplayspeed-\d+/g)) {
    const speedClasses = carouselElement.className.match(/js-autoplayspeed-\d+/g)
    if (!speedClasses) {
    return;
    }
    const speedClass = speedClasses[0].split("-")[2]
    autoRotationInterval = parseInt(speedClass);
    }

    /** @type { HTMLElement[] } */
    const carouselItemsArray = Array.from(carouselItems).filter(item => item instanceof HTMLElement);

    // 1. set carousel to be full width of the containing area
    carouselElement.style.width = '100%';


    // if not, use height of the tallest image
    carouselElement.style.display = 'block';

    observeCarouselHeightChanges(carouselElement);

    // finally, set the carousel to be visible
    carouselElement.classList.add('tw-block', 'tw-relative', 'md:tw-overflow-hidden');

    // define interval timer so we can clear it later
    let intervalInstance = null;

    /**
    * HELPER FUNCTIONS
    */
    /**
    * Gets the currently active slide
    * @returns {HTMLElement} item
    * @returns {HTMLElement | Element} item
    */
    function getActiveItem() {
    return selector.querySelector('[data-carousel-item="active"]');
    const activeItem = carouselElement?.querySelector('[data-carousel-item-current="true"]');

    if (!activeItem || !(activeItem instanceof HTMLElement)) {
    // @ts-ignore carouselItems is definitely defined by this point
    return carouselItemsArray[0];
    }
    return activeItem;
    }
    /**
    *
    * gets the position of the item in the array
    * @param {HTMLElement} item
    * @returns {HTMLElement} item
    * @param {HTMLElement | Element} item
    * @returns {number} itemIndex
    */
    function getPositionOfItem(item) {
    return carouselItemsArray.findIndex((carouselItem) => {
    return carouselItem === item;
    const position = carouselItemsArray.findIndex((carouselItem) => {
    return carouselItem === item && carouselItem.getAttribute('data-carousel-item');
    });
    return position;
    }

    /**
    *
    * Sets the carousel to the next slide
    * @param {HTMLElement} item
    * @returns {void}
    * @param {HTMLElement| Element } carouselItem
    * @returns null
    */
    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');
    }
    });
    function setItemAsActive(carouselItem) {
    carouselItem.setAttribute('data-carousel-item-current', 'true');
    fadeIn(carouselItem);
    }

    /**
    *
    * @param {HTMLElement} item
    * @param {HTMLElement| Element } carouselItem
    * @returns null
    */
    function setItemAsInactive(item) {
    item.setAttribute('data-carousel-item', '');
    item.classList.add('tw-opacity-0');
    item.classList.remove('tw-opacity-100');
    function setItemAsInactive(carouselItem) {
    carouselItem.setAttribute('data-carousel-item-current', 'false');
    fadeOut(carouselItem);
    }

    /**
    @@ -94,47 +212,77 @@ function createCarousel(
    * @returns {void}
    */
    function cycle() {
    if(autoRotationInterval <= 0) {
    return
    if (!autoRotationInterval || autoRotationInterval <= 0) {
    return;
    }
    intervalInstance = window.setInterval(() => {
    next();
    }, 3_000);

    intervalInstance = window.setTimeout(() => {
    next();
    }, autoRotationInterval);
    }

    function pause() {
    clearTimeout(intervalInstance);
    }
    /**
    * Clears the cycling interval
    * @returns {void}
    */
    function pause() {
    clearInterval(intervalInstance);
    clearInterval(intervalInstance);
    }
    /**
    * Slides to the next position
    *
    * @param {number} nextItem
    * @param {HTMLElement | Element} nextItem
    * @returns {void}
    */
    function slideTo(nextItem) {
    const activeItem = getActiveItem();
    if (!activeItem || !nextItem) {
    return;
    }

    setItemAsInactive(activeItem);
    setItemAsActive(nextItem);
    showActiveIndicator(nextItem);
    pause();
    cycle();
    }

    function showActiveIndicator(nextItem) {
    const nextItemIndex = getPositionOfItem(nextItem);

    const indicators = carouselElement.querySelectorAll('[data-carousel-indicator-for]');

    indicators && Array.from(indicators).map((indicator, index) => {
    if (index === nextItemIndex) {
    indicator.setAttribute('aria-pressed', 'true');
    } else {
    indicator.setAttribute('aria-pressed', 'false');
    }
    });
    }
    /**
    * 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];
    const activeItemPosition = getPositionOfItem(activeItem) ?? 0;

    if (!carouselItemsArray) {
    return;
    }
    if (activeItemPosition === carouselItemsArray.length - 1) {
    // if it is the last item, set first item as next
    return slideTo(carouselItemsArray[0]);
    }
    const nextItem = carouselItemsArray[activeItemPosition + 1];

    if (!nextItem.getAttribute('data-carousel-item')) {
    // if it's an indicator, set first item as next
    return slideTo(carouselItemsArray[0]);
    }
    slideTo(nextItem);
    }
    @@ -144,15 +292,22 @@ function createCarousel(
    * @returns {void}
    */
    function prev() {
    onPrevCallback && onPrevCallback();
    let prevItem = null;
    const activeItem = getActiveItem();
    if (!carouselItemsArray) return;
    let activeItem = getActiveItem();

    if (!activeItem) {
    console.info('no active item');
    activeItem = carouselItemsArray[0];
    }

    const activeItemPosition = getPositionOfItem(activeItem);

    if (activeItemPosition === 0) {
    prevItem = carouselItems[carouselItems.length -1];
    } else {
    prevItem = carouselItems[activeItemPosition - 1];
    const prevItem = carouselItemsArray[activeItemPosition - 1];

    const actualCarouselItems = carouselItemsArray.filter(item => item.getAttribute('data-carousel-item'));

    if (!prevItem && actualCarouselItems) {
    return slideTo(actualCarouselItems[actualCarouselItems.length - 1]);
    }
    slideTo(prevItem);
    }
    @@ -161,32 +316,64 @@ function createCarousel(
    * INIT FUNCTIONS
    */

    /**
    * Create the indicators for the carousel
    * @returns {void}
    */
    /**
    * 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}`);
    if (!carouselElement.classList.contains('js-show-indicators')) {
    return;
    }

    indicatorContainer.appendChild(indicator);
    const indicatorContainer = `
    <div class="indicator-container tw-absolute tw-bottom-4 tw-left-0 tw-right-0 tw-flex tw-justify-center tw-mb-4">
    ${carouselItemsArray.map((item, index) => `
    <button data-carousel-indicator-for="${index}" aria-pressed="${index === 0 ? "true" : "false"}" class="tw-w-4 tw-h-4 tw-mx-1 tw-rounded-full tw-border tw-border-primary-600 tw-transition-colors tw-duration-300 tw-ease-in-out tw-cursor-pointer hover:tw-ring-2 hover:tw-ring-primary-600 tw-bg-white aria-pressed:tw-bg-zinc-500" aria-label="Slide ${index + 1}">
    </button>
    `).join('')}
    </div>
    `;

    const activeItem = getActiveItem();
    const activeItemIndex = getPositionOfItem(activeItem);
    carouselElement.insertAdjacentHTML('beforeend', indicatorContainer);

    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);
    const instantiatedIndicators = carouselElement.querySelectorAll('[data-carousel-indicator-for]');

    const instantiatedIndicatorsArray = Array.from(instantiatedIndicators);

    instantiatedIndicatorsArray.map(indicator => {
    const clickedCarouselItem = indicator.getAttribute('data-carousel-indicator-for');
    indicator?.addEventListener('click', () => {
    clearTimeout(intervalInstance);
    const carouselItem = carouselItemsArray.find((carouselItem) => carouselItem.getAttribute('data-carousel-item') === clickedCarouselItem);
    carouselItem && slideTo(carouselItem);

    instantiatedIndicators.forEach((indicator) => {
    indicator.setAttribute('aria-pressed', 'false');
    });

    indicator.setAttribute('aria-pressed', 'true');
    });
    })
    }


    function createNavigators() {
    if (!carouselElement.classList.contains('js-show-navigators')) {
    return;
    }
    const navigatorPrev = `
    <button class="carousel-navigate navigate-prev tw-absolute tw-left-0 tw-bottom-0 tw-top-0 tw-text-white tw-text-2xl tw-shadow-sm tw-transition-all hover:tw-scale-110" type="button"><span class="tw-block tw-rounded tw-transition-opacity tw-bg-white/20 tw-border-white/50 hover:tw-bg-zinc-400">&larr;</span></button>
    `;
    const navigatorNext = `
    <button class="carousel-navigate navigate-next tw-absolute tw-right-0 tw-bottom-0 tw-top-0 tw-text-white tw-text-2xl tw-shadow-sm tw-transition-all hover:tw-scale-110" type="button"><span class="tw-block tw-rounded tw-transition-opacity tw-bg-white/20 tw-border-white/50 hover:tw-bg-zinc-400">&rarr;</span></button>
    `;
    carouselElement.insertAdjacentHTML('beforeend', navigatorPrev);
    carouselElement.insertAdjacentHTML('beforeend', navigatorNext);

    carouselElement.querySelectorAll('.carousel-navigate')?.forEach((navigator) => {
    navigator.addEventListener('click', () => {
    navigator.classList.contains('navigate-prev') ? prev() : next();
    });
    });
    }
    @@ -198,45 +385,40 @@ function createCarousel(
    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);
    if (!carouselItemsArray) {
    return;
    }

    // Set the active item first and make it visible
    if (activeItem && activeItem instanceof HTMLElement) {
    activeItem.classList.add('tw-absolute', 'tw-inset-0', 'tw-block');
    activeItem.setAttribute('data-carousel-item-current', 'true');
    activeItem.style.opacity = '1';
    } else {
    slideTo(0)
    carouselItemsArray[0].classList.add('tw-absolute', 'tw-inset-0', 'tw-block');
    carouselItemsArray[0].setAttribute('data-carousel-item-current', 'true');
    carouselItemsArray[0].style.opacity = '1';
    }
    /**
    * 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();

    // Apply opacity changes to other items
    carouselItemsArray.map((item, index) => {
    if (item !== activeItem) {
    item.classList.add('tw-absolute', 'tw-inset-0', 'tw-hidden');
    item.style.opacity = '0';
    }
    item.setAttribute('data-carousel-item', `${index}`);
    });
    }
    // initialise the carousel
    createIndicators();
    createNavigators();
    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);
    });
    }
    cycle();

    window.addEventListener('visibilitychange', () => {
    if (document.visibilityState === 'visible') {
    cycle();
    } else {
    pause();
    }
    });
    }
    47 changes: 0 additions & 47 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -1,47 +0,0 @@
    <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>
  2. endymion1818 revised this gist May 15, 2023. 1 changed file with 14 additions and 2 deletions.
    16 changes: 14 additions & 2 deletions carousel.js
    Original file line number Diff line number Diff line change
    @@ -4,7 +4,13 @@ document.body.addEventListener('load', function () {
    * @param {HTMLElement} selector
    * @returns a ✨ new carousel ✨
    */
    function createCarousel(selector) {
    function createCarousel(
    selector,
    autoRotationInterval = 3_000,
    onPrevCallback = null,
    onNextCallback = null,
    onIndicatorClickCallback = null
    ) {
    // nope
    if (typeof window === 'undefined') {
    return;
    @@ -88,6 +94,9 @@ function createCarousel(selector) {
    * @returns {void}
    */
    function cycle() {
    if(autoRotationInterval <= 0) {
    return
    }
    intervalInstance = window.setInterval(() => {
    next();
    }, 3_000);
    @@ -117,6 +126,7 @@ function createCarousel(selector) {
    * @returns {void}
    */
    function next() {
    onNextCallback && onNextCallback();
    let nextItem = null;
    const activeItem = getActiveItem();
    const activeItemPosition = getPositionOfItem(activeItem);
    @@ -134,11 +144,12 @@ function createCarousel(selector) {
    * @returns {void}
    */
    function prev() {
    onPrevCallback && onPrevCallback();
    let prevItem = null;
    const activeItem = getActiveItem();
    const activeItemPosition = getPositionOfItem(activeItem);

    if (activeItemPosition === 1) {
    if (activeItemPosition === 0) {
    prevItem = carouselItems[carouselItems.length -1];
    } else {
    prevItem = carouselItems[activeItemPosition - 1];
    @@ -173,6 +184,7 @@ function createCarousel(selector) {
    indicator.querySelector('svg').classList.remove('tw-text-white');
    }
    indicator.addEventListener('click', () => {
    onIndicatorClickCallback && onIndicatorClickCallback();
    slideTo(item);

    });
  3. endymion1818 revised this gist May 12, 2023. 2 changed files with 226 additions and 208 deletions.
    371 changes: 194 additions & 177 deletions carousel.js
    Original file line number Diff line number Diff line change
    @@ -1,31 +1,164 @@
    document.body.addEventListener('load', function () {
    /**
    * Initialise carousels
    /**
    *
    * @param {HTMLElement} selector
    * @returns a ✨ new carousel ✨
    */
    function createCarousel(selector) {
    // 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
    */
    const allCarousels = document.querySelectorAll('[data-carousel]')
    allCarousels.forEach((carouselElement) => {
    createCarousel(carouselElement);
    });
    /**
    * Let's Go!
    /**
    * 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() {
    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 createCarousel(selector) {
    if (typeof window === 'undefined') {
    return;
    function next() {
    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];
    }
    if(!selector) {
    return;
    slideTo(nextItem);
    }

    /**
    * Based on the currently active item it will go to the previous position
    * @returns {void}
    */
    function prev() {
    let prevItem = null;
    const activeItem = getActiveItem();
    const activeItemPosition = getPositionOfItem(activeItem);

    if (activeItemPosition === 1) {
    prevItem = carouselItems[carouselItems.length -1];
    } else {
    prevItem = carouselItems[activeItemPosition - 1];
    }
    slideTo(prevItem);
    }

    const carouselItems = selector.querySelectorAll('[data-carousel-item]');
    const carouselItemsArray = Array.from(carouselItems);
    /**
    * INIT FUNCTIONS
    */

    // init indicators
    const indicatorTemplate = selector.querySelector('.carousel-indicator');
    /**
    * Create the indicators for the carousel
    * @returns {void}
    */
    function createIndicators() {
    const indicatorContainer = selector.querySelector('#indicator-container');

    carouselItemsArray.map((item, index) => {
    const indicator = indicatorTemplate.cloneNode(true);
    const indicator = indicatorTemplate.content.firstElementChild.cloneNode(true);
    indicator.setAttribute('data-carousel-indicator', index);
    indicator.setAttribute('aria-label', `Slide ${index + 1}`);

    @@ -44,170 +177,54 @@ document.body.addEventListener('load', function () {

    });
    });
    }

    // define interval timer so we can clear it later
    let intervalInstance = null;
    /**
    * Gets the currently active slide
    * @returns {number} the index of the active item
    */
    function getActiveItem() {
    return selector.querySelector('[data-carousel-item="active"]');
    }
    /**
    *
    * @param {number} position
    * @returns {number}
    */
    function getItemByPosition(position) {
    return carouselItemsArray[position];
    }
    /**
    *
    * @param {HTMLElement} item
    * @returns {boolean} whether the item is active or not
    */
    function getPositionOfItem(item) {
    return carouselItemsArray.findIndex((carouselItem) => {
    return carouselItem === item;
    });
    }
    /**
    *
    * @param {HTMLElement} item
    * @returns null
    */
    function setItemAsActive(item) {
    item.setAttribute('data-carousel-item', 'active');
    item.classList.remove('tw-hidden');

    // update the indicators if available
    const currentItemIndex = getPositionOfItem(item);
    const indicators = selector.querySelectorAll('[data-carousel-indicator]');
    /**
    * Function to initialise carousel
    * @returns {void}
    */
    function init() {
    const activeItem = getActiveItem();

    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 {Item} item
    * @returns null
    */
    function setItemAsInactive(item) {
    item.setAttribute('data-carousel-item', '');
    item.classList.add('tw-hidden');
    }
    /**
    * Set an interval to cycle through the carousel items
    */
    function cycle() {
    intervalInstance = window.setInterval(() => {
    next();
    }, 3_000);
    }
    /**
    * Clears the cycling interval
    */
    function pause() {
    clearInterval(intervalInstance);
    }
    const items = Array.from(carouselItems)
    items.map(item => {
    item.classList.add(
    'tw-absolute',
    'tw-inset-0',
    'tw-transition-transform',
    'tw-transform'
    )
    });
    /**
    * Slides to the next position
    *
    * @param {number} nextItem
    * @returns null
    * if no active item is set then first position is default
    */
    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
    */
    function next() {
    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);
    if(activeItem) {
    slideTo(activeItem);
    } else {
    slideTo(0)
    }

    /**
    * Based on the currently active item it will go to the previous position
    * Add event listeners to the buttons if they exist
    */
    function prev() {
    let prevItem = null;
    const activeItem = getActiveItem();
    const activeItemPosition = getPositionOfItem(activeItem);
    console.log({activeItemPosition});
    if (activeItemPosition === 1) {
    prevItem = carouselItems[carouselItems.length -1];
    } else {
    prevItem = carouselItems[activeItemPosition - 1];
    }
    slideTo(prevItem);
    }
    /**
    * Initialise carousel
    */
    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)
    }
    // prev & next buttons
    const prevButton = selector.querySelector('[data-carousel-prev]');
    const nextButton = selector.querySelector('[data-carousel-next]');
    nextButton.addEventListener('click', () => {
    next();
    });
    prevButton.addEventListener('click', () => {
    prev();
    })
    }
    /**
    * Initialise carousels
    */
    init();
    /**
    * Allow public access to some lifecycle events
    */
    return {
    init,
    cycle,
    next,
    prev
    }
    };
    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);
    });
    }
    63 changes: 32 additions & 31 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -1,46 +1,47 @@
    <div id="animation-carousel" class="tw-relative tw-w-full tw-h-96 md:tw-h-[36rem]" data-carousel="static">
    <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-96 md:tw-h-[36rem]">
    <!-- Carousel indicators -->
    <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">
    <button type="button" class="carousel-indicator 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 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>
    <!-- Item 1 -->
    <div class="tw-hidden tw-duration-200 tw-ease-linear tw-h-96 md:tw-h-[36rem]" data-carousel-item>
    <img src="https://placehold.co/600x400/orange/white" 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-96 md:tw-h-[36rem]" alt="...">
    <!-- 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>
    <!-- Item 2 -->
    <div class="tw-hidden tw-duration-200 tw-ease-linear tw-h-96 md:tw-h-[36rem]" data-carousel-item>
    <img src="https://placehold.co/600x400/red/white" 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-96 md:tw-h-[36rem]" alt="...">
    <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>
    <!-- Item 3 -->
    <div class="tw-hidden tw-duration-200 tw-ease-linear tw-h-96 md:tw-h-[36rem]" data-carousel-item="active">
    <img src="https://placehold.co/600x400/blue/teal" 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-96 md:tw-h-[36rem]" alt="...">
    </div>
    <!-- Item 4 -->
    <div class="tw-hidden tw-duration-200 tw-ease-linear tw-h-96 md:tw-h-[36rem]" data-carousel-item>
    <img src="https://placehold.co/600x400/green/purple" 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-96 md:tw-h-[36rem]" alt="...">
    </div>
    <!-- Item 5 -->
    <div class="tw-hidden tw-duration-200 tw-ease-linear tw-h-96 md:tw-h-[36rem]" data-carousel-item>
    <img src="https://placehold.co/600x400/purple/red" 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-96 md:tw-h-[36rem]" alt="...">
    <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 -->
    <!-- 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">
    <svg aria-hidden="true" class="tw-w-5 tw-h-5 tw-text-white sm:tw-w-6 sm:tw-h-6 tw-dark:text-gray-800" 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-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">
    <svg aria-hidden="true" class="tw-w-5 tw-h-5 tw-text-white sm:tw-w-6 sm:tw-h-6 dark:tw-text-gray-800" 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-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>
    </div>
  4. endymion1818 revised this gist May 12, 2023. 1 changed file with 0 additions and 2 deletions.
    2 changes: 0 additions & 2 deletions carousel.js
    Original file line number Diff line number Diff line change
    @@ -41,8 +41,6 @@ document.body.addEventListener('load', function () {
    }
    indicator.addEventListener('click', () => {
    slideTo(item);
    indicator.setAttribute('aria-current', 'true');
    indicator.classList.add('tw-text-primary-800');

    });
    });
  5. endymion1818 revised this gist May 12, 2023. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion carousel.js
    Original file line number Diff line number Diff line change
    @@ -40,7 +40,7 @@ document.body.addEventListener('load', function () {
    indicator.querySelector('svg').classList.remove('tw-text-white');
    }
    indicator.addEventListener('click', () => {
    slideTo(index);
    slideTo(item);
    indicator.setAttribute('aria-current', 'true');
    indicator.classList.add('tw-text-primary-800');

  6. endymion1818 revised this gist May 12, 2023. 1 changed file with 6 additions and 0 deletions.
    6 changes: 6 additions & 0 deletions carousel.js
    Original file line number Diff line number Diff line change
    @@ -198,7 +198,13 @@ document.body.addEventListener('load', function () {
    prev();
    })
    }
    /**
    * Initialise carousels
    */
    init();
    /**
    * Allow public access to some lifecycle events
    */
    return {
    init,
    cycle,
  7. endymion1818 created this gist May 12, 2023.
    209 changes: 209 additions & 0 deletions carousel.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,209 @@
    document.body.addEventListener('load', function () {
    /**
    * Initialise carousels
    */
    const allCarousels = document.querySelectorAll('[data-carousel]')
    allCarousels.forEach((carouselElement) => {
    createCarousel(carouselElement);
    });
    /**
    * Let's Go!
    */
    function createCarousel(selector) {
    if (typeof window === 'undefined') {
    return;
    }
    if(!selector) {
    return;
    }

    const carouselItems = selector.querySelectorAll('[data-carousel-item]');
    const carouselItemsArray = Array.from(carouselItems);

    // init indicators
    const indicatorTemplate = selector.querySelector('.carousel-indicator');
    const indicatorContainer = selector.querySelector('#indicator-container');

    carouselItemsArray.map((item, index) => {
    const indicator = indicatorTemplate.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', () => {
    slideTo(index);
    indicator.setAttribute('aria-current', 'true');
    indicator.classList.add('tw-text-primary-800');

    });
    });

    // define interval timer so we can clear it later
    let intervalInstance = null;
    /**
    * Gets the currently active slide
    * @returns {number} the index of the active item
    */
    function getActiveItem() {
    return selector.querySelector('[data-carousel-item="active"]');
    }
    /**
    *
    * @param {number} position
    * @returns {number}
    */
    function getItemByPosition(position) {
    return carouselItemsArray[position];
    }
    /**
    *
    * @param {HTMLElement} item
    * @returns {boolean} whether the item is active or not
    */
    function getPositionOfItem(item) {
    return carouselItemsArray.findIndex((carouselItem) => {
    return carouselItem === item;
    });
    }
    /**
    *
    * @param {HTMLElement} item
    * @returns null
    */
    function setItemAsActive(item) {
    item.setAttribute('data-carousel-item', 'active');
    item.classList.remove('tw-hidden');

    // update the indicators if available
    const currentItemIndex = getPositionOfItem(item);
    const indicators = selector.querySelectorAll('[data-carousel-indicator]');

    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 {Item} item
    * @returns null
    */
    function setItemAsInactive(item) {
    item.setAttribute('data-carousel-item', '');
    item.classList.add('tw-hidden');
    }
    /**
    * Set an interval to cycle through the carousel items
    */
    function cycle() {
    intervalInstance = window.setInterval(() => {
    next();
    }, 3_000);
    }
    /**
    * Clears the cycling interval
    */
    function pause() {
    clearInterval(intervalInstance);
    }
    /**
    * Slides to the next position
    *
    * @param {number} nextItem
    * @returns null
    */
    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
    */
    function next() {
    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
    */
    function prev() {
    let prevItem = null;
    const activeItem = getActiveItem();
    const activeItemPosition = getPositionOfItem(activeItem);
    console.log({activeItemPosition});
    if (activeItemPosition === 1) {
    prevItem = carouselItems[carouselItems.length -1];
    } else {
    prevItem = carouselItems[activeItemPosition - 1];
    }
    slideTo(prevItem);
    }
    /**
    * Initialise carousel
    */
    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)
    }
    // prev & next buttons
    const prevButton = selector.querySelector('[data-carousel-prev]');
    const nextButton = selector.querySelector('[data-carousel-next]');
    nextButton.addEventListener('click', () => {
    next();
    });
    prevButton.addEventListener('click', () => {
    prev();
    })
    }
    init();
    return {
    init,
    cycle,
    next,
    prev
    }
    };
    }
    46 changes: 46 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,46 @@
    <div id="animation-carousel" class="tw-relative tw-w-full tw-h-96 md:tw-h-[36rem]" data-carousel="static">
    <!-- Carousel wrapper -->
    <div class="tw-relative tw-overflow-hidden tw-rounded-lg tw-h-96 md:tw-h-[36rem]">
    <!-- Carousel indicators -->
    <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">
    <button type="button" class="carousel-indicator 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>
    </div>
    <!-- Item 1 -->
    <div class="tw-hidden tw-duration-200 tw-ease-linear tw-h-96 md:tw-h-[36rem]" data-carousel-item>
    <img src="https://placehold.co/600x400/orange/white" 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-96 md:tw-h-[36rem]" alt="...">
    </div>
    <!-- Item 2 -->
    <div class="tw-hidden tw-duration-200 tw-ease-linear tw-h-96 md:tw-h-[36rem]" data-carousel-item>
    <img src="https://placehold.co/600x400/red/white" 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-96 md:tw-h-[36rem]" alt="...">
    </div>
    <!-- Item 3 -->
    <div class="tw-hidden tw-duration-200 tw-ease-linear tw-h-96 md:tw-h-[36rem]" data-carousel-item="active">
    <img src="https://placehold.co/600x400/blue/teal" 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-96 md:tw-h-[36rem]" alt="...">
    </div>
    <!-- Item 4 -->
    <div class="tw-hidden tw-duration-200 tw-ease-linear tw-h-96 md:tw-h-[36rem]" data-carousel-item>
    <img src="https://placehold.co/600x400/green/purple" 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-96 md:tw-h-[36rem]" alt="...">
    </div>
    <!-- Item 5 -->
    <div class="tw-hidden tw-duration-200 tw-ease-linear tw-h-96 md:tw-h-[36rem]" data-carousel-item>
    <img src="https://placehold.co/600x400/purple/red" 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-96 md:tw-h-[36rem]" alt="...">
    </div>
    </div>
    <!-- Slider controls -->
    <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">
    <svg aria-hidden="true" class="tw-w-5 tw-h-5 tw-text-white sm:tw-w-6 sm:tw-h-6 tw-dark:text-gray-800" 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">
    <svg aria-hidden="true" class="tw-w-5 tw-h-5 tw-text-white sm:tw-w-6 sm:tw-h-6 dark:tw-text-gray-800" 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>