UNPKG

@ozenselin/image-carousel

Version:

A lightweight image carousel with smooth transitions, multi-control navigation, dynamic scaling, and shadow effects

2 lines (1 loc) 12 kB
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(e="undefined"!=typeof globalThis?globalThis:e||self).Carousel=n()}(this,function(){"use strict";!function(e,n){void 0===n&&(n={});var t=n.insertAt;if("undefined"!=typeof document){var o=document.head||document.getElementsByTagName("head")[0],r=document.createElement("style");r.type="text/css","top"===t&&o.firstChild?o.insertBefore(r,o.firstChild):o.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 --item-width: 14rem;\n --item-height: 14rem;\n --shadow-color: rgba(255, 255, 255, 0.5);\n\n --panel-margin-top: 6em;\n --panel-margin-bottom: 6em;\n \n --dot-padding: 3.5px;\n --dot-border-width: 2px;\n --dot-gap: 0.25em;\n --dot-bg-color: transparent;\n --dot-border-color: rgb(127, 127, 127);\n --dot-selected-bg-color: rgb(127, 127, 127);\n --dot-selected-border-color: rgb(127, 127, 127);\n --dot-transition-period: 0.3s;\n \n --icon-width: 1.75rem;\n --icon-fill-color: rgb(70, 70, 70);\n --nav-overlay-padding: 2rem;\n --nav-overlay-button-bg-color:rgb(245, 245, 245);\n --nav-overlay-button-bg-opacity: 0.9;\n --nav-overlay-button-bg-scale: 2.2;\n --nav-overlay-button-bg-blur: 10px;\n --nav-overlay-icon-fill-color: rgb(70, 70, 70);\n --nav-overlay-icon-width: 1.75rem;\n \n --nav-bottom-gap: 0.25em;\n --nav-bottom-icon-fill-color: rgb(127, 127, 127);\n --nav-bottom-icon-width: 1.25rem;\n\n --transition-period: 0.5s;\n --transition-timing-function: ease-in-out;\n\n font-family:\n system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,\n "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";\n display: flex;\n flex-direction: column;\n overflow-x: hidden;\n position: relative;\n}\n\n.carousel__panel {\n margin-top: var(--panel-margin-top);\n margin-bottom: var(--panel-margin-bottom);\n position: relative;\n width: min-content;\n max-width: 200vw;\n top: 50%;\n left: 50%;\n /* Paneli tam ortalamak için eleman genişliğinin yarısı kadar sola kaydırıyoruz */\n transform: translateX(calc(var(--item-width) / -2));\n}\n\n.carousel__list {\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 var(--transition-period) var(--transition-timing-function);\n}\n\n.carousel__item {\n color: rgb(99, 99, 99);\n font-size: 2rem;\n width: var(--item-width);\n height: var(--item-height);\n background-color: rgb(171, 164, 164);\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 1px var(--shadow-color),\n 0 2px 4px 2px var(--shadow-color),\n 0 4px 8px 4px var(--shadow-color),\n 0 8px 16px 8px var(--shadow-color),\n 0 16px 32px 8px var(--shadow-color);\n}\n\n.carousel__indicators {\n display: grid;\n grid-auto-flow: column;\n gap: var(--dot-gap);\n}\n\n.carousel__dot {\n display: inline-block;\n border: var(--dot-border-width) solid var(--dot-border-color);\n background-color: var(--dot-bg-color);\n padding: var(--dot-padding);\n border-radius: 50%;\n transition: \n transform var(--dot-transition-period) ease-out;\n}\n\n.carousel__dot--selected {\n border-color: var(--dot-selected-border-color);\n background-color: var(--dot-selected-bg-color);\n}\n\n.carousel__navigation--bottom {\n display: flex;\n flex-direction: row;\n justify-content: center;\n align-items: center;\n gap: var(--nav-bottom-gap);\n}\n\n.carousel__button {\n border: unset;\n background-color: transparent;\n font: inherit;\n display: inline-grid;\n place-items: center;\n}\n\n.carousel__icon {\n width: var(--icon-width);\n}\n\n.carousel__navigation--bottom .carousel__icon {\n fill: var(--nav-bottom-icon-fill-color);\n width: var(--nav-bottom-icon-width);\n}\n\n.carousel__navigation--overlay .carousel__icon {\n fill: var(--nav-overlay-icon-fill-color);\n width: var(--nav-overlay-icon-width);\n}\n\n.carousel__navigation--overlay {\n position: absolute;\n width: 100%;\n transform: translateY(-50%);\n margin-top: calc(var(--item-height)/2 + var(--panel-margin-top));\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n padding: var(--nav-overlay-padding);\n z-index: 1;\n}\n\n.carousel__navigation--overlay .carousel__button {\n position: relative;\n}\n\n.carousel__navigation--overlay .carousel__button::before,\n.carousel__navigation--overlay .carousel__button::after {\n content: \'\';\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: calc(var(--icon-width) * var(--nav-overlay-button-bg-scale));\n height: calc(var(--icon-width) * var(--nav-overlay-button-bg-scale));\n border-radius: 50%;\n background-color: var(--nav-overlay-button-bg-color);\n z-index: -1;\n opacity: var(--nav-overlay-button-bg-opacity);\n}\n\n.carousel__navigation--overlay .carousel__button::before {\n filter: blur(var(--nav-overlay-button-bg-blur));\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%"}]},n=e=>{if(!e||!e.maxItems)throw new Error("Config with maxItems is required");let n=0,t=0;const o=e.maxItems,r=e=>{return r=e,!!(Number.isInteger(r)&&r>=0&&r<o)&&(t=n,n=e,!0);var r};return{getCurrentIndex:()=>n,getPreviousIndex:()=>t,getMaxItems:()=>o,setCurrentIndex:r,goToNext:()=>r(n+1),goToPrevious:()=>r(n-1),canGoNext:()=>n<o-1,canGoPrevious:()=>n>0,reset:()=>{t=n,n=0},destroy:()=>{}}},t=(e,n)=>{if(!(e instanceof HTMLElement))throw new Error("rootElement must be a valid HTMLElement");if(!n||!n.classes||!n.images)throw new Error("config must contain classes and images");let t=null,o=!1;const r=()=>{t={list:e.querySelector(`.${n.classes.list}`),nextButtons:e.querySelectorAll(`.${n.classes.nextButton}`),previousButtons:e.querySelectorAll(`.${n.classes.previousButton}`),dotsContainer:e.querySelector(`.${n.classes.dotsContainer}`),items:Array.from(e.querySelectorAll(`.${n.classes.item}`)),dots:Array.from(e.querySelectorAll(`.${n.classes.dot}`))}},s=e=>{const t=document.createElement("li");t.classList.add(n.classes.item);const o=(e=>{const n=document.createElement("img");return Object.entries(e).forEach(([e,t])=>{null!=t&&n.setAttribute(e,t)}),n})(e);return t.appendChild(o),t},a=()=>{e.innerHTML=`\n <div class="${n.classes.carousel}">\n <div class="carousel__navigation carousel__navigation--overlay">\n <button class="${n.classes.button} ${n.classes.previousButton}" type="button">\n <svg class="carousel__icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px"><path d="M400-80 0-480l400-400 71 71-329 329 329 329-71 71Z"/></svg>\n </button>\n <button class="${n.classes.button} ${n.classes.nextButton}" type="button">\n <svg class="carousel__icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px"><path d="m321-80-71-71 329-329-329-329 71-71 400 400L321-80Z"/></svg>\n </button>\n </div>\n <div class="${n.classes.panel}">\n <ul class="${n.classes.list}"></ul>\n </div>\n <div class="carousel__navigation carousel__navigation--bottom">\n <button class="${n.classes.button} ${n.classes.previousButton}" type="button">\n <svg class="carousel__icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px"><path d="m313-440 224 224-57 56-320-320 320-320 57 56-224 224h487v80H313Z"/></svg>\n </button>\n <div class="${n.classes.dotsContainer}"></div>\n <button class="${n.classes.button} ${n.classes.nextButton}" type="button">\n <svg class="carousel__icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px"><path d="M647-440H160v-80h487L423-744l57-56 320 320-320 320-57-56 224-224Z"/></svg>\n </button>\n </div>\n </div>\n `;const t=e.querySelector(`.${n.classes.list}`),o=e.querySelector(`.${n.classes.dotsContainer}`);n.images.forEach(e=>{const r=s(e),a=(()=>{const e=document.createElement("span");return e.classList.add(n.classes.dot),e})();t.appendChild(r),o.appendChild(a)})},i=(e,o)=>{if(!t)return;const r=n.classes.selectedItem,s=n.classes.selectedDot;t.items[e]&&t.items[e].classList.remove(r),t.items[o]&&t.items[o].classList.add(r),t.dots[e]&&t.dots[e].classList.remove(s),t.dots[o]&&t.dots[o].classList.add(s)},l=e=>{t?.items&&t.items.forEach((t,o)=>{const r=((e,t)=>{const o=Math.abs(t-e);return n.scale.HIGHEST-n.scale.STEP*o})(o,e);t.style.transform=`scale(${r})`})};return{initialize:()=>{o||(a(),r(),l(0),i(0,0),o=!0)},destroy:()=>{t=null,o=!1,e.innerHTML=""},moveItems:e=>{if(!t?.items?.[0])return;const n=-e*parseFloat(getComputedStyle(t.items[0]).width);t.list.style.left=`${n}px`},resizeItems:l,updateClasses:i,getElements:()=>(t||r(),t),get isInitialized(){return o}}},o=(e,n,t,o)=>{if(!(e instanceof HTMLElement))throw new Error("rootElement must be a valid HTMLElement");if(!n||!t||!o)throw new Error("state, dom, and config are required");const r=[];let s=!1;const a=()=>{t.moveItems(n.getCurrentIndex()),t.resizeItems(n.getCurrentIndex()),t.updateClasses(n.getPreviousIndex(),n.getCurrentIndex())},i=()=>{n.canGoNext()&&(n.goToNext(),a())},l=()=>{n.canGoPrevious()&&(n.goToPrevious(),a())},c=e=>{const o=(r=e.target,t.getElements().dots.findIndex(e=>e===r));var r;-1!==o&&o!==n.getCurrentIndex()&&(n.setCurrentIndex(o),a())},d=e=>{switch(e.key){case"ArrowLeft":e.preventDefault(),l();break;case"ArrowRight":e.preventDefault(),i()}},u=(e,n,t)=>{e&&(e.addEventListener(n,t),r.push({element:e,eventType:n,handler:t}))};return{initialize:()=>{s||((()=>{const e=t.getElements();u(e.nextButton,"click",i),e.previousButtons.forEach(e=>{u(e,"click",l)}),e.nextButtons.forEach(e=>{u(e,"click",i)}),u(e.dotsContainer,"click",c),u(document,"keydown",d)})(),s=!0)},destroy:()=>{r.forEach(({element:e,eventType:n,handler:t})=>{e.removeEventListener(n,t)}),r.length=0,s=!1},handleNext:i,handlePrevious:l,handleIndexChange:a,get isInitialized(){return s}}};return({rootElement:r,config:s={}})=>{if(!(r instanceof HTMLElement))throw new Error("rootElement must be a valid HTMLElement");const a=((n={})=>{const t={...e,...n,classes:{...e.classes,...n.classes},scale:{...e.scale,...n.scale}};return t.maxItems=t.images.length,t})(s);let i=null,l=null,c=null,d=!1;return{initialize:()=>{d||(i=n(a),l=t(r,a),c=o(r,i,l,a),l.initialize(),c.initialize(),d=!0)},destroy:()=>{c?.destroy&&c.destroy(),l?.destroy&&l.destroy(),i?.destroy&&i.destroy(),i=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");i.setCurrentIndex(e),c.handleIndexChange()},getCurrentIndex:()=>{if(!d)throw new Error("Must call initialize() first");return i.getCurrentIndex()},get isInitialized(){return d}}}});