I am aware Javascript is single-threaded. However I do not understand why the following code does not show/hide a spinner before/after a compute-intensive task.
Code outline:
showSpinner();
computeIntensiveTask();
hideSpinner();
The code (I am using the Bootstrap spinner)
const showSpinner = () => {
const $spinner = $('#spacewalk-spinner').find('.spinner-border');
$spinner.show();
};
const hideSpinner = () => {
const $spinner = $('#spacewalk-spinner').find('.spinner-border');
$spinner.hide();
};
The function computeIntensiveTask() does a ton of sqrt() and other 3D vector math.
When the code runs, the spinner never appears. Why is this happening?
UPDATE 0
As a test I tried simply updated the spinner elements color before/after:
before
document.getElementById('spacewalk-spinner').style.color = 'rgb(255,0,0)';
after
document.getElementById('spacewalk-spinner').style.color = 'rgb(0,255,0)';
and only the 'after' color change took place.
UPDATE 1
To clarify. If I remove the call to hideSpinner() and change showSpinner() to document.getElementById('spacewalk-spinner').style.display = 'block'. The spinner shows after computeIntensiveTask() completes. This despite the fact I have placed computeIntensiveTask() within call to window.setTimeout with a 500 ms wait.
You need to coordinate the updating of the UI by using a setTimeout function. Also you need to position the showSpinner and hideSpinner functions in relation to the updating of the UI. See this snippet as an example.
const showSpinner = () => {
const $spinner = $('#spacewalk-spinner').find('.spinner-border');
$spinner.show();
console.log('show');
};
const hideSpinner = () => {
const $spinner = $('#spacewalk-spinner').find('.spinner-border');
$spinner.hide();
console.log('hide');
};
const computeIntensiveTask = () => {
showSpinner();
// begin the calculations after the UI updates by using setTimeout
setTimeout(function() {
for (var start = 1; start < 1000; start++) {
// calculating...
console.log('calc');
}
hideSpinner();
}, 0);
};
computeIntensiveTask();
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="spacewalk-spinner">
<div class="spinner-border" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
try this:
showSpinner();
setTimeout(() => {
computeIntensiveTask();
hideSpinner();
}, 300);
Related
I am building a React App and I have implemented a slider in my app. By default my slider is automatically but I made a toggle button so the user can stop the automatization at any tine.
Of course I ușe setInterval to make this auto-sliding effect. When I press the toggle first time it stops, but when I start it again and try to stop it another time it doesn't work and the auto-sliding continues when it should stop.
This is my code:
const toggle = document.querySelector(".toggle-container");
const toggleDot = document.querySelector(".toggle");
let autoSliding = true;
let slidingInterval = setInterval(() => {
nextButton.click();
}, 2500);
const stopAutoSliding = () => {
toggleDot.style.left = "10%";
toggle.style.background = "#ccc";
autoSliding = false;
clearInterval(slidingInterval);
}
const restartAutoSliding = () => {
toggleDot.style.left = "calc(90% - 20px)";
toggle.style.background = "#0c46b3";
autoSliding = true;
let slidingInterval = setInterval(() => {
nextButton.click()
}, 2500);
}
toggle.addEventListener("click", () => {
if (autoSliding) {
stopAutoSliding();
} else {
restartAutoSliding();
}
});
nextButton is just a button for which I set an addEventListener method for click events and it just simply makes the slider to move to the next slide.
If you wanna see it working follow this link.
When visiting a web page I want to declare a start time and send an AJAX request when the user leaves the site. It works like expected but when changing the tab or minimizing the browser the start time remains the same as when the user accessed the web page. I thought I could override the start time by declaring it within a function which is fired when the tab is active again but with no success.
Here is my code so far:
$(document).ready(function() {
var starts = Math.ceil(Date.now() / 1000);
//declare new start time when user returns
document.addEventListener('visibilitychange', function() {
if(!document.hidden) {
var starts = Math.ceil(Date.now() / 1000);
}
});
//AJAX
//value of old starts is used here instead of new declared starts
...
});
Does anyone have a solution for this?
Try document.visibilityState === 'visible' so your code will look like this:
$(document).ready(function() {
var starts = Math.ceil(Date.now() / 1000);
//declare new start time when user returns
document.addEventListener('visibilitychange', function() {
if(document.visibilityState === 'visible') {
var starts = Math.ceil(Date.now() / 1000);
}
});
});
Read more about it here:
https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilitychange_event
const timer = document.querySelector("#timer");
window.addEventListener('DOMContentLoaded', () => {
startTimer(); // starttime on load
});
let intervalID = null;
let timerValue = 0;
const startTimer = () => {
intervalID = setInterval(() => {
timerValue++;
timer.innerText = timerValue;
}, 1000);
}
const stopTimer = () => {
clearInterval(intervalID);
}
document.addEventListener('visibilitychange', () => {
if (document.hidden) { // codition for browser tabs is active or minimize
stopTimer(); // stop timer when you leave tab minimize it on
return;
}
startTimer(); // start timer when you comback to tab maximize it
})
<div class="container">
<h2>time spend in seconds:<span id="timer">0</span>
<h2>
</div>
I'm trying to add animation to my website using the Element.animate() method in Javascript but realized that animation events aren't triggered when the animation is ran.
Here is my code:
window.onload = function () {
let list = document.querySelector(".list");
let btn = document.querySelector(".btn");
// animation events
list.addEventListener('animationstart', () => {
console.log('start')
});
list.addEventListener('animationend', () => {
console.log('end');
});
btn.onclick = () => {
list.animate([
// keyframes
{ transform: 'translateY(0px)' },
{ transform: 'translateY(-24px)' }
], {
// timing options
duration: 1000,
fill: "forwards"
});
}
}
In the code above, the animation runs but the animation event is not triggered. Can anyone explain why this is not working or tell me how to fix it?
You are not using a CSS animation (triggered by css classes). The animationstart/end (or transitionstart/end) Events are only fired, if an CSS-defined-animation or -transition is used.
Take a look at the example at HTMLElement: animationstart event.
Instead of the start/end-events, you can access the Animation-instance directly :
btn.onclick = () => {
// animation started
let animation = list.animate(...);
animation.onfinish = () => {
// animation ended
};
};
Animation.onfinish
I am attempting to integrate my keyup function into vBulletin 3 when a user is creating a new thread. The ID of the text area is a variable, but in HTML returns:
<textarea name="message" id="vB_Editor_001_textarea"></textarea>
I have tried replacing the ID in my function with the variable for the ID this also had no effect. my jQuery function is as follows:
function delay(fn, ms) {
let timer = 0
return function() {
clearTimeout(timer)
timer = setTimeout(fn.bind(this), ms || 0)
}
}
$('#vB_Editor_001_textarea').keyup(delay(function (e) {
console.log('Time elapsed!', this.value);
}, 1000));
I have a live working example here.
Within vBulletin the textarea is contained within the editor_toolbar_on template, its raw code looks like:
<textarea name="message" id="{$editorid}_textarea" rows="10" cols="60" style="display:block; width:$stylevar[messagewidth]; height:{$editor_height}px" tabindex="1" dir="$stylevar[textdirection]">$newpost[message]</textarea>
I have attempted placing my script in my footer template (along with jQuery above it). This failed so I went straight to the textarea and placed the script right below the textarea, which also had no effect.
After those unsuccessful attempts I went ahead and placed the entire code right into the template (creating another textarea) upon getting an error for duplicate IDs I numbered this one 2:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
function delay(fn, ms) {
let timer = 0
return function() {
clearTimeout(timer)
timer = setTimeout(fn.bind(this), ms || 0)
}
}
$('#vB_Editor_002_textarea').keyup(delay(function (e) {
console.log('Time elapsed!', this.value);
}, 1000));
</script>
I am not returning any console errors, yet the log never posts.
How can I receive the keypress event from vBulletin's textarea?
As far as I can tell, there appears to be an issue with jQuery somewhere within vBulletin. This pure JS version provides the same functionality as the original post and works: (I would like to hear from others regarding the jQuery method though)
function debounce(fn, duration) {
var timer;
return function(){
clearTimeout(timer);
timer = setTimeout(fn, duration);
}
}
const txt = document.querySelector('#vB_Editor_001_textarea')
const out = document.querySelector('#out')
const status = document.querySelector('#status')
const onReady = () => {
txt.addEventListener('keydown', () => {
out.classList.remove('idle')
out.classList.add('typing')
status.textContent = 'typing...'
})
txt.addEventListener('keyup', debounce(() => {
out.classList.remove('typing')
out.classList.add('idle')
status.textContent = 'idle...'
}, 2000))
}
document.addEventListener('DOMContentLoaded', onReady)
I'm learning event phasing of nested elements so I create small project. Codepen JS starts on 43rd line.
So here's simple nested divs.
<div id="zzz" class="thir">
0
<div id="xxx" class="thir">
0
<div id="sss" class="thir">
0
</div>
</div>
</div>
And here what we do with them.
const ar2 = [zzz, xxx, sss];
ar2.map(e => {
e.addEventListener('click', nestedClick, phase);
})
function nestedClick(e) {
// e.stopPropagation();
const meow = this;
const prevColor = this.style.backgroundColor;
this.style.backgroundColor = '#757575';
window.setTimeout(() => { meow.style.backgroundColor = prevColor}, 500);
}
To visually show how capturing/bubbling works I'd like to change background color and set timeout on each step, wait until it's done and trigger next click with the same strategy.
But here I see after I click on any element event still goes through, changing color and forces all .setTimeout() like at the same time. How can I repair it?
Side question: why e.stopPropagation() works whether it's capturing or bubbling phase?
Thank you for attention!
You need to shift the start time of the timers. And for a flashing effect having a second timer would be good.
let counter = 1;
const ar2 = [...document.getElementsByClassName('thir')];
ar2.map(e => {
e.addEventListener('click', nestedClick);
e.addEventListener('mouseup', function() {
counter = 1;
});
});
function nestedClick(e) {
const prevColor = this.style.backgroundColor;
debugger;
setTimeout( () => {
this.style.backgroundColor = '#757575';
setTimeout( () => {
this.style.backgroundColor = prevColor;
}, 50 * (counter++));
}, 500 * (counter++));
}
<div id="zzz" class="thir">
CLICK ME
<div id="xxx" class="thir">
CLICK ME
<div id="sss" class="thir">
CLICK ME
</div>
</div>
</div>