4

Div in the snippet below shouldn't become green after click, but it does in IE 11 and Edge.


Let's take a look at following situation: there is a black div.
When it is clicked, we call requestAnimationFrame and change color to green immediately.
In requestAnimationFrame collback we change color to red.

For for more visibility I've added a loop into requestAnimationFrame which creates a lag for 3 seconds. Also I've added a CSS animation to show where the lag is.

I expect following behavior (and see it in Chrome and Firefox):

  1. There is a black div
  2. click occurs
  3. color is formally changed to green, but it's not rendered
  4. Without rendering callback of requestAnimationFrame is called
  5. Browser lags for 3 seconds with black div on the screen
  6. div's color changes to red
  7. Render occurs and red div is shown

However, IE11 and Edge on step 5 show green div. I think that such behavior is incorrect as browser must postpone re-render until requestAnimationFrame handler is executed.

How can I force IE/Edge to call the handler before render?

I can't prevent changes before requestAnimationFrame as in my actual code they are caused by a CSS animation. Also this problem occurs even in case of 2 recursive calls to requestAnimationFrame.

https://jsfiddle.net/8Ljn14ee/2/

var div = document.querySelector('div');

div.addEventListener('click', function () {
  requestAnimationFrame(function () {
    var t = new Date;
    t.setSeconds(t.getSeconds() + 3);
    while (new Date < t);
    div.style.background = 'red';
  });

  div.style.background = 'green';
});
div {
  height: 200px;
  width: 200px;
  border-radius: 50%;
  background: black;
  animation: w-100-200 3s linear infinite;
}

@keyframes w-100-200 {
    0% { width: 200px; }
   50% { width: 400px; }
  100% { width: 200px; }
}
<div></div>

PS: Same question in Russian.

TylerH
  • 20,799
  • 66
  • 75
  • 101
Qwertiy
  • 19,681
  • 15
  • 61
  • 128
  • Not sure if it's related, but you've got: `var t = new Date;` and `while (new Date < t);` and it should be: `var t = new Date();` and `while (new Date() < t);` – Scott Marcus Jun 29 '17 at 22:27
  • @ScottMarcus, no, it shouldn't. That's the same thing. By the way, you can see, that it actually works. – Qwertiy Jun 29 '17 at 22:28
  • Can you provide some documentation for that. I don't see that syntax listed [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date). If you don't add the parenthesis, you may get the correct date/time, but when you attempt to call a method on the returned object (like `.setSeconds()`), it would be a problem because that is an instance method and without the parenthesis, you get a value, but not an instance, since you didn't call `Date` as a constructor function. – Scott Marcus Jun 29 '17 at 22:30
  • @ScottMarcus, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new says `new constructor[([arguments])]` - as you see, braces can be omitted if there are no arguments. – Qwertiy Jun 29 '17 at 22:36
  • @ScottMarcus, just check if you don't trust: https://jsfiddle.net/8Ljn14ee/4/ This part works in the same way regardless parenthesis. And no, I'll get value (string) if I call it without `new`. With `new` it's a `Date` object. – Qwertiy Jun 29 '17 at 22:38
  • Ok. Thanks. Seems like this may be new in ES6. – Scott Marcus Jun 29 '17 at 22:40
  • @ScottMarcus, nope, it's very old: https://i.stack.imgur.com/IZ49p.png – Qwertiy Jun 29 '17 at 22:48
  • You may very well be right, but just because it worked in IE 5.5 doesn't mean it was standard. A lot of what is standard today started out as proprietary. Checking the actual spec. is the only way to really know for sure. – Scott Marcus Jun 30 '17 at 05:06
  • @ScottMarcus, I haven't seen this feature in ES6+, so I expect it to be older. Anyway, could you help with original problem, please? – Qwertiy Jun 30 '17 at 07:02
  • "must postpone re-render" Where did you read it **must** do it? Painting operations are not tied by the specs and left to the implementors. They may well not be done on the same thread as js execution. And IE may also trigger a reflow before executing rAF callbacks. Nothing forbids this behavior. – Kaiido Oct 21 '17 at 22:41
  • @Kaiido, [MDN](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) says _"The window.requestAnimationFrame() method tells the browser that you wish to perform an animation and requests that the browser call a specified function to update an animation **before the next repaint**. The method takes a callback as an argument to be invoked before the repaint."_ – Qwertiy Oct 22 '17 at 01:30
  • But when you read the specs, you will find out that they don't ever tell when repaint should occur. They only talk about animationFrame callbacks. This MDN article simplify this fact because most browser do repaint only on screen refresh rate, but as I said they are not forced to do it. So if I had to do a wild guess on what happens here, I would say: when js thread is freed, reflow. Before repaint, execute rAF callbacks. On a parallel thread draw whatever is the current flow. You could check it by setting a third color before your blocking script in the rAF callback. – Kaiido Oct 22 '17 at 02:07
  • So I could finally get my hands on a IE, and it is indeed a bug. If my theory were correct, the circle in [this fiddle](https://jsfiddle.net/rxk6yjez) should be blue, not green. And I didn't found any way to do what you want. Sorry... – Kaiido Oct 23 '17 at 01:28
  • @Kaiido, I don't think it should be blue. Seems like repaint never happens during js execution, so the color after the last block is used. Also, I'd like to notice, that reflow can be caused only by changing and reading sizes. Changing and reading some color triggers only repaint, but not reflow. See https://stackoverflow.com/q/2549296/4928642 for more details. – Qwertiy Oct 23 '17 at 07:22
  • Am not sure about your note on reflow, IMM getComputedStyle will trigger it anyhow you'll use it, but you might be right and I also tried with `elem.offsetWidth` and `elem.innerText` which do trigger the reflow for sure. And what I said in previous comment is that *if my theory were correct* (parallel thread running with whatever the current flow is at that time even if js has not finished) then it should be blue (since even if the js thread is blocked, current flow should have been updated before rAF callbacks) – Kaiido Oct 23 '17 at 07:28
  • @Kaiido, I'm almost sure that there is no parallel thread in IE... – Qwertiy Oct 23 '17 at 07:31
  • Wow you might be right... They don't even register events when js thread is blocked... – Kaiido Oct 23 '17 at 07:45

1 Answers1

1

Seems that the problem is under consideration on Edge

I thinks It's because IE doesn't executes requestAnimationFrame fast enough or it defers it a little, so it executes the div.style.background = 'green'; while other browsers don't.

I've a similar problem with requestAnimationFrame with IE and I thinks it's the same problem, check the fiddle with ie clicking the circle you see it going green a moment when other browsers don't do that.

https://jsfiddle.net/8Ljn14ee/13/

var div = document.querySelector('div');

div.addEventListener('click', function () {
  div.style.background = 'green';
  requestAnimationFrame(function () {
    div.style.background = 'red';
  });
});
div {
  height: 200px;
  width: 200px;
  border-radius: 50%;
  background: black;
  animation: w-100-200 3s linear infinite;
}

@keyframes w-100-200 {
    0% { width: 200px; }
   50% { width: 400px; }
  100% { width: 200px; }
}
<div></div>

I use that solution to calculate width or height of elements with display: none; and after in the requestAnimationFrame rehide the element, and only on IE you can see a moment where the elements are visible.

minimit
  • 29
  • 4