12

I have an svg with transitions set on it. Now when I add a class to it which has some properties varied then the transition only occur if I add delay between DOMContentLoaded event and addclass event. Here are two example, first with no delay second with an infinitesmall delay:

Without Delay:

! function() {
  window.addEventListener('DOMContentLoaded', function() {
    var logo2 = document.querySelector("svg");
    logo2.classList.add('start');
  });
}();
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 104.75 32.46">
            <defs>
                <style>
                polygon {
                    fill: red;
                    transition: opacity 3s ease-out, transform 3s ease-out;
                    opacity: 0;
                }

                .start polygon {
                 opacity: 1;
                }
    
    #A1 polygon {
     transform: translate(100px, 100px);
     transition-delay: 1s;
    }

    /*styles after animation starts*/
    .start #A1 polygon {
     transform: translate(0px, 0px);      
    }


            </style>
            </defs>
            <title>Logo</title>
            <g id="A1">
                
                <polygon  class="right" points="0.33 31.97 0.81 26.09 13.61 3.84 13.13 9.72 0.33 31.97" />
            </g>
            </svg>

With Delay:

! function() {
  window.addEventListener('DOMContentLoaded', function() {
    var logo2 = document.querySelector("svg");
    setTimeout(function(){
       logo2.classList.add('start');
    },0);
  });
}();
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 104.75 32.46">
            <defs>
                <style>
                 polygon {
                     fill: red;
                     transition: opacity 3s ease-out, transform 3s ease-out;
                     opacity: 0;
                 }

                 .start polygon {
                  opacity: 1;
                 }
     
     #A1 polygon {
      transform: translate(100px, 100px);
      transition-delay: 1s;
     }

     /*styles after animation starts*/
     .start #A1 polygon {
      transform: translate(0px, 0px);      
     }


                </style>
            </defs>
            <title>Logo</title>
            <g id="A1">
                
                <polygon  class="right" points="0.33 31.97 0.81 26.09 13.61 3.84 13.13 9.72 0.33 31.97" />
            </g>
            </svg>

As you can see in second example I added a delay of 0 second but it caused the animations to work, why?

Update1: well... we all are wrong :-)

I tried the same code without DOMContentLoaded and without delay. It still doesn't add transition without a delay:

! function() {
 
    var logo2 = document.querySelector("svg");
    logo2.classList.add('start');

}();
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 104.75 32.46">
            <defs>
                <style>
                polygon {
                    fill: red;
                    transition: opacity 3s ease-out, transform 3s ease-out;
                    opacity: 0;
                }

                .start polygon {
                 opacity: 1;
                }
    
    #A1 polygon {
     transform: translate(100px, 100px);
     transition-delay: 1s;
    }

    /*styles after animation starts*/
    .start #A1 polygon {
     transform: translate(0px, 0px);      
    }


            </style>
            </defs>
            <title>Logo</title>
            <g id="A1">
                
                <polygon  class="right" points="0.33 31.97 0.81 26.09 13.61 3.84 13.13 9.72 0.33 31.97" />
            </g>
            </svg>

I also noted that jQuery doesn't cause a reflow. Here is an example of inline jquery code that still doesn't fire ready function before CSSOM is loaded. Instead of inline jquery if we had external jquery then ready event would fire after CSSOM is ready. The understanding I have reached is that CSSOM needs a few milliseconds after html dom is rendered. So till it downloads external jquery CSSOM is ready. DOMContentLoaded simply don't care if stylesheets are loaded or not, that is it doesn't care if CSSOM is ready or not.

user31782
  • 7,087
  • 14
  • 68
  • 143
  • A related discussion on how `DOMContentLoaded` is fired before `CSSOM` build: http://stackoverflow.com/questions/42950574/are-deferred-scripts-executed-before-domcontentloaded-event – user31782 Mar 22 '17 at 15:44
  • **Attention:** Run all the scripts on firefox to understand the question. chrome has changed the way it deals with svg and css since 2017. – user31782 Aug 15 '22 at 05:37

2 Answers2

7

Because that's what DOMContentLoaded does : it fires when the DOM has been parsed, but before the CSSOM has been (and thus before styles have been applied).

If you don't want to wait for the load event,
one way is to force the browser to paint before your script execution (synchronously), by calling offsetXXX property on any document's element (e.g <body>) :

! function() {
  window.addEventListener('DOMContentLoaded', function(){
    document.body.offsetTop; // force a CSS repaint
    var logo2 = document.querySelector("svg");
    logo2.classList.add('start');
  });
}();
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 104.75 32.46">
  <defs>
    <style>
      polygon {
        fill: red;
        transition: opacity 3s ease-out, transform 3s ease-out;
        opacity: 0;
      }
      .start polygon {
        opacity: 1;
      }
      #A1 polygon {
        transform: translate(100px, 100px);
        transition-delay: 1s;
      }
      /*styles after animation starts*/
      .start #A1 polygon {
        transform: translate(0px, 0px);
      }
    </style>
  </defs>
  <title>Logo</title>
  <g id="A1">
    <polygon class="right" points="0.33 31.97 0.81 26.09 13.61 3.84 13.13 9.72 0.33 31.97" />
  </g>
