SetInterval times not running correctly - javascript

I've inherited a bug to look at consisting of two images that "flip" and use set interval to time when this happens.
From what I believe the initial change should happen after 4 seconds, then for each image change every 12 seconds (obviously this isn't important at the minute).
At the moment the first image changes at 4 seconds and the second one at around 8.
I'm also open to any improvements that can be made to this code as well.
//SPINNING LOGO
if ($("#flipLogo").length) {
function spinLogo() {
$('#flipLogo').addClass("active");
$('#flipLogo2').addClass("active");
// console.log("yup yup");
setTimeout(function () {
$('#flipLogo').removeClass("active");
$('#flipLogo2').removeClass("active");
}, 4000);
clearInterval();
}
setInterval(function () {
spinLogo();
clearInterval();
}, 12000);
}

I believe you are looking for $.toggleClass to switch between the active states.
Additionally, as Hacketo said you are calling clearInterval wrong - it should be called with the return value of setInterval if you want to stop the interval (I don't think you want to do this?)
Try this:
//SPINNING LOGO
function spinLogo() {
// Flip the active classes on or off
$('#flipLogo').toggleClass("active");
$('#flipLogo2').toggleClass("active");
}
if ($("#flipLogo").length) {
// after 4 seconds
setTimeout(function() {
// flip the logo
spinLogo();
// Set a timer to flip it again every 12 seconds
setInterval(spinLogo, 12000);
}, 4000);
}
Here it is in action:
//SPINNING LOGO
function spinLogo() {
// Flip the active classes on or off
$('#flipLogo').toggleClass("active");
$('#flipLogo2').toggleClass("active");
}
if ($("#flipLogo").length) {
// after 4 seconds
setTimeout(function() {
// flip the logo
spinLogo();
// Set a timer to flip it again every 12 seconds
setInterval(spinLogo, 12000);
}, 4000);
}
.flippable {
width: 100px;
height: 100px;
border: 1px solid black;
text-align: center;
display: none;
color: white;
}
.flippable.active {
display: block;
}
#flipLogo {
background: blue;
}
#flipLogo2 {
background: green;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="flipLogo" class="flippable active">Logo 1</div>
<div id="flipLogo2" class="flippable">Logo 2</div>
(ps it is bad form to define functions inside if statements, as hoisting will move them them to a place you might not expect)

Related

Unable to animate the icons with Javascript

I am trying to animate 4 icons to simulate wifi signals with Javascript, hower after the last signal is reached
the animation does not restart again.
Here is what i have tried.
I would appreciate any help and idea how to achieve this either with
CSS or Javascript.
function makeSignal(){
let wifi_icon = document.getElementById("wifi-signal");
wifi_icon.classList.add('ri-signal-wifi-line');
setTimeout(function() {
wifi_icon.classList.remove('ri-signal-wifi-line');
wifi_icon.classList.add('ri-signal-wifi-1-line');
}, 500);
setTimeout(function() {
wifi_icon.classList.remove('ri-signal-wifi-1-line');
wifi_icon.classList.add('ri-signal-wifi-2-line');
}, 1000);
setTimeout(function(){
wifi_icon.classList.remove('ri-signal-wifi-2-line');
wifi_icon.classList.add('ri-signal-wifi-3-line');
}, 1500);
}
makeSignal();
setInterval(makeSignal, 2000);
#signal {
display: block;
width: 100px;
height: 100px;
margin: 5px auto;
}
<link href="https://cdn.jsdelivr.net/npm/remixicon#2.5.0/fonts/remixicon.css" rel="stylesheet">
<div id="signal">
<i id="wifi-signal" class="ri-5x"></i>
</div>
Well, I have no idea if this works, but it seems that in the last setTimeout, you use wifi_icon.classList.add('ri-signal-wifi-3-line') and then, at the start of the function (next loop), you never remove that class. So add something like:
let wifi_icon = document.getElementById("wifi-signal");
wifi_icon.classList.remove('ri-signal-wifi-3-line');
wifi_icon.classList.add('ri-signal-wifi-line');

Revealing items on hover, hide them after X seconds

