JSFiddle - React, Tailwind, and code Playground
by kthornbloom
HTML
<div class="container">
<div class="item">
<div class="card">1<div class="indicator">⯇ SLIDE</div></div>
</div>
<div class="item">
<div class="card">2</div>
</div>
<div class="item">
<div class="card">3</div>
</div>
<div class="item">
<div class="card">4</div>
</div>
<div class="item">
<div class="card">5</div>
</div>
</div>
CSS
@import url('https://fonts.googleapis.com/css?family=Oswald');
*
{
box-sizing:border-box;
margin:0;
padding:0;
}
body
{
background:radial-gradient(ellipse at bottom left, #000, #333);
font-family:'Oswald';
height:100vh;
overflow:hidden;
user-select:none;
}
.container
{
display:flex;
flex-direction:row-reverse;
height:100%;
justify-content:center;
position:relative;
width:100%;
}
.item
{
cursor:grab;
height:100%;
padding:60px;
position:absolute;
touch-action:none;
transition:transform .5s;
max-width:500px;
width:100%;
}
.sliding .item
{
transition:none;
}
.card
{
align-items:center;
background:#eee;
color:#888;
display:flex;
font-size:30vh;
height:100%;
justify-content:center;
padding:20px;
pointer-events:none;
position:relative;
transition:box-shadow .3s;
width:100%;
}
.next .card:before,
.next .card:after
{
content:'';
display:block;
height:7px;
position:absolute;
}
.next .card:before
{
background:rgba(255,255,255,.6);
bottom:-7px;
left:4%;
width:92%;
}
.next .card:after
{
background:rgba(255,255,255,.2);
bottom:-14px;
left:7%;
width:86%;
}
.active
{
z-index:2 !important;
}
.next
{
z-index:1 !important;
}
.active .card
{
box-shadow:0 0 10px #000;
}
.indicator
{
animation:move 1s infinite ease-in-out;
bottom:20px;
color:#ccc;
font-size:5vh;
left:20px;
position:absolute;
z-index:10;
}
@keyframes move
{
0% { transform:translateX(0); }
50% { transform:translateX(20%); }
}
JavaScript
class Caroustack
{
constructor(params)
{
// params
params = params || {};
this.container = document.querySelector(params.container || '.container');
this.items = document.querySelectorAll((params.container || '.container') + ' > ' + (params.items || '.item'));
// transition variables
this.currentSlide = 0;
this.tempSlide = this.currentSlide;
this.nextSlide = Math.min(this.currentSlide+1, this.items.length-1);
this.delta = 0;
this.angle = 0;
// handlers
this.initHandler = event => this.initScroll(event);
this.moveHandler = event => this.moveScroll(event);
this.endHandler = event => this.endScroll(event);
// listeners
this.container.addEventListener('pointerdown', this.initHandler);
this.container.addEventListener('pointerup', this.endHandler);
this.container.addEventListener('pointercancel', this.endHandler);
// set item order
this.items.forEach((i, index) => i.style.zIndex = -index);
this.items[this.currentSlide].classList.add('active');
this.items[this.nextSlide].classList.add('next');
}
initScroll(event)
{
// set initial values
this.initialTouch = event.clientX;
this.container.classList.add('sliding');
this.container.addEventListener('pointermove', this.moveHandler);
}
moveScroll(event)
{
// transition values
this.delta = this.initialTouch - event.clientX;
this.angle = this.delta / 100;
this.scaling = 1 - Math.abs(this.delta) / 5000;
// limit direction on first and last items
//if(Math.sign(this.delta) > 0 && this.currentSlide === this.items.length - 1) return;
//else if(Math.sign(this.delta) < 0 && this.currentSlide === 0) return;
// set different slide if right or left
let slide = this.tempSlide;
this.tempSlide = this.delta < 0 ? this.currentSlide === 0 ? this.items.length - 1 : Math.max(this.currentSlide - 1, 0) : this.currentSlide;
// prevent spam slide stuck
if(this.tempSlide !== slide)
{
this.items[slide].style.transform = `none`;
}
// update slide...