@ozenselin/image-carousel
Version:
A lightweight image carousel with smooth transitions, multi-control navigation, dynamic scaling, and shadow effects
2 lines (1 loc) • 8.57 kB
JavaScript
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).Carousel=t()}(this,function(){"use strict";!function(e,t){void 0===t&&(t={});var n=t.insertAt;if(e&&"undefined"!=typeof document){var s=document.head||document.getElementsByTagName("head")[0],r=document.createElement("style");r.type="text/css","top"===n&&s.firstChild?s.insertBefore(r,s.firstChild):s.appendChild(r),r.styleSheet?r.styleSheet.cssText=e:r.appendChild(document.createTextNode(e))}}('.carousel * {\n padding: 0;\n margin: 0;\n box-sizing: border-box;\n}\n\n.carousel {\n --clr-lighter: rgb(226, 216, 216);\n --clr-light: rgb(171, 164, 164);\n --clr-dark: rgb(99, 99, 99);\n --clr-darker: rgb(43, 43, 43);\n color: var(--clr-font-dark);\n font-family:\n system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,\n "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";\n font-size: 1rem;\n padding: 0;\n margin: 0;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n overflow-x: hidden;\n}\n\n.carousel__panel {\n margin-top: 6em;\n margin-bottom: 6em;\n position: relative;\n width: min-content;\n max-width: 200vw;\n top: 50%;\n left: 50%;\n transform: translateX(-7em);\n}\n\n.carousel__list {\n --clr-shadow-light: rgba(255, 255, 255, 0.5);\n --clr-shadow-dark: rgba(0, 0, 0, 0.07);\n --clr-background-dark: rgb(171, 164, 164);\n position: relative;\n top: 0;\n left: 0;\n display: grid;\n grid-auto-flow: column;\n justify-items: center;\n list-style: none;\n transition: left 0.5s ease-in-out;\n}\n\n.carousel__item {\n color: var(--clr-dark);\n --transition-period: 0.5s;\n font-size: 2rem;\n width: 7em;\n height: 7em;\n background-color: var(--clr-light);\n display: grid;\n align-content: center;\n justify-content: center;\n transition:\n box-shadow var(--transition-period) ease,\n transform var(--transition-period) ease;\n}\n\n.carousel__item--selected {\n z-index: 1;\n transform-origin: center;\n box-shadow:\n 0 1px 2px var(--clr-shadow-light),\n 0 2px 4px var(--clr-shadow-light),\n 0 4px 8px var(--clr-shadow-light),\n 0 8px 16px var(--clr-shadow-light),\n 0 16px 32px var(--clr-shadow-light),\n 0 32px 64px var(--clr-shadow-light);\n}\n\n.carousel__indicators {\n display: grid;\n grid-auto-flow: column;\n gap: 0.25em;\n justify-content: center;\n margin-bottom: 3em;\n}\n\n.carousel__dot {\n display: inline-block;\n border: 2px solid var(--clr-dark);\n padding: 3.5px;\n border-radius: 50%;\n transition: background-color 0.3s;\n}\n\n.carousel__dot--selected {\n background-color: var(--clr-dark);\n}\n\n.carousel__actions {\n display: flex;\n flex-direction: row;\n justify-content: center;\n gap: 1em;\n}\n\n.carousel__button {\n background-color: var(--clr-darker);\n border: unset;\n color: var(--clr-lighter);\n border-radius: 1em;\n font: inherit;\n padding: 0em 1em;\n}\n');const e={scale:{HIGHEST:1.4,STEP:.2},classes:{carousel:"carousel",panel:"carousel__panel",list:"carousel__list",item:"carousel__item",dotsContainer:"carousel__indicators",dot:"carousel__dot",buttonsContainer:"carousel__actions",button:"carousel__button",nextButton:"carousel__button--next",previousButton:"carousel__button--previous",selectedItem:"carousel__item--selected",selectedDot:"carousel__dot--selected"},images:[{src:"./images/image01.png",alt:"",width:"100%"},{src:"./images/image02.jpg",alt:"",width:"100%"},{src:"./images/image03.jpg",alt:"",width:"100%"},{src:"./images/image04.png",alt:"",width:"100%"},{src:"./images/image05.png",alt:"",width:"100%"},{src:"./images/image06.png",alt:"",width:"100%"},{src:"./images/image07.png",alt:"",width:"100%"}]},t=e=>{if(!e||!e.maxItems)throw new Error("Config with maxItems is required");let t=0,n=0;const s=e.maxItems,r=e=>{return r=e,!!(Number.isInteger(r)&&r>=0&&r<s)&&(n=t,t=e,!0);var r};return{getCurrentIndex:()=>t,getPreviousIndex:()=>n,getMaxItems:()=>s,setCurrentIndex:r,goToNext:()=>r(t+1),goToPrevious:()=>r(t-1),canGoNext:()=>t<s-1,canGoPrevious:()=>t>0,reset:()=>{n=t,t=0},destroy:()=>{}}},n=(e,t)=>{if(!(e instanceof HTMLElement))throw new Error("rootElement must be a valid HTMLElement");if(!t||!t.classes||!t.images)throw new Error("config must contain classes and images");let n=null,s=!1;const r=()=>{n={list:e.querySelector(`.${t.classes.list}`),nextButton:e.querySelector(`.${t.classes.nextButton}`),previousButton:e.querySelector(`.${t.classes.previousButton}`),dotsContainer:e.querySelector(`.${t.classes.dotsContainer}`),items:Array.from(e.querySelectorAll(`.${t.classes.item}`)),dots:Array.from(e.querySelectorAll(`.${t.classes.dot}`))}},o=e=>{const n=document.createElement("li");n.classList.add(t.classes.item);const s=(e=>{const t=document.createElement("img");return Object.entries(e).forEach(([e,n])=>{null!=n&&t.setAttribute(e,n)}),t})(e);return n.appendChild(s),n},i=()=>{e.innerHTML=`\n <div class="${t.classes.carousel}">\n <div class="${t.classes.panel}">\n <ul class="${t.classes.list}"></ul>\n </div>\n <div class="${t.classes.dotsContainer}"></div>\n <div class="${t.classes.buttonsContainer}">\n <button class="${t.classes.button} ${t.classes.previousButton}" type="button">\n Previous\n </button>\n <button class="${t.classes.button} ${t.classes.nextButton}" type="button">\n Next\n </button>\n </div>\n </div>\n `;const n=e.querySelector(`.${t.classes.list}`),s=e.querySelector(`.${t.classes.dotsContainer}`);t.images.forEach(e=>{const r=o(e),i=(()=>{const e=document.createElement("span");return e.classList.add(t.classes.dot),e})();n.appendChild(r),s.appendChild(i)})},a=(e,s)=>{if(!n)return;const r=t.classes.selectedItem,o=t.classes.selectedDot;n.items[e]&&n.items[e].classList.remove(r),n.items[s]&&n.items[s].classList.add(r),n.dots[e]&&n.dots[e].classList.remove(o),n.dots[s]&&n.dots[s].classList.add(o)},l=e=>{n?.items&&n.items.forEach((n,s)=>{const r=((e,n)=>{const s=Math.abs(n-e);return t.scale.HIGHEST-t.scale.STEP*s})(s,e);n.style.transform=`scale(${r})`})};return{initialize:()=>{s||(i(),r(),l(0),a(0,0),s=!0)},destroy:()=>{n=null,s=!1,e.innerHTML=""},moveItems:e=>{if(!n?.items?.[0])return;const t=-e*parseFloat(getComputedStyle(n.items[0]).width);n.list.style.left=`${t}px`},resizeItems:l,updateClasses:a,getElements:()=>(n||r(),n),get isInitialized(){return s}}},s=(e,t,n,s)=>{if(!(e instanceof HTMLElement))throw new Error("rootElement must be a valid HTMLElement");if(!t||!n||!s)throw new Error("state, dom, and config are required");const r=[];let o=!1;const i=()=>{n.moveItems(t.getCurrentIndex()),n.resizeItems(t.getCurrentIndex()),n.updateClasses(t.getPreviousIndex(),t.getCurrentIndex())},a=()=>{t.canGoNext()&&(t.goToNext(),i())},l=()=>{t.canGoPrevious()&&(t.goToPrevious(),i())},c=e=>{const s=(r=e.target,n.getElements().dots.findIndex(e=>e===r));var r;-1!==s&&s!==t.getCurrentIndex()&&(t.setCurrentIndex(s),i())},d=e=>{switch(e.key){case"ArrowLeft":e.preventDefault(),l();break;case"ArrowRight":e.preventDefault(),a()}},u=(e,t,n)=>{e&&(e.addEventListener(t,n),r.push({element:e,eventType:t,handler:n}))};return{initialize:()=>{o||((()=>{const e=n.getElements();u(e.nextButton,"click",a),u(e.previousButton,"click",l),u(e.dotsContainer,"click",c),u(document,"keydown",d)})(),o=!0)},destroy:()=>{r.forEach(({element:e,eventType:t,handler:n})=>{e.removeEventListener(t,n)}),r.length=0,o=!1},handleNext:a,handlePrevious:l,handleIndexChange:i,get isInitialized(){return o}}};return({rootElement:r,config:o={}})=>{if(!(r instanceof HTMLElement))throw new Error("rootElement must be a valid HTMLElement");const i=((t={})=>{const n={...e,...t,classes:{...e.classes,...t.classes},scale:{...e.scale,...t.scale}};return n.maxItems=n.images.length,n})(o);let a=null,l=null,c=null,d=!1;return{initialize:()=>{d||(a=t(i),l=n(r,i),c=s(r,a,l,i),l.initialize(),c.initialize(),d=!0)},destroy:()=>{c?.destroy&&c.destroy(),l?.destroy&&l.destroy(),a?.destroy&&a.destroy(),a=null,l=null,c=null,d=!1},goToNext:()=>{if(!d)throw new Error("Must call initialize() first");c.handleNext()},goToPrevious:()=>{if(!d)throw new Error("Must call initialize() first");c.handlePrevious()},goToIndex:e=>{if(!d)throw new Error("Must call initialize() first");a.setCurrentIndex(e),c.handleIndexChange()},getCurrentIndex:()=>{if(!d)throw new Error("Must call initialize() first");return a.getCurrentIndex()},get isInitialized(){return d}}}});