I'm trying to accomplish a "reveal" effect where I show items from a grid when hovering them.
Everything's OK here but once revealed I want to make them disappear again after X seconds – so it's not that when you move the mouse from the item they disappear inmediately.
That's what I tried so far but the items are not going back to their "unrevealed" state after I leave the mouse from the item.
var timeout;
$(".home-box").hover(function () {
clearTimeout(timeout);
$(this).css("opacity", 1);
}, function () {
timeout = setTimeout(function(){
$(this).css("opacity", 0);
},500);
});
Does anyone have any idea about how to solve it?
Thanks in advance.
You should use mouseenter and mouseleave events and add separate functionalities in each.
The reference to this might be lost in the callback function passed to setTimeout.
$(".home-box").mouseenter(function() {
clearTimeout(timeout);
$(this).css("opacity", 1);
});
$(".home-box").mouseleave(function() {
var $element = $(this)
timeout = setTimeout(function(){
$element.css("opacity", 0);
},500);
});
Was it necessary? I used the event mouseover instead of the hover, as the hover will always fire when the mouse moves, even if you try to move the cursor away from the object.
$(".home-box").mouseover(function () {
$('img').css("opacity", 1);
setTimeout(function(){
$('img').css("opacity", 0);
}, 2000);
});
.home-box {
display: flex;
justify-content: center;
align-items: center;
width: 300px;
height: 300px;
border: 1px solid green;
position: relative;
}
img {
opacity: 0;
position: absolute;
height: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="home-box">
hover me pls and wait...
<img src="https://im0-tub-ru.yandex.net/i?id=1a59e5c138260403e2230f0d2b264513&n=13">
</div>
The issue is that this has a different meaning within the setTimeout - you can store the box (this) and reuse it.
var timeout;
$(".home-box").hover(function() {
clearTimeout(timeout);
$(this).css("opacity", 1);
}, function() {
var box = this;
timeout = setTimeout(function() {
$(box).css("opacity", 0);
}, 500);
});

Recursive setTimeout in eventListener function (drum sequencer)

I'm trying to fire a drum sequence on the click of a button using an event listener. Inside the event listener I iterate through the drum pattern using forEach and delay the playback of each (selected) iteration using setTimeout. The sequencer runs through one time and then stops. What I can't seem to figure out is how to structure the code block so that it continues to loop until the user clicks stop.
Here is what I have so far.
btn.addEventListener('click', () => {
cells.forEach((cell, i) => {
setTimeout(() => {
if (cell.classList.contains('selected')) {
cell.childNodes[1].play();
}
}, i * 500);
});
});
I've tried using a recursive function that was mentioned in another post by wrapping the forEach method inside another function and then recalling that function before the event listener function closes. I've also tried firing two separate setTimeout functions, the second of which recalls the function that is wrapped around the forEach method.
Any help would be greatly appreciated.
Thanks!
this might not be exactly what you are after, but I would strongly recommend moving this code into an ES6 class.
The main issue with what you have at the moment is that you don't know how many times the cells array is going to be looped over when the click event is fired. Your code essentially sets all of the timeouts at the start (think about setting 4 separate timers, one starting from 500 another from 1000, 1500, 2000). If you wanted this looping to be able to occur indefinitely, you would need an indefinite amount, or perhaps an individual timer for each beat that resets when it reaches the end.
Instead, I think a better solution is to store a variable that indicates the current position in the sequencer and sets a timeout that increments this variable after x amount of time, and resets back to the start once it reaches the end. This way you have only one timer that is counting down the time till the next beat. Once it reaches 0 it plays the next note and restarts.
class Sequencer {
// playButton = btn;
cells = [1,2,3,4]; // you would want to set this to your cells array
currentBeatIndex = 0;
isPlaying = false;
constructor(msPerBeat) {
this.msPerBeat = msPerBeat;
// this.playButton.addEventListener('click', () => this.toggleStartStop())
}
toggleStartStop() {
if (this.isPlaying) {
this.stop();
} else {
this.start();
}
}
stop() {
this.isPlaying = false;
// Could also set this.currentBeatIndex = 0 here if you wanted to return to the first beat when stopped.
}
start() {
this.isPlaying = true;
this.playCurrentNoteAndSetTimeout();
}
playCurrentNoteAndSetTimeout() {
if (this.isPlaying) {
console.log(this.cells[this.currentBeatIndex]); // use your .play() method here
setTimeout(() => {
this.toNextBeat();
this.playCurrentNoteAndSetTimeout(); // this is where your reccursion happens.
}, this.msPerBeat)
}
}
toNextBeat() {
this.currentBeatIndex = ++this.currentBeatIndex % this.cells.length; // mod operator here allows us to loop back to beginning of array when we reach the end.
}
}
const sequencer = new Sequencer(500) // 120 bpm
Try pasting this into your console and running sequencer.start() and then sequencer.stop()
As a sidenote, I'd recommend watching this video about the event loop. I found it really helpful with understanding timeouts.
I think setInterval and clearInterval will probably do what you need.
Here I have a play method on my cells which simply pulses the circle over 500 ms, and I do something similar to your loop in runPattern. I call this function once to start, and then call setInterval(runPattern, /*time*/). Storing the result of that value in the variable interval, I can then later stop the interval by calling clearInterval(interval).
Note that stop does not stop the current sequence, only preventing the next one from beginning. While we could change that, it's much more invasive to the existing code.
const handler = (() => {
const [red, blue, green, yellow] = ['b1', 'b2', 'b3', 'b4'].map(
(id, _, __, node = document.getElementById(id)) => ({
play: () => {
node.classList.add('pulse')
setTimeout(() => node.classList.remove('pulse'), 500)
}
})
)
const cells = [red, green, red, blue, yellow, blue]
const runPattern = () => {
cells.forEach((cell, i) => {
setTimeout(() => {
cell.play()
}, i * 500);
});
}
let running = false;
let interval= null
return {
start: () => {
if (!running) {
running = true
runPattern()
interval= setInterval(runPattern, 500 * cells.length + 1000)
}
},
stop: () => {
running = false
clearInterval(interval)
}
}
})()
document.getElementById('start').addEventListener('click', handler.start)
document.getElementById('stop').addEventListener('click', handler.stop)
#balls {position: relative}
span {margin: 50px; position: absolute; top: 0; width: 40px; height: 40px; border-radius: 50%; cursor: pointer; border: 1px solid #999}
.pulse {animation: pulse .5s;}
#b1 {background: #f00; left: 20px;}
#b2 {background: #00f; left: 90px;}
#b3 {background: #0f0; left: 160px}
#b4 {background: #ff0; left: 230px}
#keyframes pulse {
0% {width: 40px; height: 40px; margin: 50 50 50}
50% {width: 60px; height: 60px; margin: 30 40 60}
100% {width: 40px; height 60px; margin: 50 50 50}
}
<button id="start">Start</button>
<button id="stop">Stop</button>
<div id ="balls">
<span id="b1"></span>
<span id="b2"></span>
<span id="b3"></span>
<span id="b4"></span>
</div>

Trying to create an alternating image banner but it is not working correctly

I have three images in a banner all set with position: absolute; and layered on top of one another. What I was hoping to do was use the setTimeout() and fadeOut() and fadeIn() methods to alternate between the three different images being displayed. Something is happening, but not really what I wanted. Instead of a smooth transition between each image, there is simply a choppy toggle back and forth between the last image and the second, the first never appearing at all. Here is the code I am using:
setInterval(function()
{
setTimeout($("#banner_city").fadeOut(), 5000);
setTimeout($("#banner_dispatch").fadeOut(), 5000);
setTimeout($("#banner_vehicles").fadeOut(), 5000);
setTimeout($("#banner_city").fadeIn(), 5000);
setTimeout($("#banner_dispatch").fadeIn(), 5000);
setTimeout($("#banner_vehicles").fadeIn(), 5000);
}, 32401);
I'm not sure if it's relevant but just in case here is the html and the css as well. HTML:
<div id="banner">
<img src="images/banner/city.jpg" id="banner_city" />
<img src="images/banner/dispatch.jpg" id="banner_dispatch" />
<img src="images/banner/vehicles.jpg" id="banner_vehicles" />
</div>
Css:
#banner {
margin-top: 12px;
position: relative;
width: 833px;
height: 237px;
}
#banner_city {
position: absolute;
right: 0;
top: 0;
width: 833px;
height: 237px;
}
#banner_dispatch {
position: absolute;
right: 0;
top: 0;
width: 833px;
height: 237px;
}
#banner_vehicles {
position: absolute;
right: 0;
top: 0;
width: 833px;
height: 237px;
}
Would anyone be able to shed some light on why this is behaving in such a buggy way? Thanks very much in advance.
Your setInterval is basically telling everything to fade-out and fade-in at the same time, which isn't what you want.
There's two approaches here: have 3 separate intervals for fading each "step", or have 1 interval with some internal counter for keeping track of what to show.
Here's the first approach:
function fadeOne() {
$("#banner_city").fadeOut();
$("#banner_dispatch").fadeIn();
};
function fadeTwo() {
$("#banner_dispatch").fadeOut();
$("#banner_vehicles").fadeIn();
}
function fadeThree() {
$("#banner_vehicles").fadeOut();
$("#banner_city").fadeIn();
}
fadeOne();
setInterval(fadeOne, 15000);
setTimeout(() => {
fadeTwo();
setInterval(fadeTwo, 15000);
}, 5000);
setTimeout(() => {
fadeThree();
setInterval(fadeThree, 15000);
}, 10000);
This is effectively three functions, each being called 15 seconds apart, with a 5/10 second delay on the second and third functions.
The second approach, which combines them together:
let count = 0;
function fade() {
switch (count++ % 3) {
case 0:
fadeOne();
return;
case 1:
fadeTwo();
return;
case 2:
fadeThree();
return;
}
}
fade();
setInterval(fade, 5000);
Your code starts all the timeouts at the same time, so they all complete at the same time. You'd have to nest them to make them to execute after each other, like this:
setTimeout(function() {
// Do something
// ...
setTimeout(function() {
// Do something else
// ...
setTimeout(...)
}, 1000)
}, 1000)
This is pretty messy, so here's another approach that should do what you need:
// Get all the image elements
var images = $(".banner-image");
// Hide all the images
images.hide();
// Start the cycle
cycle();
setInterval(cycle, 1000);
var index = 0;
function cycle() {
// Fade out all images
images.fadeOut();
// Fade in the next image
images.eq(index).fadeIn();
// Increment the index
index++;
// Reset index after cycling through all images
if ((index % images.length) === 0) index = 0;
}
This code will adapt to however many image elements you include in your HTML. Here it is running on a live demo.
PS: You can use CSS Classes to apply the same styles to multiple HTML elements.

