Div class change not showing during .each loop - javascript

I have a javascript function triggered by a button click. I add a class to an element before going into an 'each' loop.
When running the code in the page, I do not see the change. But if I pause the code in the debugger, I do.
The code is set up like this:
$("#btnApplyDefaults").on('click',
function (e) {
$('#loader').addClass("loading-page");
$('#pricingSheetItems tr').filter(':has(:checkbox:checked)').each(function() {
// Do some stuff
});
$('#loader').removeClass("loading-page");
});
If I run this with data that takes a long time to run, I never see the loading image. But if I have a breakpoint, and walk through I see the image.
Here's the class:
div.loading-page {
position: absolute;
left: 50%;
background-image: url("../../dist/img/loading.gif");
background-repeat: no-repeat;
content: "";
display: block;
width: 32px;
height: 32px;
}
I'm assigning it to this div:
<div tabindex="-1" class="" id="loader"></div>

All of your code runs synchronously, meaning that it completes all of it before your UI responds. You add the loading class at the beginning, do some operations, and remove the class, all before the UI can respond.
If you want your UI to update, you can add a minor pause via setTimeout:
$("#btnApplyDefaults").on('click',
function (e) {
$('#loader').addClass("loading-page");
setTimeout(function () {
$('#pricingSheetItems tr').filter(':has(:checkbox:checked)').each(function() {
// Do some stuff
});
$('#loader').removeClass("loading-page");
}, 0);
});
This should show the loading class. I suspect it won't animate though, because your page will be busy doing computations. You should look into using WebWorkers or some sort of asynchronous worker for a better user experience.

Related

Check in javascript if a CSS3 animation is currently running on a DOM element

I am trying to make a kind of template, for a dashboard page, where when a dashboard button is clicked, something is added to the DOM.
I was trying to template this, so that when someone makes a new dashboard for example, he has the option, to specify in CSS an animation that should run on each button when clicked. If an animation is defined on the button, the actual loading of the element should be delayed until the animation completes.
Now, if I actually specify an animation in css, everything works fine, because I am delaying the logical code with a callback on the animations end. My problem is, that I can't achieve the same, when there is no animation set to the element.
What I would want is something simmilar:
function buttonClick($button) {
$button.addClass('activated');
$button.one('animationend', function() {
// ... run the logic here
});
if (...no animation specified in activated class) {
// ... run the logic here
}
}
NOTE: I am using jQuery here, if there is a method specific in jQuery for this, that would also be okay for me, but a plain javascript method would be fine as well. I heard about the jQuery(":animated") selector, but when I was testing it, it seems that it only works for animations started with jQuery itself, and not with CSS3.
As you seem to use animation CSS for your animations (given that you use the animationend event), you could use getComputedStyle to verify the content of the animation-name CSS property.
Here is a demo with two buttons: one triggers an animation on click, while the other doesn't:
$("button").click(function () {
buttonClick($(this));
});
function hasAnimation($button) {
return getComputedStyle($button[0], null)["animation-name"] != "none";
}
function onEndAnimation($button) {
$button.removeClass('activated');
console.log("animation complete on button " + $button.text());
}
function buttonClick($button) {
$button.addClass('activated');
$button.one('animationend', () => onEndAnimation($button));
if (!hasAnimation($button)) onEndAnimation($button);
}
#yes.activated {
animation-duration: 1s;
animation-name: grow;
}
#keyframes grow {
from { width: 50px; }
50% { width: 100px; }
to { width: 50px; }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="yes" style="width: 50px">Yes</button><br>
<button id = "no" style="width: 50px">No</button>

CSS visual rendering not showing until JS script has finished

I have a JS function which is triggered on a click event.
At the very first line in the function, I add a class to a html element. The html is a custom loader, and the class just makes the loader visible.
The problem is that the loader does not appear until the script has actually finished executing. The class is set on the element right after the script starts, but nothing visually happens in my browser until the script has finished executing.
$('body').on('click', '#button', function(){
$('#loader').addClass('active');
$('.fields').each(function(i, el){
// does some intensive stuff, including appending elements to a dom fragment, triggering events programmatically. This takes 5-10s to execute.
});
// the loader only appears visible in the browser once the code executed in the loop finishes.
});
The CSS in the active class is:
.active{
z-index: 9999;
display: block;
position: absolute;
left: 40%;
left: calc(50% - 18px);
left: -webkit-calc(50% - 18px);
top: 40%;
}
I tried do re-search this issue, but I couldn't find anything on google.
Maybe it's because I do not know what keywords I should search, I am not sure what is causing or how to refer to this issue.
So guys, you are my last hope. Could you point me in the right direction on how to debug or what is causing this?
If I add the fields loop inside a setTimeout function with 0 timeout, it works. But the idea is to fix it the correct way, and understand why it's not working as expected.
If I add the fields loop inside a setTimeout function with 0 timeout, it works.
This is a sensible approach, although requestAnimationFrame is stylistically slightly nicer.
But the idea is to fix it the correct way, and understand why it's not working as expected.
The browser won't trigger a repaint until the function has finished running - otherwise, it would do a repaint for every individual DOM modification, which would be expensive.

