Testing https://github.com/swieder227/POJS-carousel
A Pen by Andreas Borgen on CodePen.
| <div id="main"> | |
| <!-- Carousel --> | |
| <div class="carousel-main carousel-1" data-slide-index="2"> | |
| <!-- Slides --> | |
| <div class="carousel-item" data-slide-index="0"> | |
| <img src="https://www.fillmurray.com/810/400" /> | |
| </div> | |
| <div class="carousel-item" data-slide-index="1"> | |
| <img src="https://www.fillmurray.com/620/800" /> | |
| </div> | |
| <div class="carousel-item" data-slide-index="2"> | |
| <img src="https://www.fillmurray.com/830/600" /> | |
| </div> | |
| <div class="carousel-item" data-slide-index="3"> | |
| <img src="https://www.fillmurray.com/840/600" /> | |
| </div> | |
| <!-- Controls --> | |
| <button class="carousel-btn" data-dir="_L"></button> | |
| <button class="carousel-btn" data-dir="_R"></button> | |
| <!-- Dots --> | |
| <div class="carousel-dots"> | |
| <div class="carousel-dot" data-slide-index="0"></div> | |
| <div class="carousel-dot" data-slide-index="1"></div> | |
| <div class="carousel-dot" data-slide-index="2"></div> | |
| <div class="carousel-dot" data-slide-index="3"></div> | |
| </div> | |
| </div> | |
| </div> | |
| (function() { | |
| "use strict"; | |
| var CSS_ANIM_NEXT = "next", | |
| CSS_ANIM_PREV = "prev", | |
| CSS_ANIM_NODOUBLETAP = "preventDoubleTap", | |
| CSS_ACTIVE = "active", | |
| ATTR_GOLEFT = "_L", | |
| ATTR_GORIGHT = "_R"; | |
| var Carousel = function(obj) { | |
| this.obj = document.querySelector(obj); | |
| this.slide_index; | |
| this.slide_length; | |
| this.slide_current_obj; | |
| this._init(); | |
| }; | |
| Carousel.prototype = { | |
| constructor: Carousel, | |
| _init : function() { | |
| // start at [data-slide-index] | |
| this.slide_index = this.obj.getAttribute("data-slide-index") ? parseInt(this.obj.getAttribute("data-slide-index")) : 0; | |
| // starting obj | |
| this._updateCurrentSlideObj(); | |
| this.slide_current_obj.classList.add(CSS_ACTIVE); | |
| // store length | |
| this.slide_length = this.obj.querySelectorAll(".carousel-item").length; | |
| // animation end event to use | |
| //this.animationEnd = this.whichAnimationEvent(); | |
| // go-go-gadget-eventHandlers! | |
| this._setupHandlers(); | |
| // add swipe detection | |
| this._swipeSetup(); | |
| }, | |
| _setupHandlers : function() { | |
| var self = this, | |
| carousel = this.obj; | |
| function slide(e) { | |
| if(e.changedTouches) { | |
| //Don't turn this "touch" into a "click": | |
| e.preventDefault(); | |
| //Don't interfere with swiping, which is handled in _swipeSetup() | |
| // e.stopPropagation(); | |
| var touch = e.changedTouches[0], | |
| elm = document.elementFromPoint(touch.clientX, touch.clientY); | |
| //If the touch has moved off the button, this is a *swipe*, and we're done here: | |
| if((e.currentTarget !== elm) && (!e.currentTarget.contains(elm))) { | |
| return; | |
| } | |
| } | |
| var dir = this.getAttribute("data-dir"); | |
| self._handleSwipe(dir === ATTR_GORIGHT); | |
| } | |
| var buttons = this.obj.querySelectorAll(".carousel-btn"); | |
| for(var i = 0; i < buttons.length; i++) { | |
| buttons[i].addEventListener("click", slide); | |
| //Also add touch events, to respond faster to the click on touch screens: | |
| buttons[i].addEventListener("touchend", slide); | |
| } | |
| var dots = this.obj.querySelectorAll(".carousel-dot"); | |
| for(var i = 0; i < dots.length; i++) { | |
| dots[i].addEventListener("click", function() { self._slideJump(this.getAttribute("data-slide-index")) }); | |
| } | |
| function endSlide(e) { | |
| //console.log(e.target, e.currentTarget); | |
| var slide = e.target; | |
| slide.classList.remove(CSS_ANIM_NEXT); | |
| slide.classList.remove(CSS_ANIM_PREV); | |
| // remove top level class | |
| carousel.classList.remove(CSS_ANIM_NODOUBLETAP); | |
| } | |
| carousel.addEventListener("webkitAnimationEnd", endSlide); | |
| carousel.addEventListener( "animationend", endSlide); | |
| }, | |
| _updateCurrentSlideDot : function () { | |
| // update dots | |
| var dots = this.obj.querySelectorAll(".carousel-dot"); | |
| for(var i = 0; i < dots.length; i++){ | |
| if(i === this.slide_index) { | |
| dots[this.slide_index].classList.add(CSS_ACTIVE); | |
| } else { | |
| dots[i].classList.remove(CSS_ACTIVE); | |
| } | |
| } | |
| }, | |
| _updateCurrentSlideObj : function() { | |
| // get current slide from DOM | |
| this.slide_current_obj = this.obj.querySelector(".carousel-item[data-slide-index='"+this.slide_index+"']"); | |
| // keep dots concurrent with slides | |
| this._updateCurrentSlideDot(); | |
| }, | |
| /* | |
| Sliding Controls | |
| */ | |
| // Main movement/animation fn. Applies next/prev & active classes to correct .carousel-item's. | |
| _slide : function(dir) { | |
| // add preventDoubleTap to prevent double press | |
| var carousel = this.obj; | |
| carousel.classList.add(CSS_ANIM_NODOUBLETAP); | |
| // set diretion-based vars. these classes apply left/right css animations | |
| var class_for_current = (dir === ATTR_GORIGHT) ? CSS_ANIM_PREV : CSS_ANIM_NEXT; | |
| var class_for_target = (dir === ATTR_GORIGHT) ? CSS_ANIM_NEXT : CSS_ANIM_PREV; | |
| // anim out current | |
| var current_slide = this.slide_current_obj; | |
| current_slide.classList.add(class_for_current); | |
| current_slide.classList.remove(CSS_ACTIVE); | |
| // anim in next | |
| var target_slide = this.obj.querySelector(".carousel-item[data-slide-index='"+this.slide_index+"']"); | |
| target_slide.classList.add(class_for_target); | |
| target_slide.classList.add(CSS_ACTIVE); | |
| // update current slide | |
| this._updateCurrentSlideObj(); | |
| }, | |
| // slide Carousel one item to _L | |
| _slideLeft : function() { | |
| // if index == 0, set to length, else index-- | |
| if(this.slide_index === 0) { | |
| this.slide_index = this.slide_length - 1; | |
| } else { | |
| this.slide_index -= 1; | |
| } | |
| this._slide(ATTR_GOLEFT); | |
| }, | |
| // slide Carousel one item to _R | |
| _slideRight : function() { | |
| // if index == max, set to 0, else index++ | |
| if(this.slide_index === this.slide_length - 1){ | |
| this.slide_index = 0; | |
| } else { | |
| this.slide_index += 1; | |
| } | |
| this._slide(ATTR_GORIGHT); | |
| }, | |
| // Go directly to slide param:'jumpTo'. Animating in correct direction. | |
| _slideJump : function(jumpTo) { | |
| var carousel = this.obj; | |
| jumpTo = parseInt(jumpTo); | |
| if(jumpTo === carousel.slide_index || jumpTo > this.slide_length || jumpTo < 0) { | |
| console.error("invalid slide index. wtf m8?"); | |
| return false; | |
| } else if (jumpTo > this.slide_index) { | |
| this.slide_index = jumpTo; | |
| this._slide(ATTR_GORIGHT); | |
| } else { | |
| this.slide_index = jumpTo; | |
| this._slide(ATTR_GOLEFT); | |
| } | |
| }, | |
| /* | |
| Swipe Detection | |
| */ | |
| _swipeSetup : function() { | |
| var self = this, | |
| touchsurface = this.obj, | |
| startX, | |
| startY, | |
| threshold, //required min distance traveled to be considered swipe | |
| allowedTime = 1000, // maximum time allowed to travel that distance | |
| startTime; | |
| touchsurface.addEventListener("touchstart", function(e) { | |
| //Would prevent clicks on prev/next buttons.. | |
| // e.preventDefault(); | |
| var touchobj = e.changedTouches[0]; | |
| startX = touchobj.pageX; | |
| startY = touchobj.pageY; | |
| startTime = new Date().getTime(); // record time when finger first makes contact with surface | |
| threshold = touchsurface.clientWidth/4; | |
| }); | |
| touchsurface.addEventListener("touchmove", function(e) { | |
| e.preventDefault(); // prevent scrolling when inside DIV | |
| if(!startTime) { return; } | |
| //}); | |
| // | |
| //touchsurface.addEventListener("touchend", function(e) { | |
| //Would prevent clicks on prev/next buttons.. | |
| // e.preventDefault() | |
| var touchobj = e.changedTouches[0], | |
| distX = touchobj.pageX - startX, // get total dist traveled by finger while in contact with surface | |
| distY = touchobj.pageY - startY, // get total dist traveled by finger while in contact with surface | |
| elapsedTime = new Date().getTime() - startTime; // get time elapsed | |
| // check that elapsed time is within specified, horizontal dist traveled >= threshold, and vertical dist traveled < horizontal distance | |
| var swipeBool = (elapsedTime <= allowedTime) && | |
| (Math.abs(distX) >= threshold) && | |
| (Math.abs(distX) > Math.abs(distY)); | |
| if(swipeBool) { | |
| self._handleSwipe(distX < 0); | |
| startTime = 0; | |
| } | |
| }); | |
| }, | |
| _handleSwipe : function(goRight) { | |
| if (goRight) | |
| this._slideRight(); | |
| else { | |
| this._slideLeft(); | |
| } | |
| }, | |
| }; | |
| // Exposed Class | |
| self.Carousel = Carousel; | |
| })(); | |
| new Carousel(".carousel-1"); |
| /* Custom CSS */ | |
| #main { | |
| max-width: 800px; | |
| margin: auto; | |
| } | |
| img { | |
| display: block; | |
| position: absolute; | |
| top:0;left:0;bottom:0;right:0; | |
| max-width: 80%; | |
| max-height: 90%; | |
| margin: auto; | |
| } | |
| /* Carousel CSS */ | |
| /* | |
| Basic Carousel layout | |
| */ | |
| .carousel-main { | |
| position: relative; | |
| width: 100%; | |
| padding-bottom: 60%; | |
| margin: auto; | |
| overflow: hidden; | |
| } | |
| .carousel-main.preventDoubleTap { | |
| pointer-events: none; | |
| } | |
| .carousel-item { | |
| position: absolute; | |
| width: 100%; | |
| height: 100%; | |
| top: 0; | |
| opacity: 0; | |
| } | |
| /* | |
| Animating carousel-items | |
| */ | |
| .carousel-item.active, | |
| .carousel-item.prev, | |
| .carousel-item.next { | |
| opacity: 1; | |
| animation-duration: 0.5s; | |
| animation-timing-function: ease-in-out; | |
| animation-fill-mode: forwards; | |
| animation-iteration-count: 1; | |
| animation-delay: 0s; | |
| } | |
| /* slide("_R") */ | |
| @keyframes slideLeftOut { | |
| 0% { transform: translateX(0%);} | |
| 100% { transform: translateX(-100%);} | |
| } | |
| @keyframes slideRightIn { | |
| 0% { transform: translateX(100%);} | |
| 100% { transform: translateX(0%);} | |
| } | |
| .carousel-item.prev { | |
| animation-name: slideLeftOut; | |
| } | |
| .carousel-item.active.next { | |
| animation-name: slideRightIn; | |
| } | |
| /* slide("_L") */ | |
| @keyframes slideRightOut { | |
| 0% { transform: translateX(0%);} | |
| 100% { transform: translateX(100%);} | |
| } | |
| @keyframes slideLeftIn { | |
| 0% { transform: translateX(-100%);} | |
| 100% { transform: translateX(0%);} | |
| } | |
| .carousel-item.prev.active { | |
| animation-name: slideLeftIn; | |
| } | |
| .carousel-item.next { | |
| animation-name: slideRightOut; | |
| } | |
| /* | |
| Carousel UI | |
| */ | |
| .carousel-btn, .carousel-dot { | |
| background: rgba(0,0,0,0.5); | |
| color: red; | |
| cursor: pointer; | |
| } | |
| /* Buttons */ | |
| .carousel-btn { | |
| position: absolute; | |
| top: 0; | |
| bottom: 0; | |
| width: 3em; | |
| outline: 0; | |
| border: 0; | |
| } | |
| .carousel-btn[data-dir="_L"] { | |
| left: 0; | |
| } | |
| .carousel-btn[data-dir="_R"] { | |
| right: 0; | |
| } | |
| .carousel-btn[data-dir="_L"]:after, | |
| .carousel-btn[data-dir="_R"]:after { | |
| content: ''; | |
| width: 2em; | |
| height: 2em; | |
| position: absolute; | |
| margin-top: -1em; | |
| transform: rotate(45deg); | |
| } | |
| .carousel-btn[data-dir="_L"]:after { | |
| border-left: 0.5em solid currentColor; | |
| border-bottom: 0.5em solid currentColor; | |
| left: 1em; | |
| } | |
| .carousel-btn[data-dir="_R"]:after { | |
| border-right: 0.5em solid currentColor; | |
| border-top: 0.5em solid currentColor; | |
| right: 1em; | |
| } | |
| /* Dots */ | |
| .carousel-dots { | |
| position: absolute; | |
| bottom: 0; | |
| left: 0; | |
| right: 0; | |
| height: 2em; | |
| text-align: center; | |
| } | |
| .carousel-dot { | |
| display: inline-block; | |
| width: 1em; | |
| height: 1em; | |
| border-radius: 50%; | |
| margin: 0 0.5em; | |
| position: relative; | |
| top: 0.5em; | |
| transition: all 0.4s ease-out 0s; | |
| transform: scale(1); | |
| } | |
| .carousel-dot.active { | |
| background: currentColor; | |
| transform: scale(1.2); | |
| } |
Testing https://github.com/swieder227/POJS-carousel
A Pen by Andreas Borgen on CodePen.