make timeline-slider looping - javascript

I am wondering how to loop this auto-slide function? Its for this timeline here: https://bm-translations.de/#sprachrichtungen
I tried with if condition to check if next slide has items, but maybe because I am a beginner, its not working.
function timeLineAutoPlay() {
var current = 0;
setInterval(function() {
current++;
var current_elem = $($('.events a')[current - 1]);
if (current == 11) {
timeLineAutoPlay();
}
$($('.events a')[current]).trigger('click');
if ((current + 1) % 4 == 0) {
$('.next').trigger('click');
}
}, 8000);
}
timeLineAutoPlay();
Slider is frome here: https://codyhouse.co/gem/horizontal-timeline/
2nd problem:
If I click on a date, the next autoslide isnt the date after. Do you know how to adjust this code?
Tried this, but its not working:
timelineComponents['eventsWrapper'].on('click', 'a', function(event){
current = items.length;
}

The modulo operator is useful for wrapping at a specific number and creating a loop:
Edit: Included example for manual slider control.
Edit 2: Fixed typo in function call
// The interval id for the auto-slider if currently running.
var intervalId;
// The current slide index
var current = 0;
// Query document for the DOM nodes
var items = $('.events a');
// Clicks on `a` will bubble up to their `li` element and then to `div.events`.
// We can use this to listen to the click on li and then see which one was
// targeted.
$('.events').on('click', 'li', function(event) {
// If the third `li` was clicked, there are 2 in front of it so 2 is the index
// of the item that was clicked.
var count = $(this).prevAll('li').length;
// Update the current item based on the clicked element.
current = count;
// Reset the interval so the next slide does not occur immediately after the
// user has clicked.
if (intervalId != null) {
// Clear previous auto-play
clearInterval(intervalId);
// And start a new one which will first trigger 8000ms from now.
intervalId = timeLineAutoPlay();
}
});
function timeLineAutoPlay() {
// Return the interval id so that we can can cancel it later.
return setInterval(function() {
// Increment `current` but wrap at `items.length`, thus looping through the
// list of items.
//
// E.g. for 4 items; items.length === 4:
// current : 0 -> 1 -> 2 -> 3 -> 0 -> 1 -> ...
current = (current + 1) % items.length;
// Get the item at the current index
var item = items.eq(current);
// This would lead to multiple interval loops being spawned whenever we
// reach 11. That seems unintentional.
//
// if (current == 11) {
// timeLineAutoPlay();
// }
item.trigger('click');
}, 8000);
}
// Start the initial auto-slide and store the id
intervalId = timeLineAutoPlay();

Something like the following:
setInterval(function(){
var current_elem = $($('.events a')[current]);
if(!current_elem.hasClass("selected")){
$(".events a").each( function(index, elem) {
if($(this).hasClass("selected")){
current = index;
}
});
}
current++;
if(current >= $('.events a').length) {
current = 0;
}
$($('.events a')[current]).trigger('click');
}, 7000);

Related

How to update a variable to use in another scope

I have a variable let second = 20 that i make 1 lower until it hits 0. When it hits 0 i want to stop running a part of my code but the variable second is always 20 when i use it because i make it lower in another scope. Sorry if my explenation is a bit unclear.
Here is the code:
votingEnd = document.querySelector(".imposters__voting");
imposters = document.querySelectorAll(".imposter");
let second = 20;
window.addEventListener("load", function () {
let myinterval;
myinterval = setInterval(function () {
second--;
if (second < 11) {
votingEnd.style.color = "red";
}
votingEnd.innerHTML = `Voting ends in: ${second}s`;
if (second == 0) {
clearInterval(myinterval);
votingEnd.innerHTML = `Voting has ended`;
}
}, 1000);
});
if (second > 0) {
//second is still 20 here because i lowered it in my function above. How can i solve this
for (let i = 0; i < imposters.length; i++) {
imposters[i].addEventListener("click", function () {
let count = 0;
while (count < imposters.length) {
imposters[count++].classList.remove("voted");
}
this.classList.add("voted");
});
}
}
You could put the if (second > 0) inside the click function that way it will check for the most recent value of second instead of just once on load like so
for(let i = 0; i < imposters.length; i++){
imposters[i].addEventListener("click", function () {
if (second > 0) {
let count = 0;
while (count < imposters.length) {
imposters[count++].classList.remove("voted");
}
this.classList.add("voted");
}
});
The problem has nothing to do with scope. It has to do with timing. That last part of your code only runs once, before the interval runs twenty times.
Here's the order of operations:
Initialize second to 20.
Bind the countdown function to window.onload. (This does not run yet)
Check if seconds is greater than 0, and it is because the intervals haven't run yet. This is the only time this code ever runs.
window.onload is triggered, and your countdown begins
one second later, seconds is now 19
19 seconds later seconds is not 0, and the interval is cleared.
So what you need to do is trigger your code in each iteration of the interval.
You want something closer to:
let second = 20;
window.addEventListener("load", function () {
const myinterval = setInterval(function () {
second--;
// other logic here...
if (second > 0) {
countdownTick(); // runs every second with the interval handler
}
if (second == 0) {
clearInterval(myinterval);
// cleanup
}
}, 1000);
});
function countdownTick() {
// Do the stuff you need to do each second here
}
Your setInterval runs every second. That does not mean that the rest of the code also runs every second. Which is why second is still 20 when the code gets to if (second > 0).
So you need to make sure that this part of your code runs every second as well. One solution would be to wrap that code inside a function which you call inside the interval, like this:
votingEnd = document.querySelector(".imposters__voting");
imposters = document.querySelectorAll(".imposter");
let second = 20;
window.addEventListener("load", function () {
let myinterval;
myinterval = setInterval(function () {
second--;
if (second < 11) {
votingEnd.style.color = "red";
}
votingEnd.innerHTML = `Voting ends in: ${second}s`;
if (second == 0) {
clearInterval(myinterval);
votingEnd.innerHTML = `Voting has ended`;
}
check();
}, 1000);
});
function check() {
if (second > 0) {
//second is still 20 here because i lowered it in my function above. How can i solve this
for (let i = 0; i < imposters.length; i++) {
imposters[i].addEventListener("click", function () {
let count = 0;
while (count < imposters.length) {
imposters[count++].classList.remove("voted");
}
this.classList.add("voted");
});
}
}
}

Set correct timer for slideshow

I'm new to this and can't find a way to do it.
I have 4 images but can't set a rule that moves changes correctly that slideshow image.
Example: 1 - 2 - 3 - 4, if I go from 1 and click to go to 3, timer makes me go to 2 because it was in 1.
onload = start;
function start(){
var i = 1;
function Move(){
i = (i%4)+1;
document.getElementById('i'+i).checked = true;
}
setInterval(Move,10000);
}
You need to keep the current element index out of the start function and change it when you select an element manually:
onload = start;
var currIndex = 1;
function start() {
function Move(){
currIndex = (currIndex % 4) + 1;
document.getElementById('i' + currIndex).checked = true;
}
setInterval(Move,10000);
}
// better add a class here for simpler selector
document.querySelectorAll('#i1, #i2, #i3, #i4').addEventListener('whatever-event-you-get-on-selection', function() {
currIndex = yourSelectedId; // use data attributes or something for easier index extraction
});

How to get randomly generated array to reset in simple memory game with jQuery

I'm sorry if this has been asked before,
I've searched through Stackoverflow but couldn't find anything that answered my problem.
I'm building a simple memory game, an online version of Simon, when you click the "Start" button it runs the code below to create a random array (of length 4) out of the four colour buttons.
But when you click "Start" again for the next round it doesn't clear the array, and instead creates a second one, and then checks your input against both telling you you're both right and wrong, or right and right (depending on the random array created out of the buttons).
I've tried buttonsToClick = [] in the else section, but it doesn't reset.
I don't know what I'm missing, I've only been learning JavaScript/jQuery for about a month but I wanted to test my knowledge.
The code snipped:
var score = 0;
$("#score").html(`${score}`);
$("#button5").on("click", function() {
var buttons = document.getElementsByClassName("js-button");
var buttonsToClick = chooseRandomButtons(buttons);
currentButtons = buttonsToClick;
flashButtons(buttonsToClick, 0);
var currentOrder = 0;
$(".js-button").on("click", function() {
var selectedButton = $(this)[0];
var button = currentButtons[0];
if (selectedButton === button) {
currentButtons.splice(button,1);
/*alert("Correct");*/
score += 1;
$("#score").html(`${score}`);
} else {
currentButtons = buttonsToClick;
alert("Wrong. Click 'Start' to try again");
score = 0;
$("#score").html(`${score}`);
}
});
})
function chooseRandomButtons(buttons) {
var buttonsToClick = [];
var maxRandomNumber = buttons.length - 1;
for (var i = 0; i < 4; i++) {
buttonsToClick.push(buttons[randomIntFromInterval(0, maxRandomNumber)]);
}
return buttonsToClick;
}
function randomIntFromInterval(min, max) { // min and max included
return Math.floor(Math.random() * (max - min + 1) + min);
}
function flashButtons(buttonsToClick, index) {
setTimeout(function() {
$(buttonsToClick[index]).fadeOut(500).fadeIn(500);
if (index === buttonsToClick.length - 1) {
return;
}
flashButtons(buttonsToClick, index = index + 1);
}, 1000);
}
welcome to SO.
In general you're doing anything correct with your arrays.
The issue is your event handler.
Every time you click the #button5, which I guess is the start button, you register on all your .js-button a new listener. Since you're not unbinding the old event listeners they're still active.
Since the old event listeners have a reference to your old array, you're basically checking the button against the old game and the new game.
Your solution would be to unregister the old one before registering the new one.
This could be done for example by the .off method.
Your code should then look like this:
var currentOrder = 0;
$(".js-button").off("click").on("click", function() {
var selectedButton = $(this)[0];
var button = currentButtons[0];
if (selectedButton === button) {
currentButtons.splice(button,1);
/*alert("Correct");*/
score += 1;
$("#score").html(`${score}`);
} else {
currentButtons = buttonsToClick;
alert("Wrong. Click 'Start' to try again");
score = 0;
$("#score").html(`${score}`);
}
});
Notice the .off() there.
The documentation about the method could be found here: https://api.jquery.com/off/

Why is second card being flipped by itself in memory game

I created a card matching game with vanilla JS and encountered a bug that I'm struggling to figure out the cause of.
If you flip one card then click the reset icon, the flipped card is turned back over. But when you try flipping one card after having reset the game, a second card, which doesn't have an icon, is flipped by itself.
In my code, the class open changes the card color and triggers an animation, while the class show (when applied to a card--in other cases it's applied to a modal to set it to visible when the game has been won) displays the Font Awesome icon, so I know the class open is being added for the second card that is flipped by itself.
Here's the most relevant JS--I also provide the full JS below. For the CSS and HTML, please view my CodePen.
// Calls startGame() function with user clicks restart icon
restartButton.addEventListener('click', startGame);
function startGame() {
// Shuffles deck
cards = shuffle(cards);
// Removes any existing classes from each card
for (let i = 0; i < cards.length; i++) {
deck.innerHTML = '';
// Empty array literal is being used as a shortcut to expanded version, Array.prototype. getElementsByClassName method was used to create cards variable. Since getElementsByClassName returns an "array-like" like object rather than an array, Array.prototype/[] is needed it use array methods on element(s) selected with it.
[].forEach.call(cards, function(item) {
deck.appendChild(item);
});
// Class 'open' changes the card color and triggers an animation, while 'show' (when applied to a card; in other cases it is applied to the modal) displays the Font Awesome icon
cards[i].classList.remove('show', 'open', 'matching', 'disabled');
}
// Resets number of moves
moves = 0;
counter.innerHTML = moves;
// Resets star rating
for (let i = 0; i < stars.length; i++) {
stars[i].style.color = '#ffd700';
// When function moveCounter() is called, stars is set to display: none after a certain number of moves. (visibility: collapse was original method used to hide stars, but this prevented proper centering of stars in modal)
stars[i].style.display = 'inline';
}
// Resets timer
let second = 0;
let minute = 0;
let hour = 0;
let timer = document.querySelector('.timer');
timer.innerHTML = '0 mins 0 secs';
// Window method that stops setInterval() Window method from executing "myTimer" function every 1 second
clearInterval(interval);
}
Full JavaScript:
let card = document.getElementsByClassName('card');
// Spread operator (new in ES6) allows iterable to expand where 0+ arguments are expected
let cards = [...card];
console.log(cards);
// getElementsByClassName method returns HTMLCollection (or a NodeList for some older browsers https://www.w3schools.com/js/js_htmldom_nodelist.asp), an array-like object on which you can use Array.prototype methods. Added [0] to get the first element matched
let deck = document.getElementsByClassName('card-deck')[0];
let moves = 0;
let counter = document.querySelector('.moves');
// Const cannot be used here in order for star rating to be reset when startGame() is called
let stars = document.querySelectorAll('.fa-star');
let starsList = document.querySelectorAll('.stars li');
let matchingCard = document.getElementsByClassName('matching');
let closeIcon = document.querySelector('.close');
// Using getElementsByClassName instead of querySelector here (there's only one class to select) because querySelector is non-live, i.e., it doesn't reflect DOM manipulation. When the user wins the game, a class ("show") is added to the element with class modal, which is set to visible in CSS, so getElementsByClassName is needed (otherwise the modal remains hidden when the game has been won)
let modal = document.getElementsByClassName('modal')[0];
let openedCards = [];
let second = 0, minute = 0, hour = 0;
let timer = document.querySelector('.timer');
let interval;
const restartButton = document.querySelector('.restart');
const modalPlayAgainButton = document.querySelector('.play-again');
// Shuffle function from http://stackoverflow.com/a/2450976
function shuffle(array) {
let currentIndex = array.length, temporaryValue, randomIndex;
while (currentIndex !== 0) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
// Shuffles cards upon page load
document.body.onload = startGame();
// Calls startGame() function with user clicks restart icon
restartButton.addEventListener('click', startGame);
// Calls reset() function (hides modal and restarts game) with user clicks "play again" button in modal
modalPlayAgainButton.addEventListener('click', reset);
function startGame() {
// Shuffles deck
cards = shuffle(cards);
// Removes any existing classes from each card
for (let i = 0; i < cards.length; i++) {
deck.innerHTML = '';
// Empty array literal is being used as a shortcut to expanded version, Array.prototype. getElementsByClassName method was used to create cards variable. Since getElementsByClassName returns an "array-like" like object rather than an array, Array.prototype/[] is needed it use array methods on element(s) selected with it.
[].forEach.call(cards, function(item) {
deck.appendChild(item);
});
// Class 'open' changes the card color and triggers an animation, while 'show' (when applied to a card; in other cases it is applied to the modal) displays the Font Awesome icon
cards[i].classList.remove('show', 'open', 'matching', 'disabled');
}
// Resets number of moves
moves = 0;
counter.innerHTML = moves;
// Resets star rating
for (let i = 0; i < stars.length; i++) {
stars[i].style.color = '#ffd700';
// When function moveCounter() is called, stars is set to display: none after a certain number of moves. (visibility: collapse was original method used to hide stars, but this prevented proper centering of stars in modal)
stars[i].style.display = 'inline';
}
// Resets timer
let second = 0;
let minute = 0;
let hour = 0;
let timer = document.querySelector('.timer');
timer.innerHTML = '0 mins 0 secs';
// Window method that stops setInterval() Window method from executing "myTimer" function every 1 second
clearInterval(interval);
}
// When called, function toggles open and show classes to display cards. Class 'open' changes the card color and triggers an animation, while 'show' (when applied to a card; in other cases it is applied to the modal) displays the Font Awesome icon.
let displayCard = function() {
this.classList.toggle('open');
this.classList.toggle('show');
this.classList.toggle('disabled');
};
// Adds flipped cards to openedCards array, calls the counter function if two have been flipped, and checks if cards are a match or not
function cardOpen() {
openedCards.push(this);
let len = openedCards.length;
if (len === 2) {
moveCounter();
if (openedCards[0].type === openedCards[1].type) {
matching();
} else {
notMatching();
}
}
}
// When cards match, adds/removes relevant classes and clears the two cards' arrays
function matching() {
openedCards[0].classList.add('matching', 'disabled');
openedCards[1].classList.add('matching', 'disabled');
openedCards[0].classList.remove('show', 'open');
openedCards[1].classList.remove('show', 'open');
openedCards = [];
}
// When cards don't match, adds class "not-matching" to both and calls disable() function (to disable flipping of other cards). After half a second, removes "not-matching" class, calls enable() function (to make flipping cards possible again), and clears the two cards' arrays
function notMatching() {
openedCards[0].classList.add('not-matching');
openedCards[1].classList.add('not-matching');
disable();
setTimeout(function() {
openedCards[0].classList.remove('show', 'open', 'not-matching');
openedCards[1].classList.remove('show', 'open', 'not-matching');
enable();
openedCards = [];
}, 500);
}
// Disables all cards temporarily (while two cards are flipped)
function disable() {
Array.prototype.filter.call(cards, function(card) {
card.classList.add('disabled');
});
}
// Enables flipping of cards, disables matching cards
function enable() {
Array.prototype.filter.call(cards, function(card) {
card.classList.remove('disabled');
for (let i = 0; i < matchingCard.length; i++) {
matchingCard[i].classList.add('disabled');
}
});
}
// Updates move counter
function moveCounter() {
// Increases "moves" by one
moves++;
counter.innerHTML = moves;
// Starts timer after first move (meaning two cards have been flipped)
// TODO: timer only starts after clicking second card; start after clicking first one
if (moves == 1) {
second = 0;
minute = 0;
hour = 0;
startTimer();
}
// Sets star rating based on number of moves. (Note: using display: none for removed stars instead of visibility: collapse, because with visibility: collapse, row is centered as if stars are still present)
if (moves > 8 && moves < 12) {
for (i = 0; i < 3; i++) {
if (i > 1) {
stars[i].style.display = 'none';
}
}
}
else if (moves > 13) {
for (i = 0; i < 3; i++) {
if (i > 0) {
stars[i].style.display = 'none';
}
}
}
}
// Game timer
function startTimer() {
interval = setInterval(function() {
timer.innerHTML = minute + ' mins ' + second + ' secs';
second++;
if (second == 60) {
minute++;
second = 0;
}
if (minute == 60) {
hour++;
minute = 0;
}
}, 1000);
}
// Congratulates player when all cards match and shows modal, moves, time and rating
function congratulations() {
if (matchingCard.length == 16) {
// Window method that stops setInterval() Window method from executing "myTimer" function every 1 second
clearInterval(interval);
let finalTime = timer.innerHTML;
// Shows congratulations modal
modal.classList.add('show');
let starRating = document.querySelector('.stars').innerHTML;
// Shows number of moves made, time, and rating on modal
document.getElementsByClassName('final-moves')[0].innerHTML = moves;
document.getElementsByClassName('star-rating')[0].innerHTML = starRating;
document.getElementsByClassName('total-time')[0].innerHTML = finalTime;
// Adds event listener for modal's close button
closeModal();
}
}
// Closes modal upon clicking its close icon
function closeModal() {
closeIcon.addEventListener('click', function(e) {
modal.classList.remove('show');
startGame();
});
}
// Called when user hits "play again" button
function reset() {
modal.classList.remove('show');
startGame();
}
// Adds event listeners to each card
for (let i = 0; i < cards.length; i++) {
card = cards[i];
card.addEventListener('click', displayCard);
card.addEventListener('click', cardOpen);
card.addEventListener('click', congratulations);
}

MooTools - delay between "each" iterations

I need to set delays between "each" iteration
if (index == 3) {
$$('.check').each(function (el, ind) {
if (ind > 0) {
$$('.check')[ind - 1].addClass('hide');
};
$$('.check')[ind].removeClass('hide'); /*here should come delay*/
});
}
Please advice ;)
As Sergio said, you can use the index in the iteration for a cascading effect.
(function(){
// don't always lookup through DOM, cache el references if they are
// not changing dynamically
var checks = $$('.check'),
index = 3;
// ...
if (index === 3){
checks.each(function(el, i){
// i truthy is enough.
i && checks[i-1].addClass('hide');
// use a 100ms incremental delay for each next el
this.delay(i*100, el, 'hide');
}, Element.prototype.removeClass);
}
}());
the Function.prototype.delay can be applied on Element.prototype.removeClass (used as context for fun).
keep in mind that the above is broken - we can't see all your code or the intent behind it. delaying the applied removeClass will work but it will undo the addClass('hide') later so you'd end up with all elements visible.
As I understand you want to do a kind of highlight. You can use a native setTimeout for that. You have 2 options:
#1 animate all looped elements at the same time
#2 animate looped elements one at a time
option #1
if (index == 3) {
var check = $$('.check'); // cache this, once.
var delay = 1000; // 1 second
check.each(function (el, ind) {
if (ind > 0) {
check[ind - 1].addClass('hide');
};
(function () { // to lock the value of ind
var i = ind;
setTimeout(function () {
check[i].removeClass('hide'); /*here should come delay*/
}, delay);
})();
});
}
option #2
This case is very similar but multiplies the delay time by the index of the iteration and making animation delay/timeout bigger for each loop iteration
if (index == 3) {
var check = $$('.check'); // cache this, once.
var delay = 1000; // 1 second
check.each(function (el, ind) {
if (ind > 0) {
check[ind - 1].addClass('hide');
};
(function () { // to lock the value of ind
var i = ind;
setTimeout(function () {
check[i].removeClass('hide'); /*here should come delay*/
}, delay * i);
})();
});
}

Categories