keep window hidden until init completes

I am creating an window with a significant initialization process and I would like to keep the window hidden until init finishes. Right now at the beginning of my js code I hide the window right after it is created and then show it when init is complete. But this creates a less than pleasing flash when the app is launched as the window appears, disappears and then re-appears.
Is there a way to keep the window invisible while the init runs?
My best guess, without seeing your code, is that you need to hide the application window using CSS in the head section of your page. This way it is hidden before the browser ever renders the page. Trying to hide the window with Javascript won't work as nicely. That's because the script can't hide the window until after the browser creates it. So, depending on conditions, the user might see it flash on start.
The snippet below shows how to do this using the CSS visibility attribute. Alternatively, you may also use the display attribute.
Show and then run the snippet to try.
setTimeout(function() {
// some long init process here
// make visible on ready
window.spinner.style.display = 'none';
window.app.style.visibility = 'visible';
}, 3000);
#app {
height: 10em;
visibility: hidden;
background-color: white;
}
h3,
h4 {
margin: 0;
padding: 4px;
color: white;
background-color: steelblue;
}
#spinner {
height: 100px;
position: absolute;
}
body {
background-color: lightgray;
}
<h3>Header</h3>
<div id="content">
<img id="spinner" src="http://i.stack.imgur.com/kOnzy.gif">
<div id="app">APPLICATION READY</div>
</div>
<h4>footer</h4>
I agree with #jeff about providing some sort of progress indicator. However, the standard way to create a window that's hidden by default in Electron is to use the show option when creating the browser window:
const myWindow = new BrowserWindow({ show: false });
Then when loading/processing is finished you can make the window visible:
// this code runs in the renderer process
import { remote } from 'electron';
remote.getCurrentWindow().show();
Hide it first with CSS. display: none or visibility:hidden.
Then show with javascript by changing display or visibility after init.

Get CSS value mid-transition with native JavaScript

