diff --git a/blocks/carousel/carousel.css b/blocks/carousel/carousel.css index 9525d88..49cae15 100644 --- a/blocks/carousel/carousel.css +++ b/blocks/carousel/carousel.css @@ -3,6 +3,7 @@ --slide-width: 50%; overflow: hidden; + position: relative; } .carousel-slide-wrapper { @@ -21,8 +22,33 @@ display: flex; } +.carousel-buttons button { + width: 34px; + height: 34px; + padding: 0; + background: transparent; + border: unset; + position: absolute; + top: 50%; +} + +.carousel-buttons button:first-of-type { + transform: translateY(-50%) rotate(180deg); +} + +.carousel-buttons button:last-of-type { + transform: translateY(-50%); + right: 0; +} + +.carousel-buttons button img { + width: 24px; + height: 24px; + display: flex; +} + .carousel .transition-effect { - transition: transform 500ms ease; + transition: transform 250ms ease; } .carousel-nav { @@ -44,7 +70,8 @@ padding: 0; } -.carousel-nav-button-active { +.carousel-nav-button-active, +.carousel-nav-button-active:focus { background-color: rgb(29 29 27 / 60%); border-color: transparent; } diff --git a/blocks/carousel/carousel.js b/blocks/carousel/carousel.js index d502c57..ff6fe48 100644 --- a/blocks/carousel/carousel.js +++ b/blocks/carousel/carousel.js @@ -1,5 +1,17 @@ import { getTextLabel } from '../../scripts/scripts.js'; +const createCarouselStateManager = (onUpdate) => { + let activeSlideIndex = 0; + + return { + getActiveSlideIndex: () => activeSlideIndex, + setActiveSlideIndex: (value) => { + activeSlideIndex = value; + onUpdate(activeSlideIndex); + }, + }; +}; + const getCarouselPadding = (itemIndex) => `calc(-1 * (${itemIndex - 0.5} * var(--slide-width) + var(--slide-gap) * ${itemIndex - 1}))`; const recalcSlidePositions = (slides, activeSlideIndex, direction) => { @@ -38,6 +50,7 @@ const recalcSlidePositions = (slides, activeSlideIndex, direction) => { carouselEl.addEventListener('transitioncancel', () => { resolveTransition(); }, { once: true }); + carouselEl.addEventListener('transitionend', () => { carouselEl.classList.remove('transition-effect'); resolveTransition(); @@ -79,13 +92,9 @@ const supportSwiping = (swipeEl, onSwipe) => { export default async function decorate(block) { const slides = [...block.querySelectorAll(':scope > div > div ')]; - slides.forEach((slide, index) => { + slides.forEach((slide) => { slide.classList.add('carousle-slide'); slide.parentElement.replaceWith(slide); - - const d = document.createElement('span'); - d.innerHTML = index; - slide.append(d); }); const slideWrapper = document.createElement('div'); @@ -94,8 +103,16 @@ export default async function decorate(block) { const buttons = document.createRange().createContextualFragment(` <div class="carousel-buttons"> - <button><</button> - <button>></button> + <button aria-label="${getTextLabel('Prev slide')}"> + <span class="icon icon-arrow"> + <img data-icon-name="arrow" src="/icons/arrow.svg" alt="" loading="lazy"> + </span> + </button> + <button aria-label="${getTextLabel('Next slide')}"> + <span class="icon icon-arrow"> + <img data-icon-name="arrow" src="/icons/arrow.svg" alt="" loading="lazy"> + </span> + </button> </div> `); @@ -113,60 +130,61 @@ export default async function decorate(block) { block.append(buttons.children[0]); block.append(carouselNav.children[0]); - let activeSlideIndex = 0; - let prevActiveSlideIndex = 0; - - const getActiveSlideIndex = () => activeSlideIndex; + const onIndexUpdate = (activeIndex) => { + const navButtons = [...block.querySelectorAll('.carousel-nav .carousel-nav-button')]; - const setActiveSlideIndex = (value) => { - prevActiveSlideIndex = activeSlideIndex; - activeSlideIndex = value; + navButtons.forEach((el) => el.classList.remove('carousel-nav-button-active')); + navButtons[activeIndex].classList.add('carousel-nav-button-active'); }; - recalcSlidePositions(slides, activeSlideIndex, null); + const { getActiveSlideIndex, setActiveSlideIndex } = createCarouselStateManager(onIndexUpdate); + + onIndexUpdate(getActiveSlideIndex()); + recalcSlidePositions(slides, getActiveSlideIndex(), null); [...block.querySelectorAll('.carousel-buttons button')].forEach((button, btnIndex) => { button.addEventListener('click', () => { - prevActiveSlideIndex = activeSlideIndex; - + const activeIndex = getActiveSlideIndex(); if (btnIndex === 0) { - activeSlideIndex = ((activeSlideIndex - 1 + slides.length) % slides.length); + setActiveSlideIndex((activeIndex - 1 + slides.length) % slides.length); } else { - activeSlideIndex = (activeSlideIndex + 1) % slides.length; + setActiveSlideIndex((activeIndex + 1) % slides.length); } - recalcSlidePositions(slides, activeSlideIndex, btnIndex === 0 ? 'prev' : 'next'); + recalcSlidePositions(slides, getActiveSlideIndex(), btnIndex === 0 ? 'prev' : 'next'); }); }); [...block.querySelectorAll('.carousel-nav button')].forEach((button, btnIndex) => { - button.addEventListener('click', () => { - prevActiveSlideIndex = activeSlideIndex; - activeSlideIndex = btnIndex; + button.addEventListener('click', async (event) => { + const { target } = event; + const activeIndex = getActiveSlideIndex(); + const times = [...Array(Math.abs(activeIndex - btnIndex)).keys()]; + const direction = btnIndex < activeIndex ? 'prev' : 'next'; - const times = Array.from(Array(Math.abs(prevActiveSlideIndex - activeSlideIndex)).keys()); - const direction = activeSlideIndex < prevActiveSlideIndex ? 'prev' : 'next'; - let currentIndex = prevActiveSlideIndex; + [...target.closest('.carousel-nav').querySelectorAll('carousel-nav-button')].forEach((el) => el.classList.remove('carousel-nav-button-active')); + target.classList.add('carousel-nav-button-active'); - times.forEach(async () => { + await times.reduce(async (previousPromise) => { + await previousPromise; if (direction === 'next') { - currentIndex += 1; + setActiveSlideIndex(getActiveSlideIndex() + 1); } else { - currentIndex -= 1; + setActiveSlideIndex(getActiveSlideIndex() - 1); } - await recalcSlidePositions(slides, currentIndex, direction); - }); + + await recalcSlidePositions(slides, getActiveSlideIndex(), direction); + }, Promise.resolve()); }); }); const triggerSlideChange = (direction) => { - prevActiveSlideIndex = activeSlideIndex; if (direction === 'next') { - activeSlideIndex = (activeSlideIndex + 1) % slides.length; + setActiveSlideIndex((getActiveSlideIndex() + 1) % slides.length); } else if (direction === 'prev') { - activeSlideIndex = ((activeSlideIndex - 1 + slides.length) % slides.length); + setActiveSlideIndex((getActiveSlideIndex() - 1 + slides.length) % slides.length); } - recalcSlidePositions(slides, activeSlideIndex, direction); + recalcSlidePositions(slides, getActiveSlideIndex(), direction); }; supportSwiping(block, triggerSlideChange); diff --git a/icons/arrow.svg b/icons/arrow.svg new file mode 100644 index 0000000..559f3a1 --- /dev/null +++ b/icons/arrow.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="14" height="24" viewBox="0 0 14 24"> + <polyline fill="none" stroke="#000" stroke-width="1.4" points="1.225,23 12.775,12 1.225,1"></polyline> +</svg> \ No newline at end of file