How to loop slides and autoplay in custom javascript? - javascript

I'm a beginner in javascript. I downloaded a custom javacsript slide from this website:
https://www.cssscript.com/swiper-thumbnail-paginator/
Slide demo here: https://www.cssscript.com/demo/swiper-thumbnail-paginator/
I've also created a demo on jsfiddle: https://jsfiddle.net/t4c1nb3g/
The file consists of 5 files, namely: index.html, style.css, debounce.js, script.js, slide.js
And it has 6 images.
Currently, there is no slide loop in the demo, I want the slide to start itself (autoplay) and repeat it again and again (looping).
How can I make it loop for the slide and start itself (autoplay)?
Below is the index.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Slider</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="slide-wrapper">
<ul class="slide">
<li><img src="https://i.stack.imgur.com/2fkLR.jpg" alt=""></li>
<li><img src="https://i.stack.imgur.com/gN1Ri.jpg" alt=""></li>
<li><img src="https://i.stack.imgur.com/FgqYP.jpg" alt=""></li>
<li><img src="https://i.stack.imgur.com/su1na.jpg" alt=""></li>
<li><img src="https://i.stack.imgur.com/vZYry.jpg" alt=""></li>
<li><img src="https://i.stack.imgur.com/5dXtQ.jpg" alt=""></li>
</ul>
</div>
<div class="wrap-controls">
<div class="arrow-nav">
<button class="prev"></button>
</div>
<ul class="custom-controls">
<li><img src="https://i.stack.imgur.com/2fkLR.jpg" alt=""></li>
<li><img src="https://i.stack.imgur.com/gN1Ri.jpg" alt=""></li>
<li><img src="https://i.stack.imgur.com/FgqYP.jpg" alt=""></li>
<li><img src="https://i.stack.imgur.com/su1na.jpg" alt=""></li>
<li><img src="https://i.stack.imgur.com/vZYry.jpg" alt=""></li>
<li><img src="https://i.stack.imgur.com/5dXtQ.jpg" alt=""></li>
</ul>
<div class="arrow-nav">
<button class="next"></button>
</div>
</div>
<script type="module" src="js/script.js"></script>
</body>
</html>
Below is the style.css file:
body {
margin: 0px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
}
ul {
padding: 0px;
margin: 0px;
list-style: none;
}
img {
display: block;
max-width: 100%;
}
.slide-wrapper {
overflow: hidden;
}
.slide {
display: flex;
}
.slide:hover {
will-change: transform;
}
.slide li {
flex-shrink: 0;
max-width: 600px;
margin: 0 20px;
border-radius: 4px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0,0,0,.4);
opacity: .8;
transform: scale(.8);
transition: .4s;
}
.slide li.active {
opacity: 1;
transform: scale(1);
}
[data-control="slide"] {
display: flex;
justify-content: center;
margin-top: 20px;
}
[data-control="slide"] li a {
display: block;
width: 12px;
height: 12px;
background: #FB5;
border-radius: 50%;
overflow: hidden;
text-indent: -999px;
margin: 5px;
}
[data-control="slide"] li.active a, [data-control="slide"] li a:hover {
background: #E54;
}
.custom-controls {
display: flex;
justify-content: center;
margin-top: 40px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.custom-controls li {
opacity: .8;
transform: scale(.8);
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
margin: 2px;
box-shadow: 0 2px 2px rgba(0,0,0,.5);
transition: .3s;
cursor: pointer;
}
.custom-controls li.active {
opacity: 1;
transform: scale(1);
}
.arrow-nav {
display: flex;
justify-content: space-around;
margin: 20px 10px 0 10px;
}
.arrow-nav button {
cursor: pointer;
border: none;
border-radius: 50%;
color: white;
width: 30px;
height: 30px;
background: #999 url('../img/arrow.svg') center center no-repeat;
outline: none;
}
.arrow-nav button:hover {
background: #333 url('../img/arrow.svg') center center no-repeat;
transition: ease-in-out .3s;
}
.arrow-nav button.prev {
transform: rotate(-180deg);
}
.wrap-controls {
display: flex;
justify-content: center;
align-items: center;
}
Below is the debounce.js file:
export default function debounce(callback, delay) {
let timer;
return (...args) => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
callback(...args);
timer = null;
}, delay);
};
}
Below is the script.js file:
import SlideNav from './slide.js';
const slide = new SlideNav('.slide', '.slide-wrapper');
slide.init();
slide.addArrow('.prev', '.next');
slide.addControl('.custom-controls');
Below is the slide.js file:
import debounce from './debounce.js';
export class Slide {
constructor(slide, wrapper) {
this.slide = document.querySelector(slide)
this.wrapper = document.querySelector(wrapper);
this.dist = { finalPosition: 0, startX: 0, movement: 0 }
this.activeClass = 'active';
this.changeEvent = new Event('changeEvent');
}
transition(active) {
this.slide.style.transition = active ? 'transform .3s' : '';
}
moveSlide(distX) {
this.dist.movePosition = distX;
this.slide.style.transform = `translate3d(${distX}px, 0, 0)`;
}
updatePosition(clientX) {
this.dist.movement = (this.dist.startX - clientX) * 1.6;
return this.dist.finalPosition - this.dist.movement;
}
onStart(event) {
let movetype;
if (event.type === 'mousedown') {
event.preventDefault();
this.dist.startX = event.clientX;
movetype = 'mousemove';
} else {
this.dist.startX = event.changedTouches[0].clientX;
movetype = 'touchmove';
}
this.wrapper.addEventListener(movetype, this.onMove);
this.transition(false);
}
onMove(event) {
const pointerPosition = (event.type === 'mousemove') ? event.clientX : event.changedTouches[0].clientX;
const finalPosition = this.updatePosition(pointerPosition);
this.moveSlide(finalPosition);
}
onEnd(event) {
const movetype = (event.type === 'mouseup') ? 'mousemove' : 'touchmove';
this.wrapper.removeEventListener(movetype, this.onMove);
this.dist.finalPosition = this.dist.movePosition;
this.transition(true);
this.changeSlideOnEnd();
}
changeSlideOnEnd() {
if (this.dist.movement > 120 && this.index.next !== undefined) {
this.activeNextSlide();
} else if (this.dist.movement < -120 && this.index.prev !== undefined) {
this.activePrevSlide();
} else {
this.changeSlide(this.index.active);
}
}
addSlideEvents() {
this.wrapper.addEventListener('mousedown', this.onStart);
this.wrapper.addEventListener('touchstart', this.onStart);
this.wrapper.addEventListener('mouseup', this.onEnd);
this.wrapper.addEventListener('touchend', this.onEnd);
}
// Slides config
slidePosition(slide) {
const margin = (this.wrapper.offsetWidth - slide.offsetWidth) / 2;
return -(slide.offsetLeft - margin);
}
slidesConfig() {
this.slideArray = [...this.slide.children].map((element) => {
const position = this.slidePosition(element);
return { position, element };
});
}
slidesIndexNav(index) {
const last = this.slideArray.length - 1;
this.index = {
prev: index ? index - 1 : undefined,
active: index,
next: index === last ? undefined : index + 1,
}
}
changeSlide(index) {
const activeSlide = this.slideArray[index];
this.moveSlide(activeSlide.position);
this.slidesIndexNav(index);
this.dist.finalPosition = activeSlide.position;
this.changeActiveClass();
this.wrapper.dispatchEvent(this.changeEvent);
}
changeActiveClass() {
this.slideArray.forEach(item => item.element.classList.remove(this.activeClass));
this.slideArray[this.index.active].element.classList.add(this.activeClass);
}
activePrevSlide() {
if (this.index.prev !== undefined) this.changeSlide(this.index.prev);
}
activeNextSlide() {
if (this.index.next !== undefined) this.changeSlide(this.index.next);
}
onResize() {
setTimeout(() => {
this.slidesConfig();
this.changeSlide(this.index.active);
}, 1000);
}
addResizeEvent() {
window.addEventListener('resize', this.onResize);
}
bindEvents() {
this.onStart = this.onStart.bind(this);
this.onMove = this.onMove.bind(this);
this.onEnd = this.onEnd.bind(this);
this.activePrevSlide = this.activePrevSlide.bind(this);
this.activeNextSlide = this.activeNextSlide.bind(this);
this.onResize = debounce(this.onResize.bind(this), 200);
}
init() {
this.bindEvents();
this.transition(true);
this.addSlideEvents();
this.slidesConfig();
this.addResizeEvent();
this.changeSlide(0);
return this;
}
}
export default class SlideNav extends Slide {
constructor(slide, wrapper) {
super(slide, wrapper);
this.bindControlEvents();
}
addArrow(prev, next) {
this.prevElement = document.querySelector(prev);
this.nextElement = document.querySelector(next);
this.addArrowEvent();
}
addArrowEvent() {
this.prevElement.addEventListener('click', this.activePrevSlide);
this.nextElement.addEventListener('click', this.activeNextSlide);
}
createControl() {
const control = document.createElement('ul');
control.dataset.control = 'slide';
this.slideArray.forEach((item, index) => {
control.innerHTML += `<li>${index + 1}</li>`;
});
this.wrapper.appendChild(control);
return control;
}
eventControl(item, index) {
item.addEventListener('click', (event) => {
event.preventDefault();
this.changeSlide(index);
});
this.wrapper.addEventListener('changeEvent', this.activeControlItem);
}
activeControlItem() {
this.controlArray.forEach(item => item.classList.remove(this.activeClass));
this.controlArray[this.index.active].classList.add(this.activeClass);
}
addControl(customControl) {
this.control = document.querySelector(customControl) || this.createControl();
this.controlArray = [...this.control.children];
this.activeControlItem();
this.controlArray.forEach(this.eventControl);
}
bindControlEvents() {
this.eventControl = this.eventControl.bind(this);
this.activeControlItem = this.activeControlItem.bind(this);
}
}
Thank you very much,
best regards.
Franks.

