Search code examples
javascriptjquerycsscss-transitionschaining

jQuery queuing css methods


I have discovered some behavior, that seems quite strange, to my understanding, how jQuery.css() works. I know, it is not queuable, but in the fiddle below, when I apply 2 css method in succession - but not in one queue, it behaves differently, that applying them with 1ms delay by setTimeout.

The result is, that one box fades in, but the other goes immediately to opacity 1.

Can somebody clarify please.

var fadingBox = $("<div></div>")
  .css({
    "opacity": "0"
  })
  .appendTo("#cont");


setTimeout(function() {
  fadingBox.css({
    "background": "red",
    "opacity": "1"
  });
}, 1);


var notFadingBox = $("<div></div>")
  .css({
    "background": "red",
    "opacity": "0"
  })
  .appendTo("#cont");


notFadingBox.css("opacity", "1");
div {
  -webkit-transition: opacity 2s;
  -moz-transition: opacity 2s;
  -ms-transition: opacity 2s;
  -o-transition: opacity 2s;
  transition: opacity 2s;
  height: 50px;
  width: 50px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="cont">

</div>

https://jsfiddle.net/Lukis/0mbz7e9x/35/

EDIT: Updated the jsFiddle, which was wrong before.


Solution

  • The browser's layout engine cannot differentiate between the beginning state and end state of your element because, in its eyes, both the beginning state (opacity: 0) and end state (opacity: 1) were set at the same time.

    In order for the layout engine to understand that there should be separation, you'll need to cause an interruption between the beginning and end. Unfortunately, as far as I'm aware, there isn't a legitimate or "nice" way to do this, so you need to utilize a workaround.

    setTimeout is one way to force a separation, which is why your first example works.

    (This method is commonly misunderstood, so I feel it important to clarify that this does not work because you're "giving the browser time", but rather because setTimeout creates a queued callback. This callback is then executed separately, bypassing the initial issue of setting both states at the same time.)

    See the example below, where I set the timeout to 0 and still get a proper transition:

    var $fadingBox= $("<div></div>")
        .css({"background" : "red","opacity": "0"})
        .appendTo("#cont");
    
    setTimeout(function(){
        $fadingBox.css("opacity", "1");
    }, 0);
    #cont div {
      transition: 2s opacity;
      padding: 100px;
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <div id="cont"></div>

    Some functions also cause layout engine interruption/refresh, although I wouldn't always depend on these working, as you're capitalizing on something that may be removed in future optimizations.

    For example, you could take advantage of an element's offsetHeight property, whose getter function causes the layout engine to "pause":

    var $fadingBox = $("div");
    $fadingBox.css("opacity", "0");
    $fadingBox[0].offsetHeight;      //LAYOUT ENGINE REFRESH
    $fadingBox.css("opacity", "1");
    div {
      background: red;
      transition: 2s opacity;
      padding: 100px;
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <div></div>