5

Consider the following HTML document:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style type="text/css">
        body {
            background: crimson;
        }

        div {
            transition: opacity 5s;
            font-size: 4em;
            opacity: 0;
        }

        .loaded div {
            opacity: 1;
        }
    </style>
    <script type="text/javascript">
        document.addEventListener('DOMContentLoaded', function() {
            document.getElementById('body').className += "loaded";
        })
    </script>
</head>
<body id="body">
<div>
TEST
</div>
</body>
</html>

The div is supposed to have its opacity set to 0 and a 5s transition on opacity.

When the DOM is loaded, the body is given a class that set the div opacity to 1.

I'm expecting the div opacity to transition from 0 to 1 in 5s. But for some reason, it happens immediately.

If I use setTimemout, every works as expected:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style type="text/css">
        body {
            background: crimson;
        }

        div {
            transition: opacity 5s;
            font-size: 4em;
            opacity: 0;
        }

        .loaded div {
            opacity: 1;
        }
    </style>
    <script type="text/javascript">
        document.addEventListener('DOMContentLoaded', function() {
            setTimeout(function() {
                document.getElementById('body').className += "loaded";
            }, 0);
        })
    </script>
</head>
<body id="body">
<div>
TEST
</div>
</body>
</html>

Makes me wonder is styles are loaded after DOMContentLoaded event is triggered. Is this a normal behavior or am I doing something wrong here ?

Eric MORAND
  • 6,373
  • 4
  • 27
  • 34
  • 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. (https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded) (haven't tried out your snippet) – maracuja-juice Oct 28 '17 at 12:37
  • A similar question, though less clear: https://stackoverflow.com/questions/42891628/why-dont-transitions-on-svg-work-on-domcontentloaded-without-delay – Nickolay Oct 28 '17 at 15:48

2 Answers2

5

Short version:

You're getting into the area of underspecified behavior. I suggest you use the load event for triggering the transition.

Long version:

A CSS transition is triggered when computed values of an element's CSS properties change (spec), so it can not be triggered before the style system first computes the styles for the document.

The style system ought to do the initial styling pass on the document (i.e. calculate the computed values the first time) after it loads the relevant stylesheets -- otherwise it will have to re-do the work after the stylesheets finish loading.

On the other hand, the idea of DOMContentLoaded is to fire as soon as the HTML source is parsed -- without waiting for any other resources (including stylesheets) to finish loading, so it naturally can fire before any style was calculated.

The browsers have complicated heuristics that block execution of inline scripts (and thus parsing and DOMContentLoaded) and determine when the initial layout and painting happens. The scripts can force reflow/restyle, so depending on the specific web page code and timing, the styling information may be available by the time your script runs. I'm not sure if it works reliably in all browsers, but forcing layout from DOMContentLoaded might cause your transition to work:

div.offsetTop // force layout
Nickolay
  • 31,095
  • 13
  • 107
  • 185
  • 1
    Make sure your `load` event fires. For instance I first tried to simply change 'DOMContentLoad' to 'load' in the example from this question; this won't work, since the 'load' event fires on `window`, not on `document`. – Nickolay Oct 28 '17 at 18:20
  • 2
    I confirm that by using the window.load event, it works perfectly. – Eric MORAND Oct 28 '17 at 18:59
5

From the Mozilla Documentation:

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.

The documentation suggests that you should instead use the load event:

A very different event load should be used only to detect a fully-loaded page.

So you would do it like this:

window.addEventListener('load', function() {
        document.getElementById('body').className += "loaded";
});

Note that I didn't only change the event name but also changed the object that I assign the listener to.

maracuja-juice
  • 994
  • 2
  • 14
  • 33