After debugging your case on Codepen, I finally figured it out.
Basically you need to know 2 changes that I made:
The first one is handling the edge cases:
When you're at the end and click Next, you should go to the beginning
When you're at the beginning and click Previous, you should go to the end
The 2nd aspect is making this slider autoplaying or looping.
Now for the code
For the 1st change (the edge cases), modify in your code the methods activePrevSlide and activeNextSlide as the following:
activePrevSlide() {
if (this.index.prev !== undefined)
this.changeSlide(this.index.prev);
else
this.changeSlide(this.slideArray.length - 1);
}
activeNextSlide() {
if (this.index.next !== undefined)
this.changeSlide(this.index.next);
else
this.changeSlide(0);
}
For the 2nd aspect, add at the end of script.js, this statement:
setInterval(slide.activeNextSlide, 3000);
It will autoplay each 3 seconds (you can change it definitely).
Finally, this is the full code, if you want to play here:
function debounce(callback, delay) {
let timer;
return (...args) => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
callback(...args);
timer = null;
}, delay);
};
}
class Slide {
constructor(slide, wrapper) {
this.slide = document.querySelector(slide)
this.wrapper = document.querySelector(wrapper);
this.dist = { finalPosition: 0, startX: 0, movement: 0 }
this.activeClass = 'active';
this.changeEvent = new Event('changeEvent');
}
transition(active) {
this.slide.style.transition = active ? 'transform .3s' : '';
}
moveSlide(distX) {
this.dist.movePosition = distX;
this.slide.style.transform = `translate3d(${distX}px, 0, 0)`;
}
updatePosition(clientX) {
this.dist.movement = (this.dist.startX - clientX) * 1.6;
return this.dist.finalPosition - this.dist.movement;
}
onStart(event) {
let movetype;
if (event.type === 'mousedown') {
event.preventDefault();
this.dist.startX = event.clientX;
movetype = 'mousemove';
} else {
this.dist.startX = event.changedTouches[0].clientX;
movetype = 'touchmove';
}
this.wrapper.addEventListener(movetype, this.onMove);
this.transition(false);
}
onMove(event) {
const pointerPosition = (event.type === 'mousemove') ? event.clientX : event.changedTouches[0].clientX;
const finalPosition = this.updatePosition(pointerPosition);
this.moveSlide(finalPosition);
}
onEnd(event) {
const movetype = (event.type === 'mouseup') ? 'mousemove' : 'touchmove';
this.wrapper.removeEventListener(movetype, this.onMove);
this.dist.finalPosition = this.dist.movePosition;
this.transition(true);
this.changeSlideOnEnd();
}
changeSlideOnEnd() {
if (this.dist.movement > 120 && this.index.next !== undefined) {
this.activeNextSlide();
} else if (this.dist.movement < -120 && this.index.prev !== undefined) {
this.activePrevSlide();
} else {
this.changeSlide(this.index.active);
}
}
addSlideEvents() {
this.wrapper.addEventListener('mousedown', this.onStart);
this.wrapper.addEventListener('touchstart', this.onStart);
this.wrapper.addEventListener('mouseup', this.onEnd);
this.wrapper.addEventListener('touchend', this.onEnd);
}
// Slides config
slidePosition(slide) {
const margin = (this.wrapper.offsetWidth - slide.offsetWidth) / 2;
return -(slide.offsetLeft - margin);
}
slidesConfig() {
this.slideArray = [...this.slide.children].map((element) => {
const position = this.slidePosition(element);
return { position, element };
});
}
slidesIndexNav(index) {
const last = this.slideArray.length - 1;
this.index = {
prev: index ? index - 1 : undefined,
active: index,
next: index === last ? undefined : index + 1,
}
}
changeSlide(index) {
const activeSlide = this.slideArray[index];
this.moveSlide(activeSlide.position);
this.slidesIndexNav(index);
this.dist.finalPosition = activeSlide.position;
this.changeActiveClass();
this.wrapper.dispatchEvent(this.changeEvent);
}
changeActiveClass() {
this.slideArray.forEach(item => item.element.classList.remove(this.activeClass));
this.slideArray[this.index.active].element.classList.add(this.activeClass);
}
activePrevSlide() {
if (this.index.prev !== undefined)
this.changeSlide(this.index.prev);
else
this.changeSlide(this.slideArray.length - 1);
}
activeNextSlide() {
if (this.index.next !== undefined)
this.changeSlide(this.index.next);
else
this.changeSlide(0);
}
onResize() {
setTimeout(() => {
this.slidesConfig();
this.changeSlide(this.index.active);
}, 1000);
}
addResizeEvent() {
window.addEventListener('resize', this.onResize);
}
bindEvents() {
this.onStart = this.onStart.bind(this);
this.onMove = this.onMove.bind(this);
this.onEnd = this.onEnd.bind(this);
this.activePrevSlide = this.activePrevSlide.bind(this);
this.activeNextSlide = this.activeNextSlide.bind(this);
this.onResize = debounce(this.onResize.bind(this), 200);
}
init() {
this.bindEvents();
this.transition(true);
this.addSlideEvents();
this.slidesConfig();
this.addResizeEvent();
this.changeSlide(0);
return this;
}
}
class SlideNav extends Slide {
constructor(slide, wrapper) {
super(slide, wrapper);
this.bindControlEvents();
}
addArrow(prev, next) {
this.prevElement = document.querySelector(prev);
this.nextElement = document.querySelector(next);
this.addArrowEvent();
}
addArrowEvent() {
this.prevElement.addEventListener('click', this.activePrevSlide);
this.nextElement.addEventListener('click', this.activeNextSlide);
}
createControl() {
const control = document.createElement('ul');
control.dataset.control = 'slide';
this.slideArray.forEach((item, index) => {
control.innerHTML += `<li>${index + 1}</li>`;
});
this.wrapper.appendChild(control);
return control;
}
eventControl(item, index) {
item.addEventListener('click', (event) => {
event.preventDefault();
this.changeSlide(index);
});
this.wrapper.addEventListener('changeEvent', this.activeControlItem);
}
activeControlItem() {
this.controlArray.forEach(item => item.classList.remove(this.activeClass));
this.controlArray[this.index.active].classList.add(this.activeClass);
}
addControl(customControl) {
this.control = document.querySelector(customControl) || this.createControl();
this.controlArray = [...this.control.children];
this.activeControlItem();
this.controlArray.forEach(this.eventControl);
}
bindControlEvents() {
this.eventControl = this.eventControl.bind(this);
this.activeControlItem = this.activeControlItem.bind(this);
}
}
const slide = new SlideNav('.slide', '.slide-wrapper');
slide.init();
slide.addArrow('.prev', '.next');
slide.addControl('.custom-controls');
setInterval(slide.activeNextSlide, 3000);
body {
margin: 0px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
}
ul {
padding: 0px;
margin: 0px;
list-style: none;
}
img {
display: block;
max-width: 100%;
}
.slide-wrapper {
overflow: hidden;
}
.slide {
display: flex;
}
.slide:hover {
will-change: transform;
}
.slide li {
flex-shrink: 0;
max-width: 600px;
margin: 0 20px;
border-radius: 4px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0,0,0,.4);
opacity: .8;
transform: scale(.8);
transition: .4s;
}
.slide li.active {
opacity: 1;
transform: scale(1);
}
[data-control="slide"] {
display: flex;
justify-content: center;
margin-top: 20px;
}
[data-control="slide"] li a {
display: block;
width: 12px;
height: 12px;
background: #FB5;
border-radius: 50%;
overflow: hidden;
text-indent: -999px;
margin: 5px;
}
[data-control="slide"] li.active a, [data-control="slide"] li a:hover {
background: #E54;
}
.custom-controls {
display: flex;
justify-content: center;
margin-top: 40px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.custom-controls li {
opacity: .8;
transform: scale(.8);
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
margin: 2px;
box-shadow: 0 2px 2px rgba(0,0,0,.5);
transition: .3s;
cursor: pointer;
}
.custom-controls li.active {
opacity: 1;
transform: scale(1);
}
.arrow-nav {
display: flex;
justify-content: space-around;
margin: 20px 10px 0 10px;
}
.arrow-nav button {
cursor: pointer;
border: none;
border-radius: 50%;
color: white;
width: 30px;
height: 30px;
background: #999 url('../img/arrow.svg') center center no-repeat;
outline: none;
}
.arrow-nav button:hover {
background: #333 url('../img/arrow.svg') center center no-repeat;
transition: ease-in-out .3s;
}
.arrow-nav button.prev {
transform: rotate(-180deg);
}
.wrap-controls {
display: flex;
justify-content: center;
align-items: center;
}
<div class="slide-wrapper">
<ul class="slide">
<li><img src="https://i.stack.imgur.com/2fkLR.jpg" alt=""></li>
<li><img src="https://i.stack.imgur.com/gN1Ri.jpg" alt=""></li>
<li><img src="https://i.stack.imgur.com/FgqYP.jpg" alt=""></li>
<li><img src="https://i.stack.imgur.com/su1na.jpg" alt=""></li>
<li><img src="https://i.stack.imgur.com/vZYry.jpg" alt=""></li>
<li><img src="https://i.stack.imgur.com/5dXtQ.jpg" alt=""></li>
</ul>
</div>
<div class="wrap-controls">
<div class="arrow-nav">
<button class="prev"></button>
</div>
<ul class="custom-controls">
<li><img src="https://i.stack.imgur.com/2fkLR.jpg" alt=""></li>
<li><img src="https://i.stack.imgur.com/gN1Ri.jpg" alt=""></li>
<li><img src="https://i.stack.imgur.com/FgqYP.jpg" alt=""></li>
<li><img src="https://i.stack.imgur.com/su1na.jpg" alt=""></li>
<li><img src="https://i.stack.imgur.com/vZYry.jpg" alt=""></li>
<li><img src="https://i.stack.imgur.com/5dXtQ.jpg" alt=""></li>
</ul>
<div class="arrow-nav">
<button class="next"></button>
</div>
</div>
Regards

Related

How to make a 3D carousal Inner rotation view?

I wanted to make a 3d rotating carousel like in the stack snippet, but required is a view from inside like in the image below. How can i achieve this?
window.addEventListener('load', () => {
var carousels = document.querySelectorAll('.carousel');
for (var i = 0; i < carousels.length; i++) {
carousel(carousels[i]);
}
});
function carousel(root) {
var
figure = root.querySelector('figure'),
nav = root.querySelector('nav'),
images = figure.children,
n = images.length,
gap = root.dataset.gap || 0,
bfc = 'bfc' in root.dataset,
theta = 2 * Math.PI / n,
currImage = 0;
setupCarousel(n, parseFloat(getComputedStyle(images[0]).width));
window.addEventListener('resize', () => {
setupCarousel(n, parseFloat(getComputedStyle(images[0]).width))
});
setupNavigation();
function setupCarousel(n, s) {
var apothem = s / (2 * Math.tan(Math.PI / n));
figure.style.transformOrigin = `50% 50% ${- apothem}px`;
for (var i = 0; i < n; i++) {
images[i].style.padding = `${gap}px`;
}
for (i = 1; i < n; i++) {
images[i].style.transformOrigin = `50% 50% ${- apothem}px`;
images[i].style.transform = `rotateY(${i * theta}rad)`;
}
if (bfc) {
for (i = 0; i < n; i++) {
images[i].style.backfaceVisibility = 'hidden';
}
}
rotateCarousel(currImage);
}
function setupNavigation() {
nav.addEventListener('click', onClick, true);
function onClick(e) {
e.stopPropagation();
var t = e.target;
if (t.tagName.toUpperCase() != 'BUTTON') {
return;
}
if (t.classList.contains('next')) {
currImage++;
}
else {
currImage--;
}
rotateCarousel(currImage);
}
}
function rotateCarousel(imageIndex) {
figure.style.transform = `rotateY(${imageIndex * -theta}rad)`;
}
}
body {
margin: 0;
font-family: 'Roboto', sans-serif;
font-size: 16px;
}
h1 {
text-align: center;
margin-bottom: 1.5em;
}
h2 {
text-align: center;
color: #555;
margin-bottom: 0;
}
.carousel {
padding: 20px;
perspective: 500px;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
}
.carousel>* {
flex: 0 0 auto;
}
.carousel figure {
margin: 0;
width: 40%;
transform-style: preserve-3d;
transition: transform 0.5s;
}
.carousel figure img {
width: 100%;
box-sizing: border-box;
padding: 0 0px;
}
.carousel figure img:not(:first-of-type) {
position: absolute;
left: 0;
top: 0;
}
.carousel nav {
display: flex;
justify-content: center;
margin: 20px 0 0;
}
.carousel nav button {
flex: 0 0 auto;
margin: 0 5px;
cursor: pointer;
color: #333;
background: none;
border: 1px solid;
letter-spacing: 1px;
padding: 5px 10px;
}
<h2>Eight images, with 80px gap</h2>
<div class="carousel" data-gap="80">
<figure>
<img src="https://source.unsplash.com/VkwRmha1_tI/800x533" alt="">
<img src="https://source.unsplash.com/EbuaKnSm8Zw/800x533" alt="">
<img src="https://source.unsplash.com/kG38b7CFzTY/800x533" alt="">
<img src="https://source.unsplash.com/nvzvOPQW0gc/800x533" alt="">
<img src="https://source.unsplash.com/mCg0ZgD7BgU/800x533" alt="">
<img src="https://source.unsplash.com/1FWICvPQdkY/800x533" alt="">
<img src="https://source.unsplash.com/bjhrzvzZeq4/800x533" alt="">
<img src="https://source.unsplash.com/7mUXaBBrhoA/800x533" alt="">
</figure>
<nav>
<button class="nav prev">Prev</button>
<button class="nav next">Next</button>
</nav>
</div>
The carousel needs to be rotated and translated in the opposite direction to make it an inner rotation view.
The transform-origin sets the center of the carousel. The first two numbers are x and y, which just center the carousel on the screen. The third number is z which determines whether the carousel is moved towards the screen or away from the screen. The negative sign on apothem is removed to pull it out of the screen so the center becomes closer to the camera and achieves the inner carousel. backface-visibility needs to be always set to hidden because there might be images behind the camera now. Then to fix the rotation direction with the next button, * -theta is changed to positive.
window.addEventListener('load', () => {
var carousels = document.querySelectorAll('.carousel');
for (var i = 0; i < carousels.length; i++) {
carousel(carousels[i]);
}
});
function carousel(root) {
var
figure = root.querySelector('figure'),
nav = root.querySelector('nav'),
images = figure.children,
n = images.length,
gap = root.dataset.gap || 0,
theta = 2 * Math.PI / n,
currImage = 0;
setupCarousel(n, parseFloat(getComputedStyle(images[0]).width));
window.addEventListener('resize', () => {
setupCarousel(n, parseFloat(getComputedStyle(images[0]).width))
});
setupNavigation();
function setupCarousel(n, s) {
var apothem = s / (2 * Math.tan(Math.PI / n));
figure.style.transformOrigin = `50% 50% ${apothem}px`;
for (var i = 0; i < n; i++) {
images[i].style.padding = `${gap}px`;
}
for (i = 1; i < n; i++) {
images[i].style.transformOrigin = `50% 50% ${apothem}px`;
images[i].style.transform = `rotateY(${i * theta}rad)`;
}
rotateCarousel(currImage);
}
function setupNavigation() {
nav.addEventListener('click', onClick, true);
function onClick(e) {
e.stopPropagation();
var t = e.target;
if (t.tagName.toUpperCase() != 'BUTTON') {
return;
}
if (t.classList.contains('next')) {
currImage++;
}
else {
currImage--;
}
rotateCarousel(currImage);
}
}
function rotateCarousel(imageIndex) {
figure.style.transform = `rotateY(${imageIndex * theta}rad)`;
}
}
body {
margin: 0;
font-family: 'Roboto', sans-serif;
font-size: 16px;
}
h1 {
text-align: center;
margin-bottom: 1.5em;
}
h2 {
text-align: center;
color: #555;
margin-bottom: 0;
}
.carousel {
padding: 20px;
perspective: 500px;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
}
.carousel>* {
flex: 0 0 auto;
}
.carousel figure {
margin: 0;
width: 40%;
transform-style: preserve-3d;
transition: transform 0.5s;
}
.carousel figure img {
width: 100%;
box-sizing: border-box;
padding: 0 0px;
backface-visibility: hidden;
}
.carousel figure img:not(:first-of-type) {
position: absolute;
left: 0;
top: 0;
}
.carousel nav {
display: flex;
justify-content: center;
margin: 20px 0 0;
}
.carousel nav button {
flex: 0 0 auto;
margin: 0 5px;
cursor: pointer;
color: #333;
background: none;
border: 1px solid;
letter-spacing: 1px;
padding: 5px 10px;
}
<h2>Eight images, with 80px gap</h2>
<div class="carousel" data-gap="80">
<figure>
<img src="https://source.unsplash.com/VkwRmha1_tI/800x533" alt="">
<img src="https://source.unsplash.com/EbuaKnSm8Zw/800x533" alt="">
<img src="https://source.unsplash.com/kG38b7CFzTY/800x533" alt="">
<img src="https://source.unsplash.com/nvzvOPQW0gc/800x533" alt="">
<img src="https://source.unsplash.com/mCg0ZgD7BgU/800x533" alt="">
<img src="https://source.unsplash.com/1FWICvPQdkY/800x533" alt="">
<img src="https://source.unsplash.com/bjhrzvzZeq4/800x533" alt="">
<img src="https://source.unsplash.com/7mUXaBBrhoA/800x533" alt="">
</figure>
<nav>
<button class="nav prev">Prev</button>
<button class="nav next">Next</button>
</nav>
</div>

Is there any other way to sort a drag and drop todo list without using the index of the items?

I'm working on a javascript to-do list where you can view all the elements on the list or you can view just the active items or the completed items.
Each of the views has its own array which I sorted out using the index of each element
but when I reorder the list on one of the views, the change is not implemented in the other views.
How do I rectify this?
const dragArea1 = document.querySelector('#task1');
const dragArea2 = document.querySelector('#task2');
const dragArea3 = document.querySelector('#task3');
const addnew = document.querySelector('[name="addnew"]')
const add = document.querySelector('[name="new"]')
const countIt = document.querySelector('#count')
var all = [];
var active = [];
var complete = [];
var lists = document.querySelectorAll('ul');
var views = document.querySelectorAll('.action .views a');
var mobileViews = document.querySelectorAll('#views a');
var list = document.querySelector('.list');
countIt.innerHTML = active.length;
addnew.addEventListener('click', () => {
var newItem
if (addnew.checked == true) {
newItem = {
val: add.value,
checked: false
}
all.push(newItem);
active.push(newItem);
window.setTimeout(() => {
addnew.checked = false;
add.value = '';
}, 300);
displayAll();
count();
}
})
list.addEventListener('click', (ev) => {
if (ev.target.tagName === 'LABEL' || ev.target.tagName === 'P' || ev.target.tagName === 'LI') {
ev.target.classList.toggle('checked');
sortAllList();
if (lists[1].style.display == 'block') {
activeToComplete();
}
if (lists[2].style.display == 'block') {
completeToActive();
}
sortActive();
sortComplete();
}
if (all.length == 0) {
var htmlCode = `<em style="text-align: center; width: 100%; padding: 20px;">add a todo item</em>`;
lists[0].innerHTML = htmlCode;
}
if (active.length == 0) {
var htmlCode = `<em style="text-align: center; width: 100%; padding: 20px;">add a todo item</em>`;
lists[1].innerHTML = htmlCode;
}
if (complete.length == 0) {
var htmlCode = `<em style="text-align: center; width: 100%; padding: 20px;">complete a todo item</em>`;
lists[2].innerHTML = htmlCode;
}
// console.log(ev.target.tagName);
})
function count() {
// to keep count of active items
countIt.innerHTML = active.length;
}
views[0].classList.add('view')
mobileViews[0].classList.add('view')
function displayAll() {
sortActive();
sortComplete();
var htmlCode = "";
if (all.length !== 0) {
for (let i = 0; i < all.length; i++) {
htmlCode += `
<li draggable="true">
<div class="check">
<input type="checkbox" name="listItem" id="item${i}">
<label for="item${i}"></label>
</div>
<p class="itemdesc">${all[i].val}</p>
<span onclick="del(${i})">╳</span>
</li>
`
}
lists[0].innerHTML = htmlCode;
}
lists[0].style.display = 'block';
lists[1].style.display = 'none';
lists[2].style.display = 'none';
views[0].classList.add('view')
views[1].classList.remove('view')
views[2].classList.remove('view')
mobileViews[0].classList.add('view')
mobileViews[1].classList.remove('view')
mobileViews[2].classList.remove('view')
count()
keepChecked();
}
function sortActive() {
// to add active items to the active array
var fit
fit = all.filter(el => el.checked == false)
active = fit
count();
}
function sortComplete() {
//to add completed items to the complete array
var com
com = all.filter(el => el.checked == true)
complete = com
// console.log('complete', complete);
}
function sortAllList() {
// to sort the items into active and completed
const items = document.querySelectorAll('#task1 li');
for (let i = 0; i < all.length; i++) {
if (items[i].classList.contains('checked') == true) {
all[i].checked = true
} else {
all[i].checked = false
}
}
}
function activeToComplete() {
let newA
const items = document.querySelectorAll('#task2 li')
for (let i = 0; i < active.length; i++) {
if (items[i].classList.contains('checked') == true) {
active[i].checked = true;
// active.splice(i,1);
// console.log(active.splice());
} else {
active[i].checked = false
}
}
newA = active.filter(el => el.checked !== true)
console.log(newA);
active = newA;
}
function keepChecked() {
// to keep the completed items checked afetr changing views
const allItems = document.querySelectorAll('#task1 li');
for (let i = 0; i < all.length; i++) {
if (all[i].checked == true) {
allItems[i].classList.add('checked')
}
}
}
function completeToActive() {
const items = document.querySelectorAll('#task3 li')
for (let i = 0; i < complete.length; i++) {
if (items[i].classList.contains('checked') == true) {
complete[i].checked = true;
} else {
complete[i].checked = false
complete.splice(i, 1);
console.log(complete.splice());
}
}
}
function displayActive() {
sortAllList();
sortActive();
var htmlCode = "";
if (active.length !== 0) {
for (let i = 0; i < active.length; i++) {
htmlCode += `
<li draggable="true">
<div class="check">
<input type="checkbox" name="listItem" id="item${i}">
<label for="item${i}"></label>
</div>
<p class="itemdesc">${active[i].val}</p>
<span onclick="del(${i})">╳</span>
</li>
`
}
lists[1].innerHTML = htmlCode;
}
lists[1].style.display = 'block';
lists[0].style.display = 'none';
lists[2].style.display = 'none';
views[1].classList.add('view')
views[0].classList.remove('view')
views[2].classList.remove('view')
mobileViews[1].classList.add('view')
mobileViews[0].classList.remove('view')
mobileViews[2].classList.remove('view')
count()
}
function displayCompleted() {
let unique = [...new Set(complete)]
// console.log(unique[0].val);
var htmlCode = "";
if (unique.length !== 0) {
for (let i = 0; i < unique.length; i++) {
htmlCode += `
<li draggable="true" class="checked">
<div class="check">
<input type="checkbox" name="listItem" id="item${i}">
<label for="item${i}"></label>
</div>
<p class="itemdesc">${unique[i].val}</p>
<span onclick="del(${i})">╳</span>
</li>
`
}
lists[2].innerHTML = htmlCode;
}
lists[2].style.display = 'block';
lists[1].style.display = 'none';
lists[0].style.display = 'none';
views[2].classList.add('view')
views[0].classList.remove('view')
views[1].classList.remove('view')
mobileViews[2].classList.add('view')
mobileViews[1].classList.remove('view')
mobileViews[0].classList.remove('view')
count()
}
function clearComplete() {
var htmlCode = `<em style="text-align: center; width: 100%; padding: 20px;">complete a todo item</em>`;
complete = [];
lists[2].innerHTML = htmlCode;
}
function del(theIndex) {
let i = theIndex;
if (lists[0].style.display == 'block') {
all.splice(i, 1);
displayAll();
}
if (lists[1].style.display == 'block') {
active.splice(i, 1);
let removeFromAll = all.find(el => {
el == active.splice()
})
all.splice(removeFromAll, 1);
displayActive();
}
if (lists[2].style.display == 'block') {
complete.splice(i, 1);
let removeFromAll = all.find(el => {
el == complete.splice()
})
all.splice(removeFromAll, 1);
displayCompleted();
}
sortActive();
sortComplete();
}
new Sortable(dragArea1, {
animation: 350
})
new Sortable(dragArea2, {
animation: 350
})
new Sortable(dragArea3, {
animation: 350
})
:root {
--blue: hsl(220, 98%, 61%);
/* vd -> Very Drak */
--vdblue: hsl(235, 21%, 11%);
--vdDesaturatedblue: hsl(235, 24%, 19%);
--lightgrayblue: hsl(234, 39%, 85%);
--lightgrayblueh: hsl(236, 33%, 92%);
--darkgrayblue: hsl(234, 11%, 52%);
--vdGrayishblueh: hsl(233, 14%, 35%);
--vdGrayishblue: hsl(237, 14%, 26%);
--checkbg: linear-gradient(rgba(87, 221, 255, .7), rgba(192, 88, 243, .7));
--font: 'Josefin Sans', sans-serif;
font-size: 18px;
}
* {
padding: 0;
margin: 0;
font-family: var(--font);
/* font-weight: 700; */
}
*,
*::after,
*::before {
box-sizing: border-box;
}
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
textarea:-webkit-autofill,
textarea:-webkit-autofill:hover,
textarea:-webkit-autofill:focus,
select:-webkit-autofill,
select:-webkit-autofill:hover,
select:-webkit-autofill:focus {
border: none;
-webkit-text-fill-color: white;
background-color: transparent !important;
-webkit-box-shadow: 0 0 0px 1000px #00000000 inset;
transition: background-color 5000s ease-in-out 0s;
}
input:focus, input:active, input:visited, textarea:focus, textarea:active, textarea:visited{
background-color: transparent;
border: none;
outline: none;
}
a, em, span{
display: inline-block;
cursor: pointer;
}
a{
text-decoration: none;
display: inline-block;
}
header, main, footer{
width: 100%;
max-width: 30rem;
padding: 10px;
}
main {
display: flex;
flex-direction: column;
gap: 30px;
align-items: center;
}
main #new,
li {
display: flex;
align-items: center;
gap: 20px;
padding: 1rem;
width: 100%;
}
main section,
main #views {
width: 100%;
}
main section,
main #new,
main #views {
border-radius: 5px;
}
main .list {
min-height: 2.5rem;
max-height: 20rem;
/* height: 10rem; */
position: relative;
overflow-y: auto;
}
main .list ul {
/* position: absolute; */
/* top: 20px; */
width: 100%;
display: none;
}
main .list ul:nth-child(1) {
display: block;
}
main #new input[name="new"] {
padding: 10px;
height: inherit;
}
input {
background-color: transparent;
width: calc(100% - 70px);
border: none;
font-size: 1rem;
}
li {
justify-content: flex-start;
}
li .check {
position: relative;
}
main #new .check input,
li .check input {
display: none;
}
main #new .check label,
li .check label {
width: 30px;
height: 30px;
border-radius: 30px;
display: inline-block;
}
main #new .check input:checked~label,
li.checked .check label {
background-image: var(--checkbg), url(images/icon-check.svg);
background-position: center center;
background-repeat: no-repeat;
}
li p {
width: 85%;
}
li.checked label {
background-color: #66666696;
}
li.checked p {
text-decoration: line-through;
}
li span {
/* justify-self: flex-end; */
display: none;
}
li:hover span {
display: flex;
}
main .action {
display: flex;
justify-content: space-between;
/* gap: 2rem; */
padding: 1.1rem;
font-size: .8rem;
}
.views a,
#views a {
font-weight: 700;
}
.action a.view {
color: var(--blue);
}
main #views {
padding: .8rem;
text-align: center;
font-size: .8rem;
display: none;
}
#views a.view {
color: var(--blue);
}
main #views+p {
font-size: .7rem;
}
li,
em {
border-bottom: 1px solid var(--darkgrayblue);
}
li,
li p,
main .action a:hover {
color: var(--lightgrayblue);
}
a,
em,
li.checked p,
p,
span,
input,
li span {
color: var(--darkgrayblue);
}
header img {
content: url("images/icon-sun.svg");
}
main #new {
background-color: var(--vdDesaturatedblue);
}
main #new .check label,
li .check label {
border: 1px solid var(--vdGrayishblue);
}
main #new .check label:hover,
li .check label:hover {
border: 1px solid var(--vdGrayishblue);
}
main section,
main #views {
background-color: var(--vdDesaturatedblue);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.15.0/Sortable.min.js"></script>
<main role="main">
<div id="new">
<div class="check">
<input type="checkbox" name="addnew" id="addnew">
<label for="addnew"></label>
</div>
<input type="text" name="new" placeholder="Create a new todo...">
</div>
<section>
<div class="list">
<ul id="task1">
<em style="text-align: center; width: 100%; padding: 20px;">add a todo item</em>
</ul>
<ul id="task2">
<em style="text-align: center; width: 100%; padding: 20px;">add a todo item</em>
</ul>
<ul id="task3">
<em draggable="true" style="text-align: center; width: 100%; padding: 20px;">complete a todo item</em>
</ul>
</div>
<div class="action">
<p>
<span id="count"></span> items left
</p>
<div class="views">
All
Active
Completed
</div>
Clear Completed
</div>
</section>
<div id="views">
All
Active
Completed
</div>
<p>Drag and drop to reorder list</p>
</main>

