Skip to content

Instantly share code, notes, and snippets.

@Sphinxxxx
Created August 20, 2017 13:00
Show Gist options
  • Save Sphinxxxx/ac586623630d830346ee1d18fa506720 to your computer and use it in GitHub Desktop.
Save Sphinxxxx/ac586623630d830346ee1d18fa506720 to your computer and use it in GitHub Desktop.
Swipeable carousel
<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);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment