JSFiddle - React, Tailwind, and code Playground

by kthornbloom

HTML

<div class="container">
	<div class="item">
		<div class="card">1<div class="indicator">&#11207; 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...