</svg>
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Why does `document.body.offsetTop;` cause a repaint? – user31782 Mar 22 '17 at 08:04
  • Because this property must return the offset position of the element according to its offsetParent's position, to do so, it has to do a reflow, so that everyone has a position. – Kaiido Mar 22 '17 at 08:10
  • I always thought `DOMContentLoaded` is same as jQuery dom ready. many posts on SO suggest this. Would `readystatechange`event fire after CSSOM is loaded? – user31782 Mar 22 '17 at 08:17
  • I can't use jquery for this svg work for some reason. Do you know how jquery cause the reflow after ready? Does the reflow occur after jquery file is loaded, or after I manually call jquery ready function? – user31782 Mar 22 '17 at 08:36
  • I appreciate your help. I don't want you to read whole jquery code :-) I was asking if in case you already know. Now it would be my task to read jquery code. – user31782 Mar 22 '17 at 08:40
  • Oh actually I said crap in these comments... FF and chrome behave differently, and my FF even beahve differently between stack-snippet and a real test page. So actually on chrome, `$.ready()` callbacks are deffered, and fires after an original DOMContentLoaded event handler : https://jsfiddle.net/rzz6c0w2/ – Kaiido Mar 22 '17 at 09:02
  • 1
    If we have a large css file in head. And when browser reaches `document.body.offsetTop; // force a CSS repaint` if style sheet hasn't been loaded yet, would browser halt further javascript processing? Would it wait for style sheet to be loaded and applied and then find `offSet` and then start further javascript processing? – user31782 Mar 22 '17 at 12:35
  • 1
    @user31782, stylesheets are loaded when DOMContentLoaded fires (except if you append it dynamically after of course). What won't be loaded is the CSSOM, this means the CSS content won't have been parsed in relation with the DOM if I may say so. So to answer your question, `offsetXXX` hack won't wait for anything to load, it just forces a reflow. However, it won't wait for external content that should be loaded from your stylesheets (e.g background-image, fonts) to be actually loaded. For this, use the `load` event. – Kaiido Mar 22 '17 at 13:01
  • I ran another test and found that if css files takes longer than jquery to download then after jquery ready function I need manual reflow of page. – user31782 Mar 22 '17 at 13:09
  • Something has changed in chrome's behaviour, all the animations on this page are not working in chrome. However in firefox, it still works as expected. – user31782 Aug 15 '22 at 05:35
4

As you can see in second example I added a delay of 0 second but it caused the animations to work, why?

Since the CSS object model have not been loaded when the DOMContentLoaded event fires

The DOMContentLoaded event is fired when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading. A very different event load should be used only to detect a fully-loaded page. It is an incredibly popular mistake to use load where DOMContentLoaded would be much more appropriate, so be cautious.

https://developer.mozilla.org/en/docs/Web/Events/DOMContentLoaded

So, adding the css class will not run the animation.

The setTimeOut method is a javascript event, once fired (even with 0 time), it will be added to the end of the current browser execution queue (which in your case will be added after loading the CSS model). Hence the animation will fire properly.

Update:

But jquery domready fires after CSSOM is loaded. So are all these posts on SO are technically incorrect?

domready uses DOMContentLoaded so theoratically speaking, they behave the same way.

Are deferred script tags(both inline or external) executed before CSSOM is loaded?

The script deferred by the defer attribute is executed before the DOMContentLoaded is fired. So the answer is YES.

KAD
  • 10,972
  • 4
  • 31
  • 73
  • I have many confusions now. 1. Many posts on SO suggest jquery domready is same as DOMContentLoaded(e.g. [this](http://stackoverflow.com/a/8575961/3429430)) But jquery domready fires after CSSOM is loaded. So are all these posts on SO are technically incorrect? 2. Are deferred script tags(both inline or external) executed before CSSOM is loaded? – user31782 Mar 22 '17 at 08:04
  • As I am discussing with below answer's user, It seems like jquery ready and DOMContentLoaded are same so other stackoverflow posts, in principle, are correct but they should have mentioned that without jquery we need a browser repaint after `DOMContentLoaded`. Your last link doesn't mention about CSSOM. – user31782 Mar 22 '17 at 08:39
  • `domready` uses `DOMContentLoaded` so theoratically speaking, they behave the same way. As for the answer, in most broswers defer occurs after the page load so the CSSOM shall be loaded at that time – KAD Mar 22 '17 at 08:50
  • No no if CSSOM is loaded at that time then DOMContentLoaded must fire before that. But it can't because DOMContentLoaded fires only after executing deferred scripts. – user31782 Mar 22 '17 at 08:52
  • Yeah that's true, the script deferred by the defer attribute is executed before the (DOMContentLoaded) is fired. I updated the answer – KAD Mar 22 '17 at 09:06
  • I think MDN would have typo there. It should be _[deferred] script is meant to be executed **loaded** after the document has been parsed, but before firing DOMContentLoaded_. If MDN is right then how can external scripts apply css styles before CSSOM is ready? – user31782 Mar 22 '17 at 11:26
  • The only way they can do it is if they force render – KAD Mar 22 '17 at 12:14
  • I have updated the question. The understanding I have reached to is that `DOMContentLoaded` simply don't care if CSSOM is ready or not. – user31782 Mar 22 '17 at 12:21