Is this a Javascript blocking / async scenario? - javascript

The user clicks the map and it calls clickMap, the DIV#calculating is displayed, the map calcs are done and then in updateResults the DIV#calculating is hidden.
However the DIV is never shown on screen.
The console log shows
block 9775.199999996403
none 10517.899999998917
The delay between show and hide maybe from 100ms up to 5000ms, no matter what I never see the DIV.
function clickMap(e) {
if (myAStar.nodesPosition[e.offsetY][e.offsetX]) {
document.getElementById("calculating").style.display = "block";
console.log(document.getElementById("calculating").style.display, performance.now());
myAStar = new AStarPathFinder(mapW, mapH);
path = [];
myAStar.setStart(0, 0);
myAStar.setDestination(e.offsetX, e.offsetY);
myAStar.wh = document.getElementById('hWeight').value;
myAStar.loadMap(map);
findPath();
drawMap();
updateResults();
} else {
mapCanvas.classList.add("oops");
setTimeout(() => {
mapCanvas.classList.remove("oops");
}, 500);
}
}
function updateResults() {
document.getElementById("calculating").style.display = "none";
console.log(document.getElementById("calculating").style.display, performance.now());
}

Related

Trigger rendering of all `content-visibility: auto` elements BEFORE a user scrolls to them without blocking main thread for >50ms

I want to have a fast page-load but also not have the user see a delay before content is rendered while scrolling down the page.
I have a large html page with many <dl> elements.
To speed up page load, I have set content-visibility: auto in css. See https://web.dev/content-visibility/
dl {
content-visibility: auto;
contain-intrinsic-size: 1000px;
}
Due to the complexity of the contents of the <dl>s there is a noticeable delay when a user scrolls while the <dl>s are rendered as they come into the viewport.
Thus, soon after page-load I want to render all the offscreen <dl> straight away (before a user scrolls to them) but in such a way that it does not block the main thread and scrolling remains responsive.
So, I want to set content-visibility: visible on the <dl>s starting from the top one, and not blocking the main thread (for more than say 50ms). So, maybe allowing user interaction after rendering each <dl>.
So, I need a version of the below, that doesn't block the main thread:
document.querySelectorAll('dl').forEach(function(dlElement, currentIndex, listObj) { dlElement.style['content-visibility'] = 'visible' });
My use case: My page is of math notes, which I want all on one page to reduce friction. I use katex which (for now, before we can use mathml on chrome) produces very large and complex html, which even server-side rendered still takes a lot of time for layout and rendering on the browser.
Rather than leave this unanswered, let me paste my (unperfected) code I have been testing for the last few weeks.
// --------- Shim requestIdleCallback if not supported in browser ----------------------------------
window.requestIdleCallback =
window.requestIdleCallback ||
function (cb) {
var start = Date.now();
return setTimeout(function () {
cb({
didTimeout: false,
timeRemaining: function () {
return Math.max(0, 50 - (Date.now() - start));
}
});
}, 1);
}
window.cancelIdleCallback =
window.cancelIdleCallback ||
function (id) {
clearTimeout(id);
}
// Global
let isRequestIdleCallbackScheduled = false;
let nodeToRemove = null;
let isVisualUpdateScheduled = false;
let totalDlElementsLeftToShow = 0;
function startIfNeeded() {
totalDlElementsLeftToShow = document.querySelectorAll('dl:not([style*="content-visibility: visible;"]), .ra:not([style*="content-visibility: visible;"]').length;
if (totalDlElementsLeftToShow > 0) {
console.log('Not a mobile - Let\'s make things visible when we are next idle');
scheduleVisibilityChanges();
}
else {
console.log('Apparently, all visible');
}
}
function scheduleVisibilityChanges() {
// Only schedule the rIC if one has not already been set.
if (isRequestIdleCallbackScheduled) {
//console.log('returning because idle callback scheduled');
startIfNeeded();
}
isRequestIdleCallbackScheduled = true;
//console.log('scheduling visibility changes when next idle or in 30 seconds at the latest');
requestIdleCallback(processHiddenElements, { timeout: 30000 });
}
function processHiddenElements(deadline) {
// Since our operation takes a while, we only want to go ahead if we have at least 50ms.
while (deadline.timeRemaining() > 49 && totalDlElementsLeftToShow > 0) {
// console.log('time remaining is ', deadline.timeRemaining(), '- scheduling next visual update');
// Don't set content-visibility immediately wait for the next
// requestAnimationFrame callback.
scheduleVisualUpdateIfNeeded();
}
// console.log('Deadline reached, will check again the next time the user is idle if there are more events still to send');
if (totalDlElementsLeftToShow > 0) {
requestIdleCallback(processHiddenElements, { timeout: 30000 });
}
}
function scheduleVisualUpdateIfNeeded() {
if (isVisualUpdateScheduled) {
// console.log('returning - visual update already scheduled')
return;
};
isVisualUpdateScheduled = true;
// console.log('requesting animation frame');
requestAnimationFrame(setContentToVisible);
}
function setContentToVisible() {
// console.log('changing visibility of element ');
let completeHiddenNodeList = document.querySelectorAll('dl:not([style*="content-visibility: visible;"]), .ra:not([style*="content-visibility: visible;"]');
// We chunk the layout changes
let i;
let numberToChunk = 20;
if (completeHiddenNodeList.length < 20) {
numberToChunk = completeHiddenNodeList.length
}
for (i = 0; i < numberToChunk; ++i) {
completeHiddenNodeList[i].style.contentVisibility = 'visible';
}
isVisualUpdateScheduled = false;
isRequestIdleCallbackScheduled = false;
totalDlElementsLeftToShow = totalDlElementsLeftToShow - numberToChunk;
}
if (!navigator.userAgentData.mobile) {
startIfNeeded();
}