Javascript audio object not playing on mobile devices

I made this audio player but the codesandbox does not work in mobile devices. It works perfectly fine in desktop.
I get 'Could not get the stack frame of error' error when i try to play song from my phone.
Audio player
The player gets audio files data in a javascript object and they get built into a list. The audios start playing when list item is clicked. The code looks like this
$(function () {
$("#time-progress").progress({ percent: 0 });
let audio = new Audio();
const songs = [
{
artist: "Yaru Makaveli & Yada Sads",
image: "https://i.ytimg.com/vi/rI2vJENDvmY/maxresdefault.jpg",
title: "Cypher Weyn",
song: "cypherweyn2.mp3"
},
{
artist: "Abebe Araya",
image: "https://i.ytimg.com/vi/mp_cR7pVEcw/maxresdefault.jpg",
title: "Natsnet",
song: "natsnet.mp3"
},
{
artist: "Shewit & Semere",
image: "https://i.ytimg.com/vi/ucolLdVzRyg/maxresdefault.jpg",
title: "Betey",
song: "betey.mp3"
},
{
artist: "Q Rap M.O.DB",
image: "https://i.scdn.co/image/ab6761610000e5eb749c5e25b3d167fd3008914b",
title: "Waero",
song: "waero.mp3"
},
{
artist: "Yaru Makaveli & Yada Sads",
image: "https://i.ytimg.com/vi/rI2vJENDvmY/maxresdefault.jpg",
title: "Tealime",
song: "tealime.mp3"
},
{
artist: "Eden Gebreselassie",
image:
"https://is4-ssl.mzstatic.com/image/thumb/Music116/v4/00/4a/14/004a1407-eaed-45d3-048d-12dae76b7d3f/artwork.jpg/375x375bb.jpg",
title: "Goblel",
song: "goblel.mp3"
},
{
artist: "Amanuel Yemane",
image: "https://i.ytimg.com/vi/iukjMznrHcI/maxresdefault.jpg",
title: "Adi Latni",
song: "adilatni.mp3"
},
{
artist: "Tmnit Welday",
image: "https://i.ytimg.com/vi/MqVT5GdW6hQ/maxresdefault.jpg",
title: "Segar",
song: "segar.mp3"
}
];
let list_of_songs = songs
.map(function (song, index) {
return ` <div class="item" data-src="${song.song}"" data-title="${song.title}" data-artist="${song.artist}" data-index=${index} data-image=${song.image}>
<img class="ui avatar image" src="${song.image}"">
<div class="content">
<div id="equalizer">
<div id="bar1"></div>
<div id="bar2"></div>
<div id="bar3"></div>
<div id="bar4"></div>
</div>
<i class="icon button-overlay circle outline"></i>
<span class="header">${song.title}</span>
<div class="description">${song.artist}</div>
</div>
</div>`;
})
.join("");
let play = document.querySelector("#play");
let currentSong = 0;
document.getElementById("song-list").innerHTML = list_of_songs;
audio = new Audio(`./music/${songs[0].song}`);
let icons = document.querySelectorAll(".icon");
$(document).on("click", ".item", function () {
let { src, artist, title, image, index } = this.dataset;
currentSong = Number(index);
let list_items = document.querySelectorAll(".item");
list_items.forEach((e) => {
e.classList.remove("active");
});
this.classList.add("active");
let newaudio = new Audio(`./music/${src}`);
if (audio.currentTime > 0 && !audio.paused && audio.src == newaudio.src) {
play.classList.remove("pause");
play.classList.add("play");
audio.pause();
this.querySelector(".button-overlay").classList.add("play");
this.querySelector(".button-overlay").classList.remove("pause");
} else if (
audio.currentTime > 0 &&
audio.paused &&
audio.src == newaudio.src
) {
play.classList.remove("play");
play.classList.add("pause");
this.querySelector(".button-overlay").classList.add("pause");
this.querySelector(".button-overlay").classList.remove("play");
audio.play();
} else {
this.querySelector(".button-overlay").classList.add("pause");
this.querySelector(".button-overlay").classList.remove("play");
playSong(src, artist, title, image);
}
});
audio.addEventListener("timeupdate", function (e) {
let currentTime = audio.currentTime;
let duration = audio.duration;
let minutes = Math.floor(currentTime / 60);
minutes = minutes >= 10 ? minutes : "0" + minutes;
let seconds = Math.floor(currentTime % 60);
seconds = seconds >= 10 ? seconds : "0" + seconds;
document.querySelector("#timer").innerText = `${minutes}:${seconds}`;
//progress
let status = Math.floor((currentTime / duration) * 100);
$("#time-progress").progress({ percent: status });
});
let artist_img = document.querySelector(".artist-image");
let song_title = document.querySelector("#title");
icons.forEach((icon) => {
icon.addEventListener("click", (e) => {
let type = e.target.dataset.type;
let image, src, artist, title;
var list_items = document.querySelectorAll(".item");
switch (type) {
case "play":
if (audio.currentTime > 0) {
play.classList.remove("play");
play.classList.add("pause");
play.dataset.type = "pause";
audio.play();
} else {
currentSong = 0;
let item = document.querySelector(".item");
item.classList.add("active");
item.querySelector(".icon").classList.add("pause");
document
.querySelector(".item")
.querySelector(".button-overlay")
.classList.remove("play");
const { song, artist, title, image } = songs[0];
playSong(song, title, artist, image);
}
break;
case "pause":
audio.pause();
artist_img.classList.remove("rotate-image");
e.target.classList.remove("pause");
e.target.classList.add("play");
e.target.dataset.type = "play";
break;
case "prev":
currentSong =
currentSong - 1 < 0 ? songs.length - 1 : currentSong - 1;
list_items.forEach((e) => {
e.classList.remove("active");
});
list_items[currentSong].classList.add("active");
var off =
$(".item.active").offset().top - $("#song-list").offset().top;
$("#song-list").scrollTop(off);
image = songs[currentSong].image;
src = songs[currentSong].song;
artist = songs[currentSong].artist;
title = songs[currentSong].title;
playSong(src, title, artist, image);
break;
case "next":
currentSong =
currentSong + 1 > songs.length - 1 ? 0 : currentSong + 1;
list_items.forEach((e, index) => {
e.classList.remove("active");
});
list_items[currentSong].classList.add("active");
var off =
$(".item.active").offset().top - $("#song-list").offset().top;
$("#song-list").scrollTop(off);
image = songs[currentSong].image;
src = songs[currentSong].song;
artist = songs[currentSong].artist;
title = songs[currentSong].title;
playSong(src, title, artist, image);
break;
default:
break;
}
});
});
function playSong(src, artist, title, image) {
document.querySelector(
".artist-img-bg"
).style.backgroundImage = `url(${image})`;
audio.src = `./music/${src}`;
artist_img.src = `${image}`;
artist_img.classList.add("rotate-image");
song_title.innerText = `${artist} - ${title}`;
document.querySelector("#title").classList.add("song-title");
play.classList.remove("play");
play.classList.add("pause");
play.dataset.type = "pause";
audio.play();
}
$(document).on("click", ".button-overlay", function () {
if (audio.currentTime > 0) {
this.classList.remove("play");
this.classList.add("pause");
audio.play();
}
});
});
body {
margin: 0;
padding: 0;
z-index: 101;
}
.container {
padding: 15px;
margin: 0 auto;
width: 100%;
background: linear-gradient(#042a45, #7295ae);
display: flex;
height: 100vh;
align-items: center;
justify-content: center;
z-index: 100;
}
.music-player {
width: 400px;
height: auto;
background: linear-gradient(to top,#042a45, #7295ae);
border-radius: 1em;
display: flex;
align-items: center;
flex-direction: column;
padding: 10px;
}
#top-img-container {
width: 100%;
min-height: 200px;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
}
.artist-img-bg {
filter: blur(3px);
-webkit-filter: blur(3px);
background-repeat: no-repeat;
background-size: cover;
background-position: center;
position: absolute;
z-index: 1;
width: 100%;
min-height: 200px;
}
.artist-image {
margin-top: 20px ;
max-height: 300px;
width: 150px;height: 150px;max-width: 300px;
border-radius: 50%;
box-shadow: 0 0 10px 10px #154275;
position: absolute;
z-index: 999;
}
.rotate-image {
animation:rotate 100s infinite;
animation-timing-function: linear;
}
#keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(365deg);
}
}
.controls {
width: 100%;
margin-bottom: 2em;
margin-top: 1em;display: flex;
justify-content:space-around;
}
.icon {
color:aliceblue;
font-size: 1.5em !important;
cursor: pointer;
transition: transform .2s ease-in-out;
}
.icon:hover {
transform: scale(1.2);
}
.song-title-container {
position: relative;
overflow: hidden;
width: 90%;
margin: 10px auto 0;
}
.song-title {
margin: 10px 0 15px;
text-align: center;
color: #ffffff;
font-weight: 700;animation: song_title 7s infinite ease-in-out 3s;
animation-timing-function: linear;
position: relative;
}
#keyframes song_title {
0% {
left:0%;
} 49% {
left: -100%;
visibility: hidden;
} 50% {
left: 200%;
visibility: hidden;
}
51% {
left: 100%;
visibility: visible;
}
100% {
visibility: visible;
left: 0%
}
}
#timer, #song-list .header, #song-list .description {
color: white;
}
#playlist-title {
color: white;
width: 100%;
margin-left: 20px;
}
#song-list {
height: 200px;overflow-y: scroll;width: 95%;
background-color: #16415f;
border-radius: 1em;
}
#song-list .item {
cursor: pointer;
padding: 10px;
position: relative;
}
.item.active {
background-color: #38596e;
box-shadow: 0px 2px 10px #07141d;
}
.list-image {
position: relative;
display: inline;
}
.content .icon {
position: absolute;
left: 20px;
top: 20px;
visibility: hidden;
}
.item.active .content .button-overlay {
visibility: visible;
}
#song-list .avatar {
width: 3em;
height: 3em;
}
#song-list .item:hover {
background-color: #3a5a6f;
}
#equalizer {
position: absolute;
right: 5px;top: 50%;transform: translateY(-50%);width: 40px;
max-height: 20px;
visibility: hidden;
display: flex;
justify-content: space-evenly;
}
.item.active .content #equalizer {
visibility: visible;
}
#bar1, #bar2, #bar3, #bar4 {
background-color: #1aa303;
width: 2px;
}
#bar1 {
animation: bar1 1.2s linear infinite;
}
#bar2 {
animation: bar2 0.9s linear infinite;
}
#bar3 {
animation: bar2 1.4s linear infinite;
}
#bar4 {
animation: bar4 1s linear infinite;
}
#keyframes bar1 {
0% {
height: 5px;
}
50% {
height: 10px;
}
100% {
height: 5px;
}
}#keyframes bar2 {
0% {
height: 10px;
}
50% {
height: 5px;
}
100% {
height: 10px;
}
}
#keyframes bar3 {
0% {
height: 5px;
}
50% {
height: 10px;
}
100% {
height: 5px;
}
}
#keyframes bar4 {
0% {
height: 10px;
}
50% {
height:5px;
}
100% {
height: 10px;
}
}
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Music Player</title>
<link rel="stylesheet" href="./main.css" />
<link
href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css"
rel="stylesheet"
/>
</head>
<body>
<div class="ui-container container">
<div class="music-player">
<div id="top-img-container">
<div class="artist-img-bg"></div>
<img
src="https://i.pinimg.com/originals/1d/3a/26/1d3a2651f9ad02fb12aae08f618a2847.png"
class="artist-image"
alt="on and on"
/>
</div>
<div class="song-title-container">
<h3 id="title"></h3>
<div id="time-progress" class="ui tiny progress">
<div style="min-width: 1%" class="bar"></div>
<div id="timer" class="label">-00:00-</div>
</div>
</div>
<div class="controls">
<i data-type="prev" id="prev" class="icon backward"></i>
<i data-type="play" id="play" class="icon play circle outline"></i>
<i data-type="next" id="pause" class="icon forward"></i>
</div>
<h3 id="playlist-title">Playlist - Best of 2021</h3>
<div id="song-list" class="ui middle aligned divided list"></div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.js"></script>
<script type="text/javascript" src="./main.js"></script>
</body>
</html>
It works fine in desktop browsers but it does not work in mobile devices. What am I doin wrong?

