2

So after a lot of searching about an issue that I am having, I figured out that the issue that I am having is, because when I apply css to the element, the info is not immediatelly available, here is what I mean:

I have an element that I am hiding and showing with display: block; and display: none;, the issue is that right after I show the element, I need to obtain its height:

$element.css({ display: "block"});

console.log($element.outerHeight()); // <- this returns 0

So I started experimenting with timeouts at some point, and even with a 1ms timeout, it still works and returns the correct height:

setTimeout(() => {
    console.log($element.outerHeight()); // with 1ms timeout, it works and gives real height
}, 1)

After figuring that out, with 1 ms delay it could not be some inherited transition or anything, so that got me thinking that maybe, between the jump from display: block; to .outerHeight() something is not yet updated, after some digging I found this:

Quote: "You'll see different behaviors in different browsers since jQuery is updating the markup with inline styles and the browser decides when to repaint."

Does jQuery's css method block until the change is fully applied?

And this seems to be my actual problem, so the question now is, how do I figure out when that repainting has happened, I have tried with onload, load events but none of them get triggered.

I have also tried the suggested code in that post with animation:

$element.animate({
  display: 'block'
}, {
  duration: 0,
  done: function(animation, jumpedToEnd) {
    console.log($element.outerHeight());
  }
})

But that did not work either.

Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175
Darkbound
  • 3,026
  • 7
  • 34
  • 72

1 Answers1

2

The issue you are having is that you do not really have control on how the browser is displaying changes. The information is not immediately available, as you have pointed out and even though doing timeouts is technically a solution, it lacks elegance and endangers your UI of getting into callback hell.

What you will need to test in order to see whether more elegant solutions work is

1. Whether it works with 0 milliseconds

If it works with 0 milliseconds, then basically the UI is refreshed just after the function is successfully executed and before the next function from the event loop is running.

2. Whether doing it without jQuery behaves differently

Compare the behavior of

$element.css({ display: "block"});

with

for (let index = 0; index < $element.length; index++) {
    $element[index].style.display = "block";
}

is equivalent. If so, then it's not a jQuery problem. If not, then it's a jQuery problem.

Conclusion

As a general guidance, you will probably need a callback-like behavior and to achieve that you will probably want to use the promise architecture. If it works.

Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175
  • I have just tried and yes, it does work with a timeouf ot 0 ms, I have also tried with pure JS and without timeout `document.getElementsByClassName("classofelement")[0].style.display = "block";` that doesnt work, so its not a jQuery issue. I am capable of doing it with promises, however, I don't know, what is the thing that I ahve to wait on, what is the function that I can use .then() on so that I can do whatever I want to do, after the event loop ran? – Darkbound Dec 25 '20 at 12:56
  • @Darkbound so, presumably this is the flow: 1. Your function changes some CSS value 2. The function ends 3. The browser displays some changes based on the CSS changes and subsequently attribute values are changed 4. Your next function is executed. The reason for your callback's successful behavior is that 3. happens between 2. and 4. The reason of the failure of immediate access is that in that case you invoce the attribute between 1. and 2., before 3. This is the problem and this is why a callback-like behavior works. – Lajos Arpad Dec 25 '20 at 13:03
  • @Darkbound for more information, I suggest that you could read about the event loop, then you would grasp how Javascript handles function calls. Assuming that you know how the event loop works, the information you need is that the UI is refreshed between the function calls. Javascript is single-threaded and it would be suboptimal for a browser to do changes inside a function because of a change, because there might be several changes. – Lajos Arpad Dec 25 '20 at 13:05
  • @Darkbound assuming that you have a function with 1000 UI changes, if the browser would do them all separately, then it would be quickly overwhelmed and useless from the user's perspective. Since browsers would have bad UX in that model, it is presumable (and my earlier experiments confirm it) that the browser does not refresh the UI while the JS function is running. It must therefore refresh the UI between nodes in the event loop. – Lajos Arpad Dec 25 '20 at 13:07
  • I understand that (kinda) and I did read about the callback hell and event loop that you posted in your initial reply. I understand why setTimeout with 0ms works. What I am after is some "cleaner" solution isnt there some event that is fired when a render is complete, so that I can do it with something like $(document).on("renderdone", ()=>{}), or in other words, how do I split that functionality into two different functions that will run independently, but still will get activated from one click – Darkbound Dec 25 '20 at 13:08
  • @Darkbound I do not know about such an event, but for me a .then() looks clean-enough. I think that a .then() is cleaner than a separate event, possibly with hacks that would execute after the render, because you generally need the logic that is to be executed around your initial function. – Lajos Arpad Dec 25 '20 at 13:15
  • I agree, but where would I use the .then() ? I cant use it on the $element.css(..).then() – Darkbound Dec 25 '20 at 13:23
  • @Darkbound you need to use it on a promise, which you could wrap around the function inside which you do these CSS changes. – Lajos Arpad Dec 25 '20 at 13:27
  • And how would I resolve the promise? I still need a way to detect that its done repainting. – Darkbound Dec 25 '20 at 13:36
  • @Darkbound You would make a promise which would resolve, so that the then will be called just after your main function ends. We have already discussed that this works even with 0 millisecond timeout, so if you manage to create a function that is called in the event loop just after your function, then that should work. Off course, you need to confirm this with tests. If this is still unclear, then you might want to create a JSFiddle. – Lajos Arpad Dec 25 '20 at 13:39
  • allrighty, I have tried that, but it doesnt work, I guess I'll just stick to the setTimeout with 0. `new Promise((res, rej) => { $element.css({ display: "block" }); res(""); }).then(() => { console.log($element.outerHeight()); })` – Darkbound Dec 25 '20 at 17:00
  • 1
    @Darkbound note that you can make it easy to use: `function myFunction(callback) {setTimeout(callback, 0);}` and then you can call it as myFunction(callback), not having to worry about the setTimeout when you use it. – Lajos Arpad Dec 25 '20 at 18:11