<div id="slideshow" class="slideshow">
	<div id="slideshow-container" class="slideshow-container"></div>
	<ul id="pager" class="pager">
		<li>
			<a href="#" class="pager-btn pager-btn-prev">
				<</a>
		</li>
		<li><a href="#" class="pager-btn pager-btn-next">></a></li>
	</ul>
	<div id="nav" class="nav"></div>
</div>
html,
body {
	height:100%;
}
body {
	display:flex;
	align-items: center;
	justify-content: center;
}
.slideshow {
	background-color: #777;
	height: 40vw;
	overflow: hidden;
	position:relative;
	//position: absolute;
	//top: 0%;
	//left: 50%;
	//-webkit-transform: translateX(-50%);
	//transform: translateX(-50%);
	width: 60vw;
	@media (min-width: 800px) {
		height: 300px;
		width: 500px;
	}
	&-container {
		position: absolute;
		top: 0;
		width: 100%;
		> img {
			display: block;
			height: auto;
			max-width: 100%;
			position: absolute;
			user-select: none;
			-webkit-user-select: none;
			-webkit-user-drag: none;
		}
	}
}

.pager {
	list-style: none;
	margin: 0;
	padding: 0;
	position: absolute;
	top: 50%;
	left: 50%;
	-webkit-transform: translate(-50%, -50%);
	transform: translate(-50%, -50%);
	width: 100%;
	&-btn {
		background: #333;
		border-radius: 100%;
		color: #fff;
		font-size: 2vw;
		height: 8vw;
		line-height: 8vw;
		position: absolute;
		top: 0;
		-webkit-transform: translateY(-50%);
		transform: translateY(-50%);
		text-decoration: none;
		text-align: center;
		width: 8vw;
		@media (min-width: 800px) {
			height: 44px;
			line-height: 44px;
			width: 44px;
		}
		&-prev {
			left: 1vw;
		}
		&-next {
			right: 1vw;
		}
	}
}