integrating SetTimeOut function after button click

I have created a snippet of code that changes the state from display:block to display:none by using an onClick element. My goal is to delay the change in state, for a few seconds whilst an animation effect occurs.
This snippet of code below is what I am currently using to change the state of the element, but unsure on how to incorporate a delay.
Any advice or suggestions is greatly appreciated.
Thanks,
Ant
function showDiv1(elem) {
var divsToCheck = ["close","Holder"]; // Add to here to check more divs
for (var i = 0; i < divsToCheck.length; i++) {
if (divsToCheck[i] == elem) {
document.getElementById(divsToCheck[i]).style.display = "block";
} else {
document.getElementById(divsToCheck[i]).style.display = "none";
}
}
}
Put the code you want to be delayed inside an anonymous function, like so:
function showDiv1(elem) {
var divsToCheck = ["close","Holder"]; // Add to here to check more divs
for (var i = 0; i < divsToCheck.length; i++) {
if (divsToCheck[i] == elem) {
setTimeout(function() {
document.getElementById(divsToCheck[i]).style.display = "block";
}, 500);
} else {
setTimeout(function() {
document.getElementById(divsToCheck[i]).style.display = "none";
}, 500);
}
}
}
Here, 500 means delaying by 500 ms. You can change this to whatever amount of time (in milliseconds) that you need.
You should call a function in your loop that takes care of the setTimeout and hiding/showing instead of calling the timeout function in a loop. (Won't work because i is no longer available). See here: https://jsbin.com/refodobaca/1/edit?html,css,js,output
function showDiv1(elem) {
var divsToCheck = ["close","Holder"]; // Add to here to check more divs
for (var i = 0; i < divsToCheck.length; i++) {
if (divsToCheck[i] == elem) {
showHideDiv(divsToCheck[i], true);
} else {
showHideDiv(divsToCheck[i], false);
}
}
}
function showHideDiv(elem, bShow) {
var timeoutSeconds = 3;
setTimeout(function() {
document.getElementById(elem).style.display = (bShow ? "block" : "none");
}, timeoutSeconds * 1000);
}
showDiv1('Holder');
Your question is a little unclear because the name of your function is showDiv1 but you explain that you're trying to hide an element, so I've tried to answer in light of this and hopefully it will give you some ideas.
This code displays a couple of divs. If you click on them they turn red and, after a couple of seconds (to represent an animation), they are hidden.
dummyAnim returns a promise. After the animation has run its course (here represented by a two second delay) it is resolved. I've used await to pause code execution in an async function until the animation has resolved.
// Grab the elements and add click handlers to them
const divs = document.querySelectorAll('div');
divs.forEach(div => div.addEventListener('click', hideElement, false));
function dummyAnim() {
// return a promise which resolves (an animation)
// after two seconds
return new Promise((resolve, reject) => {
setTimeout(() => resolve(), 2000);
});
}
async function hideElement() {
this.classList.add('red');
await dummyAnim();
this.classList.add('hidden');
}
div { color: black; display: block }
.hidden { display: none }
.red { color: red }
<div>close</div>
<div>holder</div>

Calling function with setTimeout in rapid fashion

I want the backgroundColor of a div to change for 250ms, and then change back. for this I use the following code as onclick on the div:
function keyAnimation(key) {
basicColor = key.style.backgroundColor;
key.style.backgroundColor = "red";
setTimeout(function () {
key.style.backgroundColor = basicColor;
}, 250);
But when I click the div multiple times quickly (within the 250ms) it remains red.
How can I code this so it will always go back to the basicColor after 250ms?
Add a flag that blocks additional keypresses:
var running = false;
function keyAnimation(key) {
if(running) return;
running = true;
const basicColor = key.style.backgroundColor;
key.style.backgroundColor = "red";
setTimeout(function () {
key.style.backgroundColor = basicColor;
running = false;
}, 250);
}

Javascript slider loop

I have been trying to create an image slider for html by changing the value of the image element, using a loop to go through each image and then returning to the original image.
However once it reaches the end of the loop it doesn't continue.
Here is my Js code:
window.onload = function() {
var x = 0
while(x<1000){
setTimeout(function() {
document.getElementById('carousel').src='img/header2.jpg';
setTimeout(function() {
document.getElementById('carousel').src='img/header3.jpg';
setTimeout(function() {
document.getElementById('carousel').src='img/header.jpg';
}, 5000);
}, 5000);
}, 5000);
x = x+1
}
}
I'd recommend you to not do while loop in this case. Just recall setTimeout when executed.
window.onload = function() {
/* Element */
var carousel = document.getElementById('carousel'),
/* Carousel current image */
cimage = 2,
/* Carousel source images */
csources = [
'img/header2.jpg',
'img/header3.jpg',
'img/header.jpg'
];
function changeCarouselImage() {
// Reset the current image if it's ultrapassing the length - 1
if(cimage >= 3) cimage = 0;
// Change the source image
carousel.src = csources[cimage ++];
// Re-call the function after 5s
setTimeout(changeCarouselImage, 5000);
}
setTimeout(changeCarouselImage, 5000);
};
The final function does not set another timeout, and thus the animation does not continue. Seperating into individual functions should help:
var carousel = document.getElementById('carousel');
function ani1() {
carousel.src = 'img/header.jpg';
window.setTimeout(ani2, 500);
}
function ani2() {
carousel.src = 'img/header2.jpg';
window.setTimeout(ani3, 500);
}
function ani3() {
carousel.src = 'img/header3.jpg';
window.setTimeout(ani1, 500);
}
window.onload = ani1;

JQuery transition animation

This program randomly selects two employees from a json-object Employees array, winnerPos is already defined.
For better user experience I programmed these functions to change pictures one by one. The animation stops when the randomly selected person is shown on the screen.
The slideThrough function will be triggered when the start button is pressed.
function slideThrough() {
counter = 0;
start = true;
clearInterval(picInterval);
picInterval = setInterval(function () {
changePicture();
}, 500);
}
function changePicture() {
if (start) {
if (counter > winnerPos) {
setWinner();
start = false;
killInterval();
} else {
var employee = Employees[counter];
winnerPic.fadeOut(200, function () {
this.src = 'img/' + employee.image;
winnerName.html(employee.name);
$(this).fadeIn(300);
});
counter++;
}
}
}
The problem is the animation doesn't work smoothly. At first it works, but not perfect. The second time the transition happens in an irregular way, i.e. different speed and fadeIn/fadeOut differs from picture to picture.
Could anyone help me to fine-tune the transition?
I would avoid using setInterval() and add a function to the call to .fadeIn() that starts the animation of the next picture.
It would look like this:
function changePicture(pos) {
pos = pos || 0;
if (pos <= winnerPos) {
var employee = Employees[pos];
winnerPic.fadeOut(200, function() {
this.src = 'img/' + employee.image;
winnerName.html(employee.name);
$(this).fadeIn(300, function() {
changePicture(pos + 1);
});
});
} else {
setWinner();
}
}
To start the animation, you call changePicture() without any arguments, like this.
changePicture();
jsfiddle

Categories

Resources