How do I change grid for a memory game using dropdown list, I've manage to make it work but the cards stops flipping when I change the grid?

When I first load page the game works but when I change grid the game cards don't flip. I'm not sure if it the use of onchange inside the select tag or it something else. If I change the value of a variable named value inside displayCardsDiv manual it also works fine maybe if I could find a way to update it by dropddown list or button click it will work.
Html
<div class="game-container" >
<script>displayCardsDiv();</script>
<div ></div>
</div>
</div>
<div class="game-title">
<label class="changeGrid">change Grid</label>
<select id="grid" name="grid" onchange="displayCardsDiv()">
<option value="first" id="first">2x2</option>
<option value="second" id="second">3x2</option>
<option value="third" id="third">4x3</option>
</select>
<button class="reset-btn" onclick="resetGame()">Reset</button>
</div>
<script src="./src/script.js"></script>
</body>
Javascript
"src/img/apple-eye.png",
"src/img/apple-eye.png",
"src/img/blue-nife.png",
"src/img/blue-nife.png",
"src/img/devil.png",
"src/img/devil.png",
"src/img/headless.png",
"src/img/headless.png",
"src/img/heart.png",
"src/img/heart.png",
"src/img/mommy.png",
"src/img/mommy.png",
];
class MemoryGame {
constructor() {
this.cardsArray = playCard;
}
beforeGameStart() {
this.cardChecker = null;
this.matchingCardsArray = [];
this.isLocked = true;
setTimeout(() => {
this.shuffleCards(this.cardsArray);
this.isLocked = false;
}, 500);
}
handleCardFlip(card) {
if (this.flipCard(card)) {
card.classList.add("showingCard");
if (this.cardChecker) {
this.checkForMatch(card);
} else {
this.cardChecker = card;
}
}
}
checkForMatch(card) {
if (this.findTypeOfCard(card) === this.findTypeOfCard(this.cardChecker))
this.matchCards(card, this.cardChecker);
else this.misMatchCards(card, this.cardChecker);
this.cardChecker = null;
}
matchCards(firstCard, secondCard) {
this.matchingCardsArray.push(firstCard);
this.matchingCardsArray.push(secondCard);
firstCard.classList.add("matched");
secondCard.classList.add("matched");
setTimeout(() => {
if (this.matchingCardsArray.length === this.cardsArray.length) {
alert("All cards matched");
resetGame();
}
}, 1000);
}
misMatchCards(firstCard, secondCard) {
this.isLocked = true;
setTimeout(() => {
firstCard.classList.remove("showingCard");
secondCard.classList.remove("showingCard");
this.isLocked = false;
}, 1000);
}
shuffleCards(cardsArray) {
cardsArray.forEach((card) => {
let randomPos = Math.floor(Math.random() * cardsArray.length);
card.style.order = randomPos;
});
}
findTypeOfCard(card) {
return card.getElementsByClassName("value")[0].src;
}
flipCard(card) {
return (
!this.isLocked &&
!this.matchingCardsArray.includes(card) &&
card !== this.cardChecker
);
}
}
function resetGame() {
location.reload();
}
let gameContainer = document.querySelector(".game-container");
function twoByTwoGrid() {
gameContainer.style.gridTemplateColumns = "repeat(2, auto)";
return imagesArray.slice(0, 4);
}
function threeByTwoGrid() {
gameContainer.style.gridTemplateColumns = "repeat(3, auto)";
return imagesArray.slice(0, 6);
}
function displayCardsDiv() {
let deckOfCards = [];
let displayGameCards = "";
let value = document.querySelector("#grid").value;
if (value == "first") {
deckOfCards = twoByTwoGrid();
}
if (value === "second") {
deckOfCards = threeByTwoGrid();
}
if (value !== "first" && value !== "second") {
gameContainer.style.gridTemplateColumns = "repeat(4, auto)";
deckOfCards = [...imagesArray];
}
for (let i in deckOfCards) {
displayGameCards += `<div class="card">
<div class="card-back card-face"><img class="demonsCards" src="src/img/demons.png"></div>
<div class="card-front card-face"><img class="value" src="${deckOfCards[i]}"></div>
</div>`;
}
document.getElementsByClassName("game-container")[0].innerHTML =
displayGameCards;
}
displayCardsDiv();
const playCard = document.querySelectorAll(".card");
const game = new MemoryGame(playCard);
game.beforeGameStart();
for (let i = 0; i < imagesArray.length; i++) {
playCard[i].addEventListener("click", () => game.handleCardFlip(playCard[i]));
}
module.exports = { game };
CSS
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
min-height: 100vh;
}
body {
margin: 0;
background: radial-gradient(#46bddb, #4672db);
}
h1{
background: -webkit-repeating-linear-gradient(#eee, #333);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
img {
height: 175px;
width: 125px;
}
.game-title {
color: aliceblue;
font-family: 'Noto Sans Mono', monospace;
font-weight: normal;
text-align: center;
font-size: 4em;
margin-top: 0px;
}
.game-info-container {
grid-column: 1 / -1;
display: flex;
justify-content: space-between;
}
.game-info {
font-family: 'Pacifico', cursive;
color: aliceblue;
font-size: 4em;
}
.changeGrid{
font-size: large;
}
.game-container {
margin: 50px auto;
display: grid;
grid-template-columns:repeat(4, auto);
grid-gap: 10px;
justify-content: center;
perspective: 500px;
}
.card {
position: relative;
height: 175px;
width: 125px;
cursor: pointer;
}
.card-face {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
border-radius: 12px;
border-width: 1px;
border-style: solid;
overflow: hidden;
transition: transform 500ms ease-in-out;
backface-visibility: hidden;
}
.card.showingCard .card-back {
transform: rotateY(180deg);
}
.card.showingCard .card-front {
transform: rotateY(0);
}
.card-back {
background-color:aliceblue;
border-color: aliceblue;
transform: rotateY(0);
}
.demonsCards {
align-self: flex-start;
transition: transform 100ms ease-in-out;
transform: translateY(-10px);
}
.card:active {
transform: scale(0.79);
transition: transform .1s;
}
.card-front:hover .card-value {
transform: scale(1);
}
.card-front {
background-color: #FFBB89;
border-color: #333;
transform: rotateY(-180deg);
}
#media (max-width: 600px) {
.game-container {
grid-template-columns: repeat(2, auto)
}
.game-info-container {
flex-direction: column;
align-items: center;
}
}

Two identical modals, but one does not work. Why?

EDIT:
The new code:
const modal2 = () => {
'use strict';
class Modal {
constructor() {
this.triggers = document.querySelectorAll('.js-modal2');
this.close = document.querySelectorAll('.js-close-modal');
this.modals = document.querySelectorAll('.modal');
this.modalInners = document.querySelectorAll('.modal-inner');
this.listeners();
}
listeners() {
window.addEventListener('keydown', this.keyDown);
this.triggers.forEach(el => {
el.addEventListener('click', this.openModal, false);
});
this.modals.forEach(el => {
el.addEventListener('transitionend', this.revealModal, false);
el.addEventListener('click', this.backdropClose, false);
});
this.close.forEach(el => {
el.addEventListener('click', Modal.hideModal, false);
});
this.modalInners.forEach(el => {
el.addEventListener('transitionend', this.closeModal, false);
});
}
keyDown(e) {
if (27 === e.keyCode && document.body.classList.contains('modal-body')) {
Modal.hideModal();
}
}
backdropClose(el) {
if (!el.target.classList.contains('modal-visible')) {
return;
}
let backdrop = el.currentTarget.dataset.backdrop !== undefined ? el.currentTarget.dataset.backdrop : true;
if (backdrop === true) {
Modal.hideModal();
}
}
static hideModal() {
let modalOpen = document.querySelector('.modal.modal-visible');
modalOpen.querySelector('.modal-inner').classList.remove('modal-reveal');
document.querySelector('.modal-body').addEventListener('transitionend', Modal.modalBody, false);
document.body.classList.add('modal-fadeOut');
}
closeModal(el) {
if ('opacity' === el.propertyName && !el.target.classList.contains('modal-reveal')) {
document.querySelector('.modal.modal-visible').classList.remove('modal-visible');
}
}
openModal(el) {
if (!el.currentTarget.dataset.modal) {
console.error('No data-modal attribute defined!');
return;
}
let modalID = el.currentTarget.dataset.modal2;
let modal2 = document.getElementById(modalID);
document.body.classList.add('modal-body');
modal.classList.add('modal-visible');
}
revealModal(el) {
if ('opacity' === el.propertyName && el.target.classList.contains('modal-visible')) {
el.target.querySelector('.modal-inner').classList.add('modal-reveal');
}
}
static modalBody(el) {
if ('opacity' === el.propertyName && el.target.classList.contains('modal') && !el.target.classList.contains('modal-visible')) {
document.body.classList.remove('modal-body', 'modal-fadeOut');
}
}}
new Modal();
};
export default modal2;
I am sorry if this is going to be a bit long.
I have two ul elements on my page that contain a list that is made to look like a subway map.
For the first ul, I made a class inside the li elements, which fetch json data that pops up in a modal.
This modal WORKS!
The problem is my SECOND modal.
Here I have a simple button, and when I click that button I want a modal to pop up, containing my second ul element.
The problem is that I can´t get THIS modal to work.
I renamed everything, both in the html, css and script, and I am going crazy because I can´t find the error. I need this project finished tonight, so I am getting more and more frustrated about it
My test page is here: http://handig.dk/EXAM2019/index.html
The second section contains my working modal.
The third contains the non-working modal.
The html
`<div class="map">
<ul>
<li id="getTime" class="js-modal" data-modal="modal-1">Text</li>
<li id="getTime2" class="js-modal" data-modal="modal-1">Text</li>
<li id="getTime3" class="js-modal" data-modal="modal-1">Text</li>
<li id="getTime4" class="js-modal" data-modal="modal-1">Text</li>
<li id="getTime5" class="js-modal" data-modal="modal-1">Text</li>
<li id="getTime6" class="js-modal" data-modal="modal-1">Text</li>
<li id="getTime7" class="js-modal" data-modal="modal-1">Text</li>
<li id="getTime8" class="js-modal" data-modal="modal-1">Text</li>
<li id="getTime9" class="js-modal" data-modal="modal-1">Text</li>
<li id="getTime10" class="js-modal" data-modal="modal-1">Text</li>
<li id="getTime11" class="js-modal" data-modal="modal-1">Text</li>
<li id="getTime12" class="js-modal" data-modal="modal-1">Text</li>
</ul>
<h2>Text</h2>
<div id="modal-1" class="modal">
<div class="modal-inner">
<div id="modal-body">
</div>
<a class="js-close-modal">×</a>
<div class="modal-content">
<p class="modal-headline">Text</p>
</div>
</div><!-- .modal-content -->
</div><!-- .modal-inner -->
</div><!-- .modal -->
<!-- SECOND MODAL (NOT WORKING) -->
<div class="trains">
<button class="js-modal2" data-modal="modal-2">Tjek driften</button>
<div id="modal-2" class="modal2">
<div class="modal-inner2">
<div id="modal-body2">
</div>
<a class="js-close-modal2">×</a>
<div class="modal-content2">
<p class="modal-headline">Drift</p>
<ul>
<li>Text</li>
<li>Text</li>
<li>Text</li>
<li>Text</li>
<li>Text</li>
<li>Text</li>
<li>Text</li>
<li>Text</li>
<li>Text</li>
<li>Text</li>
<li>Text</li>
<li>Text</li>
</ul>
</div>
</div><!-- .modal-content2 -->
</div><!-- .modal-inner2 -->`
CSS for the WORKING modal
.modal-body {
overflow: hidden;
position: relative;
}
#modal-body {
background:#900;
}
.modal-body:before {
position: fixed;
display: block;
content: '';
top: 0px;
bottom: 0px;
right: 0px;
left: 0px;
background-color: rgba(0, 0, 0, 0.75);
z-index: 10;
}
.modal-body:before {
-webkit-animation: fadeIn 320ms ease;
animation: fadeIn 320ms ease;
transition: opacity ease 320ms;
}
.modal-body.modal-fadeOut:before {
opacity: 0;
}
.modal {
transition: all ease 0.01s;
display: block;
opacity: 0;
height: 0;
position: fixed;
content: '';
top: 0;
left: 0;
right: 0;
z-index: 2000;
text-align: center;
overflow: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.modal.modal-visible {
opacity: 1;
height: auto;
bottom: 0;
}
.modal-inner {
transition: all ease 320ms;
-webkit-transform: translateY(-50px);
transform: translateY(-50px);
position: relative;
display: inline-block;
background-color: #fff;
width: 90%;
max-width: 600px;
background: #fff;
opacity: 0;
margin: 40px 0;
border-radius: 4px;
box-shadow: 0 30px 18px -20px #020202;
}
.modal-inner.modal-reveal {
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
.js-close-modal {
transition: color 320ms ease;
color: #9e9e9e;
opacity: 0.75;
position: absolute;
z-index: 2;
right: 0px;
top: 0px;
width: 30px;
height: 30px;
line-height: 30px;
font-size: 20px;
cursor: pointer;
text-align: center;
}
.js-close-modal:hover {
color: #000;
}
.modal-headline {
font-size: 1.5em;
font-weight: 700;
}
.modal-content {
letter-spacing: 1px;
}
.modal-content2 {
letter-spacing: 1px;
}
CSS for modal that does NOT work
.modal-body2 {
overflow: hidden;
position: relative;
}
#modal-body2 {
background:#900;
}
.modal-body2:before {
position: fixed;
display: block;
content: '';
top: 0px;
bottom: 0px;
right: 0px;
left: 0px;
background-color: rgba(0, 0, 0, 0.75);
z-index: 10;
}
.modal-body2:before {
-webkit-animation: fadeIn 320ms ease;
animation: fadeIn 320ms ease;
transition: opacity ease 320ms;
}
.modal-body2.modal2-fadeOut:before {
opacity: 0;
}
.modal2 {
transition: all ease 0.01s;
display: block;
opacity: 0;
height: 0;
position: fixed;
content: '';
top: 0;
left: 0;
right: 0;
z-index: 2000;
text-align: center;
overflow: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.modal2.modal2-visible {
opacity: 1;
height: auto;
bottom: 0;
}
.modal-inner2 {
transition: all ease 320ms;
-webkit-transform: translateY(-50px);
transform: translateY(-50px);
position: relative;
display: inline-block;
background-color: #fff;
width: 90%;
max-width: 600px;
background: #fff;
opacity: 0;
margin: 40px 0;
border-radius: 4px;
box-shadow: 0 30px 18px -20px #020202;
}
.modal-inner2.modal2-reveal {
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
.js-close-modal2 {
transition: color 320ms ease;
color: #9e9e9e;
opacity: 0.75;
position: absolute;
z-index: 2;
right: 0px;
top: 0px;
width: 30px;
height: 30px;
line-height: 30px;
font-size: 20px;
cursor: pointer;
text-align: center;
}
.js-close-modal2:hover {
color: #000;
}
Script for modal that WORKS
const modal = () => {
'use strict';
class Modal {
constructor() {
this.triggers = document.querySelectorAll('.js-modal');
this.close = document.querySelectorAll('.js-close-modal');
this.modals = document.querySelectorAll('.modal');
this.modalInners = document.querySelectorAll('.modal-inner');
this.listeners();
}
listeners() {
window.addEventListener('keydown', this.keyDown);
this.triggers.forEach(el => {
el.addEventListener('click', this.openModal, false);
});
this.modals.forEach(el => {
el.addEventListener('transitionend', this.revealModal, false);
el.addEventListener('click', this.backdropClose, false);
});
this.close.forEach(el => {
el.addEventListener('click', Modal.hideModal, false);
});
this.modalInners.forEach(el => {
el.addEventListener('transitionend', this.closeModal, false);
});
}
keyDown(e) {
if (27 === e.keyCode && document.body.classList.contains('modal-body')) {
Modal.hideModal();
}
}
backdropClose(el) {
if (!el.target.classList.contains('modal-visible')) {
return;
}
let backdrop = el.currentTarget.dataset.backdrop !== undefined ? el.currentTarget.dataset.backdrop : true;
if (backdrop === true) {
Modal.hideModal();
}
}
static hideModal() {
let modalOpen = document.querySelector('.modal.modal-visible');
modalOpen.querySelector('.modal-inner').classList.remove('modal-reveal');
document.querySelector('.modal-body').addEventListener('transitionend', Modal.modalBody, false);
document.body.classList.add('modal-fadeOut');
}
closeModal(el) {
if ('opacity' === el.propertyName && !el.target.classList.contains('modal-reveal')) {
document.querySelector('.modal.modal-visible').classList.remove('modal-visible');
}
}
openModal(el) {
if (!el.currentTarget.dataset.modal) {
console.error('No data-modal attribute defined!');
return;
}
let modalID = el.currentTarget.dataset.modal;
let modal = document.getElementById(modalID);
document.body.classList.add('modal-body');
modal.classList.add('modal-visible');
}
revealModal(el) {
if ('opacity' === el.propertyName && el.target.classList.contains('modal-visible')) {
el.target.querySelector('.modal-inner').classList.add('modal-reveal');
}
}
static modalBody(el) {
if ('opacity' === el.propertyName && el.target.classList.contains('modal') && !el.target.classList.contains('modal-visible')) {
document.body.classList.remove('modal-body', 'modal-fadeOut');
}
}}
new Modal();
};
export default modal;
Script for modal that does NOT WORK
const modal2 = () => {
'use strict';
class Modal2 {
constructor() {
this.triggers = document.querySelectorAll('.js-modal2');
this.close = document.querySelectorAll('.js-close-modal2');
this.modals = document.querySelectorAll('.modal2');
this.modalInners = document.querySelectorAll('.modal-inner2');
this.listeners();
}
listeners() {
window.addEventListener('keydown', this.keyDown);
this.triggers.forEach(el => {
el.addEventListener('click', this.openModal2, false);
});
this.modals.forEach(el => {
el.addEventListener('transitionend', this.revealModal2, false);
el.addEventListener('click', this.backdropClose, false);
});
this.close.forEach(el => {
el.addEventListener('click', Modal2.hideModal2, false);
});
this.modalInners.forEach(el => {
el.addEventListener('transitionend', this.closeModal2, false);
});
}
keyDown(e) {
if (27 === e.keyCode && document.body.classList.contains('modal-body2')) {
Modal2.hideModal2();
}
}
backdropClose(el) {
if (!el.target.classList.contains('modal2-visible')) {
return;
}
let backdrop = el.currentTarget.dataset.backdrop !== undefined ? el.currentTarget.dataset.backdrop : true;
if (backdrop === true) {
Modal2.hideModal2();
}
}
static hideModal2() {
let modalOpen = document.querySelector('.modal2.modal2-visible');
modalOpen.querySelector('.modal-inner2').classList.remove('modal2-reveal');
document.querySelector('.modal-body2').addEventListener('transitionend', Modal2.modalBody2, false);
document.body.classList.add('modal2-fadeOut');
}
closeModal2(el) {
if ('opacity' === el.propertyName && !el.target.classList.contains('modal2-reveal')) {
document.querySelector('.modal2.modal2-visible').classList.remove('modal2-visible');
}
}
openModal(el) {
if (!el.currentTarget.dataset.modal) {
console.error('No data-modal attribute defined!');
return;
}
let modal2ID = el.currentTarget.dataset.modal2;
let modal2 = document.getElementById(modal2ID);
document.body.classList.add('modal-body2');
modal.classList.add('modal2-visible');
}
revealModal2(el) {
if ('opacity' === el.propertyName && el.target.classList.contains('modal2-visible')) {
el.target.querySelector('.modal-inner2').classList.add('modal2-reveal');
}
}
static modalBody2(el) {
if ('opacity' === el.propertyName && el.target.classList.contains('modal2') && !el.target.classList.contains('modal2-visible')) {
document.body.classList.remove('modal-body2', 'modal2-fadeOut');
}
}}
new Modal2();
};
export default modal2;
in your javascript, this.triggers refer to your button but in your function openModal the variable modal2ID is undefined because your button doesn't have dataset.modal2

Categories

Resources