.nav {
	position: absolute;
	bottom: 0;
	left: 0;
	text-align: center;
	width: 100%;
	&-btn {
		background: #ccc;
		border-radius: 100%;
		display: inline-block;
		height: 4vw;
		margin: 0 5px;
		width: 4vw;
		&.active {
			background: #333;
			cursor: default;
		}
		@media (min-width: 800px) {
			border-radius: 50%;
			height: 20px;
			width: 20px;
		}
	}
}
(() => {
	
	//画像に関するクラス
	class Asset {
		constructor() {
			
			//URL
			this.urls = [
				"https://tsukulog.net/wp-content/uploads/2018/06/kazukihiro512102_TP_V.jpg",
				"https://tsukulog.net/wp-content/uploads/2018/06/kazukihiro512157_TP_V.jpg",
				"https://tsukulog.net/wp-content/uploads/2018/06/kazukihiro512158_TP_V.jpg",
				"https://tsukulog.net/wp-content/uploads/2018/06/kazukihiro512159_TP_V.jpg",
				"https://tsukulog.net/wp-content/uploads/2018/06/kazukihiro512160_TP_V.jpg"
			];
			
			//生成した画像を格納する配列
			this._images = [];
			
			//画像の枚数
			this._imageLength = 0;
		}
		
		get images() {
			return this._images;
		}
		
		get imageLength() {
			return this._imageLength;
		}
		
		//画像を生成するメソッド
		createImage() {
			
			//URLの数だけ画像を生成
			for(let i = 0; i < this.urls.length; i++) {
				const image = new Image();
				image.src = this.urls[i];
				this._images[i] = image;
			}
			
			this._imageLength = this._images.length;
			//console.log(this._images);
			//console.log(this._imageLength);
		}
	}
	
	//スライドの集合に関するクラス
	class Slides {
		constructor() {
			this.elem = document.getElementById("slideshow-container");
			
			//現在表示されているスライドの番号
			this._currentIndex = 0;
			
			//#slideshow-containerをmousedownしたときのポインターのx座標
			this.downX = 0;
			
			//#slideshow-containerをmousemoveしたときのポインターのx座標
			this.moveX = 0;
			
			//downX~moveXまでの距離
			this.differenceX = 0;
			
			//ドラッグしているかどうか
			this.isDragging = false;
		}
		
		get currentIndex() {
			return this._currentIndex;
		}
		
		//_currentIndexを更新するメソッド
		set currentIndex(value) {
			if(typeof value === 'number') {
				this._currentIndex = value;
			}
		}
		
		//ドラッグ&ドロップでスライドを移動できる状態にするメソッド
		setupListener(timer, nav) {
			this.elem.addEventListener("mousedown", e => this.mouseDown(e));
			document.body.addEventListener("mousemove", e => this.mouseMove(e));
			document.body.addEventListener("mouseup", e => this.mouseUp(e, nav));
			document.body.addEventListener("mouseleave", e => this.mouseUp(e, nav));

			this.elem.addEventListener("touchstart", e => this.mouseDown(e));
			document.body.addEventListener("touchmove", e => this.mouseMove(e));
			document.body.addEventListener("touchend", e => this.mouseUp(e, nav));
			document.body.addEventListener("touchleave", e => this.mouseUp(e, nav));
			
			this.elem.addEventListener('mouseover', (e) => timer.stop());
			this.elem.addEventListener('mouseout', (e) => timer.start(this, nav));
		}
		
		//#slideshow-containerに画像を挿入するメソッド
		insertImages(images) {
			images.forEach((image) => this.elem.appendChild(image));
		}
		
		//挿入された画像を横並びに配置するメソッド
		setImages(images) {
			images.forEach((image, index) => image.style.left = `${100 * index}%`);
		}
		
		//スライドを移動させるメソッド
		move(index) {
			const image = this.elem.getElementsByTagName('img');
			const max = image.length - 1;
			
			//先頭のスライドを左に移動させると最後のスライドを表示する
			if (index < 0) {
				index = max;
			}
			
			//最後のスライドを右に移動させると先頭のスライドを表示する
			if (index > max) {
				index = 0;
			}

			this.elem.style.transition = "left 0.5s linear";
			
			//通常左へ進むためマイナスとしている
			this.elem.style.left = `${-100 * index}%`;

			//更新
			this._currentIndex = index;
		}
		
		//mousedownされると実行されるメソッド
		mouseDown(e) {
			e.preventDefault();

			let event;

			if (e.type === "mousedown") {
				event = e;
			} else {
				event = e.changedTouches[0];
			}

			this.downX = event.clientX;

			//ドラッグ開始
			this.isDragging = true;
		}
		
		//mousemoveされると実行されるメソッド
		mouseMove(e) {
			
			//ドラッグしていれば
			if (this.isDragging) {
				e.preventDefault();

				let event;

				if (e.type === "mousemove") {
					event = e;
				} else {
					event = e.changedTouches[0];
				}

				this.moveX = event.clientX;

				this.differenceX = this.moveX - this.downX;
				
				//mousemoveされたときの#slideshow-containerの幅を取得
				const slidesWidth = this.elem.clientWidth;
				
				this.elem.style.transform = `translateX(${Slides.clamp(this.differenceX, this.differenceX - slidesWidth, this.differenceX + slidesWidth)}px)`;
			}
		}
		
		//mouseupされると実行されるメソッド
		mouseUp(e, nav) {
			
			//ドラッグしていれば
			if (this.isDragging) {
				
				//ドラッグ終了
				this.isDragging = false;

				/*
				if (this.differenceX == this.downX) {
					return;
				}
				*/
				
				//mouseupされたときの#slideshow-containerの幅
				const slidesWidth = this.elem.clientWidth;
				
				//右へslidesWidth / 4より多くドラッグしていれば移動する
				if (this.moveX > this.downX && Math.abs(this.differenceX) > slidesWidth / 4) {
					this._currentIndex = this._currentIndex - 1;
					console.log('→');
				}
				
				//左へslidesWidth / 4より多くドラッグしていれば移動する
				if (this.moveX < this.downX && Math.abs(this.differenceX) > slidesWidth / 4) {
					this._currentIndex = this._currentIndex + 1;
					console.log('←');
				}
				
				//スライドを移動
				this.move(this._currentIndex);
				
				//ナビゲーションを更新
				nav.update(this._currentIndex);
				
				this.elem.style.transition = "all 0.5s ease";
				this.elem.style.transform = '';
				
				//transition後に次に備えて削除
				setTimeout(() => {
					this.elem.style.transition = '';
				}, 500);
			}
		}
		
		//スライドの移動範囲を制限するメソッド
		static clamp(number, min, max){ //numberをminからmaxまでの値で返す
			return Math.max(min, Math.min(number, max));
		}
	}
	
	//ナビゲーションに関するクラス
	class Nav {
		constructor() {
			
			//#navの参照を取得
			this.elem = document.getElementById("nav");
			
			//ナビボタンを取得
			this.btn = this.elem.getElementsByTagName("a");
		}
		
		//#navに画像の枚数だけボタンを挿入するメソッド
		set(imageLength) {
			for (let i = 0; i < imageLength; i++) {
				
				//ナビボタンを生成
				const navBtn = document.createElement("a");
				navBtn.classList.add('nav-btn');
				navBtn.setAttribute("href", "#");
				
				//ナビボタンを#navに挿入
				this.elem.appendChild(navBtn);
			}
		}
		
		//ナビゲーションをクリックできる状態にするメソッド
		setupListener(slides) {
			const navBtn = this.btn;
			
			//ナビボタンをクリックするとクリックしたナビボタンと同じ番号のスライドに切り替える
			for (let i = 0; i < navBtn.length; i++) {
				navBtn[i].addEventListener("click", e => {
					e.preventDefault();
					
					//クリックしたナビボタン
					const target = e.target;
					
					//クリックしたナビボタンの番号
					const targetIndex = [].slice.call(navBtn).indexOf(target);
					//https://lab.syncer.jp/Web/JavaScript/Snippet/54/
					//https://lab.syncer.jp/Web/JavaScript/Snippet/53/
					
					//クリックしたナビボタンと同じ番号のスライドに切り替える
					slides.move(targetIndex);
					
					//targetIndex番目のナビボタンをアクティブにする
					this.update(targetIndex);
				});
			}
		}
		
		//現在表示されている画像と同じ番号のナビボタンをアクティブにするメソッド
		update(currentIndex) {
			const navBtn = this.btn;
			//console.log(navBtn);
			
			//一旦全てのナビボタンをinactiveにする
			for (let i = 0; i < navBtn.length; i++) {
				navBtn[i].classList.remove("active");
			}

			//currentIndex番目のナビボタンをactiveにする
			navBtn[currentIndex].classList.add("active");
		}
	}
	
	//ページャーに関するクラス
	class Pager {
		constructor() {
			this.elem = document.getElementById("pager");
			this.btn = this.elem.getElementsByTagName("a");
		}
		
		//ページャーをクリックできる状態にするメソッド
		setupListener(slides, nav) {
			const pagerBtn = this.btn;
			for (let i = 0; i < pagerBtn.length; i++) {
				pagerBtn[i].addEventListener("click", e => {
					e.preventDefault();
					const target = e.target;
					
					//クリックしたページャーボタンが.pager-btn-prevであれば一つ前のスライドに切り替える
					if (target.classList.contains("pager-btn-prev")) {
						slides.currentIndex = slides.currentIndex - 1;
						
					//.pager-btn-nextであれば一つ後のスライドに切り替える
					} else {
						slides.currentIndex = slides.currentIndex + 1;
					}
					
					slides.move(slides.currentIndex);
					nav.update(slides.currentIndex);
				});
			}
		}
	}
	
	//タイマーに関するクラス
	class Timer {
		constructor() {
			this.timer = null;
		}
		start(slides, nav) {
			
			//5秒毎にスライドを切り替える
			this.timer = setInterval(() => {
				slides.currentIndex = slides.currentIndex + 1;
				slides.move(slides.currentIndex);
				nav.update(slides.currentIndex);
			}, 5000);
		}
		stop() {
			clearInterval(this.timer);
		}
	}
	
	/*
	 * MAIN SCRIPTS
	 */
	
	//インスタンス化
	const asset = new Asset();
	const slides = new Slides();
	const nav = new Nav();
	const pager = new Pager();
	const timer = new Timer();
	
	//全体を初期化する関数
	function init() {
		asset.createImage();
		const images = asset.images;
		const imageLength = asset.imageLength;

		slides.insertImages(images);
		slides.setImages(images);
		slides.move(slides.currentIndex);
		
		nav.set(imageLength);
		nav.update(slides.currentIndex);
		
		timer.start(slides, nav);
		
		setupListeners();
	}
	
	//スライドショーを操作できる状態にする関数
	function setupListeners() {
		nav.setupListener(slides);
		pager.setupListener(slides, nav);
		slides.setupListener(timer, nav);
	}
	
	//起動
	init();
})();
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://code.jquery.com/jquery-2.2.4.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js