This question was asked before, but the answer uses jQuery, here.
So, I am going to tweak the question to specifically ask for a native solution, to minimize dependencies.
Let's say hypothetically, you have a <div> and that <div> is in mid-transition of its opacity value and top value. How would I get the value of both of those properties mid-transition using native JavaScript?
It is very easy to port the jQuery script from the linked thread into its vanilla JavaScript equivalent and below is a sample. The output is printed on the right side (output#op element) once timer expires.
All that we are doing is the following:
Attach two event handlers to the element which triggers the transition (sometimes the triggering element can be different from the one that has animation). In the other thread, the element that is triggering the transition and the one that is being transitioned is the same. Here, I have put it on two different elements just for a different demo.
One event handler is for mouseover event and this creates a timer (using setTimeout) which gets the opacity and top value of the element that is being transitioned upon expiry of timer.
The other event handler is for mouseleave event to clear the timer when the user has hovered out before the specific point at which we need the opacity and top value to be obtained.
Getting the opacity and top value of the element that is being transitioned can be obtained by using the window.getComputedStyle method.
Unlike the demo in the other thread (which uses setInterval), here I have used setTimeout. The difference is that setInterval adds an interval and so the function is executed every x seconds whereas the function passed to setTimeout is executed only once after x seconds. You can use whichever fits your needs.
var wrap = document.querySelector('.wrapper'),
el = document.querySelector('.with-transition'),
op = document.querySelector('#op');
var tmr;
wrap.addEventListener('mouseenter', function() {
tmr = setTimeout(function() {
op.innerHTML = 'Opacity: ' + window.getComputedStyle(el).opacity +
', Top: ' + window.getComputedStyle(el).top;
}, 2500);
});
wrap.addEventListener('mouseleave', function() {
clearTimeout(tmr);
});
.wrapper {
position: relative;
height: 400px;
width: 400px;
background: yellowgreen;
}
.with-transition {
position: absolute;
top: 0px;
left: 100px;
width: 200px;
height: 100px;
background: yellow;
opacity: 0;
transition: all 5s linear;
}
.wrapper:hover .with-transition {
top: 300px;
opacity: 1;
}
output {
position: absolute;
top: 50px;
right: 50px;
}
<div class='wrapper'>
<div class='with-transition'></div>
</div>
<output id='op'></output>
The answer referenced in the duplicate question is easily modified to NOT use jquery. There is no black magic happening there.
The real question is why would you want to do this?
If You need control over a transition just impliment the partial transition with javascript, do what you need, then complete the transition.

Delayed CSS rendering while Javascript does it's work

I have a very large and complicated jQuery plugin which I will avoid posting here for simplicity's sake. My problem is very simple, and I will reduce it only to the relevant code:
I have a click event attached to a set of buttons:
$("ul.tick-boxes button").click(function(event) {
event.preventDefault();
$("ul.product-grid").addClass("loading");
$(this).toggleClass("active");
$theElement.trigger("filterOptionsChanged");
});
If you go to this link, you can see these in action in the left sidebar:
http://mazer.com/collections/refrigerator?preview_theme_id=22019779
Here is the css that produces a check-mark when you click the buttons:
ul.tick-boxes button.active .tick-box::after {
content: "\e603";
font-family: "custom-icons";
color: #51425d;
top: 2px;
left: 2px;
font-size: 0.75rem;
position: absolute;
}
If your computer is as slow as mine, then when you click these filter options, it takes a second or so to "tick" the "tick-box". If you can't see it, try unticking it, which for me takes noticeably longer. The time-point where the "tick" visibly renders is always simultaneous with the product-grid rebuilding and rendering. I haven't posted the code for manipulating the product-grid, but you can know that the line $theElement.trigger("filterOptionsChanged") triggers a lot of array and object processing to build a document fragment of the new product list, and updates the DOM at the end. I understand this can take a second, that is not my problem. But what I don't understand is why my "tick-boxes" are waiting until after the code of that event is finished to render. According to my css, all I need is a class active on the button, and that code is fired one line above triggering the "filterOptionsChanged" event, so it should fire before any product grid changes happen.
Now. If I open up my inspector in chrome, I can actually see the active classes toggling instantaneously on click, before the product grid updates. However, the css which adds the tick doesn't catch the active class on the element until after my "filterOptionsChanged" code completes.
My first attempt to solve the problem will be posted below. I read a good bit about the "expensiveness" of css pseudo-selectors. That essentially to a browser, it is like a dom manipulation every time an ::after element is created. So I then write this css:
ul.tick-boxes button .tick-box::after {
content: "\e603";
font-family: "custom-icons";
color: #51425d;
top: 2px;
left: 2px;
font-size: 0.75rem;
position: absolute;
opacity: 0;
}
ul.tick-boxes button.active .tick-box::after {
opacity: 1;
}
So now an ::after element always exists from the beginning, any rendering costs are paid for at the outset, so my thought was, now when I click this button, the tick is already there, we are just giving it an opacity of 1. Didn't fix the delay.
Now, I try removing the "filterOptionsChanged" event trigger entirely. This makes my whole sorting plugin stop working, but I don't care at this point, because I want to understand what is causing the problem. When I do remove that event trigger, the buttons and css render snappy. No more problems.
I have a vague thought that, ok, if a click event can be snappy without that event trigger, I need a way of seperating the two. First add the active class, then trigger "filterOptionsChanged". I think, ok, jQuery Deferreds. Here is that code:
$("ul.tick-boxes button").click(function(event) {
event.preventDefault();
var showLoading = jQuery.Deferred();
$("ul.product-grid").addClass("loading");
$(this).toggleClass("active");
showLoading.resolve();
$.when(showLoading).done(function() {
$theElement.trigger("filterOptionsChanged");
});
});
So showLoading is a blank Deferred, I then add my classes for the tick boxes to show, then I resolve the deferred. Now I say, when showLoading is done, then do the whole product-grid manipulation. Don't do these at the same time, javascript, wait for one to finish, then do the other. Still no avail. Any ideas?
According to this, all function calls in JavaScript block the UI until they complete; I'd wager that this includes the original function call to start the click event. A cheap solution might be to replace the trigger with something like:
setTimeout(function() { $theElement.trigger("filterOptionsChanged"); }, 200);
Which will hopefully delay the trigger long enough for the browser to repaint the UI (you could/should add a little loading icon in the original function, then remove it in the timeout). You could also take a look at web workers, which look like they're pretty much threads.
Try this,
CSS:
ul.tick-boxes button.active .tick-box::after {
content: "\e603";
font-family: "custom-icons";
color: #51425d;
top: 2px;
left: 2px;
font-size: 0.75rem;
position: absolute;
}
JS:
$("ul.tick-boxes button").click(function(event) {
event.preventDefault();
$("ul.product-grid").addClass("loading");
$(this).toggleClass("active").fadeIn(10, function () {
$theElement.trigger("filterOptionsChanged");
});
});
Using callback to delay the triggering. See if this work for you.

Categories