<link href="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
<script src="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<!------ Include the above in your HEAD tag ---------->
<!--pictures from Adobe Stock-->
<p id="loading">loading...</p>
<div id="images">
<div class="lighten">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/593507/lighten1.jpg" alt="" />
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/593507/lighten2.jpg" alt="" />
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/593507/lighten3.jpg" alt="" />
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/593507/lighten4.jpg" alt="" />
</div>
<div class="normal">
<img src="https://img.traveltriangle.com/blog/wp-content/tr:w-700,h-400/uploads/2016/07/shutterstock_115227475.jpg" alt="" />
<img src="https://img.traveltriangle.com/blog/wp-content/tr:w-700,h-400/uploads/2017/07/Varanasi.jpg" alt="" />
<img src="https://img.traveltriangle.com/blog/wp-content/tr:w-700,h-400/uploads/2017/07/Agra1.jpg" alt="" />
<img src="https://img.traveltriangle.com/blog/wp-content/tr:w-700,h-400/uploads/2016/07/spiti-valley-ki-monastery.jpg" alt="" />
</div>
<nav>
<ul>
<li class="pre"></li>
<li class="next"></li>
</ul>
</nav>
</div>
*{
margin: 0;
padding: 0;
}
body{
background: #222;
}
#loading{
color: #fff;
position: absolute;
letter-spacing: 3px;
width: 10em;
line-height: 2em;
top: calc(50% - 1em) ;
left: calc(50% - 5em);
z-index: 2;
text-align: center;
}
.loading-done{
display: none;
}
#images{
position: absolute;
width: 600px;
left: calc(50% - 300px);
}
.canvas{
position: relative;
display:block;
overflow: hidden;
}
#images img{
display: none;
vertical-align: bottom;
}
canvas{
vertical-align: bottom;
position: absolute;
}
nav ul{
height: 0;
margin: 0;
}
nav li {
position: absolute;
width: 25px;
height: 25px;
opacity: .3;
list-style: none;
top: calc(50% - 12px);
z-index: 100;
transition: opacity .3s;
}
nav li:hover{
opacity: .7;
}
.pre.after-loading{
left: -40px;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
border-top: 2px solid #fff;
border-left: 2px solid #fff;
}
.next.after-loading{
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
border-top: 2px solid #fff;
border-right: 2px solid #fff;
right: -40px;
}
/*
Double exposure is photographic technique that combines 2 different images into a single image.
Then I use this technique with canvas blend modes.
*/
window.onload = function(){
var loading = document.getElementById("loading");
loading.classList.add("loading-done");
var property = {
element: "#images",
parallax: .6,
interval: 2200,
animDuration: 1300,
easing: easingInOutQuad
}
var slider = new DXslider(property);
slider.init();
}
class DXslider{
constructor(property){
this.images = document.querySelector(property.element);
this.preButton = document.querySelector(property.element + " nav .pre");
this.nextButton = document.querySelector(property.element + " nav .next");
this.lightenImages = document.querySelectorAll(".lighten img");
this.normalImages = document.querySelectorAll(".normal img");
this.canvasBox = document.createElement("div");
this.paraEffect = property.parallax; //have to clamp 0 ~ 1
this.canvasArray = [];
this.progress = 0;
this.animating = false;
this.interval = property.interval;
this.left = true;
this.duration = property.animDuration;
this.easing = property.easing;
this.images.appendChild(this.canvasBox);
this.canvasBox.classList.add("canvas");
}
init(){
this.settingStyle();
this.settingCanvas();
this.preButton.addEventListener("click", function(e){
if(!this.animating){
this.left = false;
clearTimeout(this.timer);
this.slide();
}
}.bind(this), false);
this.preButton.addEventListener("touchend", function(e){
if(!this.animating){
this.left = false;
clearTimeout(this.timer);
this.slide();
}
}.bind(this), false);
this.nextButton.addEventListener("click", function(e){
if(!this.animating){
this.left = true;
clearTimeout(this.timer);
this.slide();
}
}.bind(this), false);
this.nextButton.addEventListener("touchend", function(e){
if(!this.animating){
this.left = true;
clearTimeout(this.timer);
this.slide();
}
}.bind(this), false);
}
settingStyle(){
this.imagesWidth = this.images.offsetWidth;
this.width = this.lightenImages[0].width;
this.height = this.lightenImages[0].height;
this.dpi = this.width / this.imagesWidth;
this.images.style.height = this.canvasBox.style.height = this.imagesWidth * this.height / this.width + "px";
this.preButton.classList.add("after-loading");
this.nextButton.classList.add("after-loading");
}
settingCanvas(){
var canvas, context, normal, lighten, n;
for(var i = 0, len = this.normalImages.length * 2; i < len; i++){
canvas = document.createElement("canvas");
this.canvasBox.appendChild(canvas);
context = canvas.getContext("2d");
canvas.width = this.width;
canvas.height = this.height;
canvas.style.width = this.imagesWidth + "px";
canvas.style.height = this.imagesWidth * this.height / this.width + "px";
//add images(lighten and normal) into canvasArray
n = i % (len / 2);
normal = this.normalImages[n];
lighten = this.lightenImages[n];
this.canvasArray.push({
canvas: canvas, context: context, normal: normal, lighten: lighten
});
}
this.render(this.progress, -this.imagesWidth);
this.timer = setTimeout(this.slide.bind(this), this.interval);
}
slide(){
this.left ?
this.tween(-this.imagesWidth, this.duration, this.easing) :
this.tween(this.imagesWidth, this.duration, this.easing);
}
tween(change, duration, easingFunc){
var startTime = new Date();
this.progress = 0;
this.animating = true;
this.update(startTime, change, duration, easingFunc);
}
update(startTime, change, duration, easingFunc){
var time = new Date() - startTime;
if(time < duration){
this.progress = easingFunc(time / duration);
this.render(this.progress, change);
requestAnimationFrame(this.update.bind(this, startTime, change, duration, easingFunc));
} else {
if(this.left){
var firstEle = this.canvasArray[0];
this.canvasArray.shift();
this.canvasArray.push(firstEle);
} else {
var lastEle = this.canvasArray[this.canvasArray.length - 1];
this.canvasArray.pop();
this.canvasArray.unshift(lastEle);
}
this.progress = 1;
this.animating = false;
time = duration;
this.left = true;
this.render(0, -this.imagesWidth);
this.timer = setTimeout(this.slide.bind(this), this.interval);
}
}
render(progress, position){
for(var i = 0, len = this.canvasArray.length; i < len; i++){
var canvas = this.canvasArray[i].canvas;
canvas.style.setProperty("-webkit-transform", "translate(" + (progress * position - (len / 2 - i) * this.imagesWidth) + "px, 0)");
canvas.style.transform = "translate(" + (progress * position - (len / 2 - i) * this.imagesWidth) + "px, 0)";
var context = this.canvasArray[i].context;
context.clearRect(0, 0, this.width, this.height);
context.globalCompositeOperation = "source-over";
context.drawImage(this.canvasArray[i].normal, 0, 0, this.width, this.height);
context.globalCompositeOperation = "lighten";
context.drawImage(this.canvasArray[i].lighten, ((len / 2 - i) * this.imagesWidth - progress * position) * this.dpi * this.paraEffect, 0, this.width, this.height);
}
}
}
//easing
//prepare only easingInOutQuad
function easingInOutQuad(t){
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
}