How to show divs when the mouse moves anywhere on screen, not just the element itself?

I managed to hide and show my classes when the user moves his mouse over the specific element. But what I would actually like is that these show when the user moves his mouse anywhere on the screen, not just the selected div's.
This is my current code:
$(window).on('mousemove', function () {
$('.barhide').addClass('show');
try {
clearTimeout(timer);
} catch (e) {}
timer = setTimeout(function () {
$('.barhide').removeClass('show');
}, 1000);
});
And my css:
.barhide {
background: #333;
color: #fff;
display: block;
opacity: 0;
transition: all 1.5s ease;
}
.barhide.show {
opacity: 1;
display: none;
}
So what I would like is that after 3 seconds, the classes with .barhide get hidden and if the user moves his mouse anywhere in screen, they show up again, instead of just when they move over the element.
Also I was wondering if it's not a lot easier to do these things with React?
I have restructured the code a bit and added some comments explaining what's happening and when. Also, lose the try since attempting to clear a timer will never throw an exception.
Keep in mind that mouseover type events are an issue on mobile devices. These two articles may help in that regard:
JQuery's Virtual Mouse Events
Simulated Mouse Events using JQuery
$(function(){
// When page loads, wait 3 seconds and hide all elements with .barhide class:
setTimeout(toggle, 3000);
});
var timer = null;
// General function for adding/removing the "hide" class.
// This is used when the page first loads and each time
// the mouse moves on the page. We're not calling toggle()
// here because a flicker effect can happen which would leave
// the elements showing instead of being hidden.
function toggle(){
$('.barhide').toggleClass('hide');
}
$(window).on('mousemove', function(){
// When anywhere on page is moused over bring back .barhide
// elements for 3 seconds. Removing "hide" simply restores
// the original CSS & layout
$('.barhide').removeClass('hide');
// Kill any previous timers
clearTimeout(timer);
// Wait 3 seconds and hide again
timer = setTimeout(toggle, 3000)
});
.barhide { background-color:blue; }
.hide { display:none; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="barhide">ONE</div>
<div class="show">TWO</div>
You just count the nr of timers running and when the last finishes you hide the bar.
var count = 0;
$(window).mousemove(function( event ) {
$('.barhide').show();
count += 1;
setTimeout(function() {
if (count == 1) {
$('.barhide').hide();
}
count -= 1;
}, 3000);
});

Categories

Resources