Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CSS last of type does not match while still loading

I think I discovered a bug in chrome and opera and would like a solution to make the css selector section:last-of-type work while the document is still loading. The bug only appears while the document is still loading: here's a minimal example in NodeJS to expose the bug:

What happens is that last-of-type does not match while the document is still loading. In IE it matches, then matches twice, then matches correctly again when loaded. It works fine in Firefox.

last-of-type.js

"use strict";
const http = require(`http`);

const PORT = 8080;

const htmlStart = `<!DOCTYPE html>
<html lang="en"><head>
    <meta charset="utf-8">
    <title>html streaming</title>
    <meta name="viewport" content="width=device-width">
    <style>

section {
    position: absolute;
    left: 0;
    right: 0;
}

section:last-of-type {
    animation: comin 1.4s ease 0s;
    left: 0;
    opacity: 1;
}

@keyframes comin {
    0% {
      left: 100%;
    }
    100% {
      left: 0;
    }
}

section:not(:last-of-type) {
   animation: comout 1.4s ease 0s;
   left: -100%;
   opacity: 0;
}

@keyframes comout {
    0% {
      left: 0;
      opacity: 1;
    }
    100% {
      left: -100%;
      opacity: 0;
    }
}
</style>
    <script>
        var headLoaded = Date.now();
        document.addEventListener("DOMContentLoaded", function() {
           console.log((Date.now() - headLoaded) / 1000);
         });
    </script>
</head>
<body>
    <h1>last-of-type test</h1>

    <section>
        <h2>First slide</h2>
        <p>Some text 111111111</p>
    </section>

    <section>
        <h2>2</h2>
        <p>22222222222222</p>
    </section>


`;

const htmlEnd = `
<p>html finised loading</p>
</body></html>`;


const INTERVAL = 8000; // ms
const server = http.createServer((request, response) => {
  response.setHeader(`Content-Type`, `text/html`);
  response.writeHead(200);
  response.write(htmlStart);
  setTimeout(function () {
        response.write(`<section>
            <h2>3</h2>
            <p>33333333333</p>
        </section>`);
  }, INTERVAL);
  setTimeout(function () {
        response.end(htmlEnd);
  }, 3 * INTERVAL);
});

server.listen(PORT);
console.log(`Listening on ${PORT}`);

The same loaded all at once works just fine. It confirms that the syntax is correct.

last-of-type-test.html

<!DOCTYPE html>
<html lang="en"><head>
    <meta charset="utf-8">
    <title>html streaming</title>
    <meta name="viewport" content="width=device-width">
    <style>

section {
    position: absolute;
    left: 0;
    right: 0;
}

section:last-of-type {
    animation: comin 1.4s ease 0s;
    left: 0;
    opacity: 1;
}

@keyframes comin {
    0% {
      left: 100%;
    }
    100% {
      left: 0;
    }
}

section:not(:last-of-type) {
   animation: comout 1.4s ease 0s;
   left: -100%;
   opacity: 0;
}

@keyframes comout {
    0% {
      left: 0;
      opacity: 1;
    }
    100% {
      left: -100%;
      opacity: 0;
    }
}
</style>
    <script>
        var headLoaded = Date.now();
        document.addEventListener("DOMContentLoaded", function() {
           console.log((Date.now() - headLoaded) / 1000);
         });
    </script>
</head>
<body>
    <h1>last-of-type test</h1>

    <section>
        <h2>First slide</h2>
        <p>some text</p>
    </section>

    <section>
        <h2>2</h2>
        <p>22222222222222</p>
    </section>

    <section>
        <h2>3</h2>
        <p>33333333333</p>
    </section>
</body></html>

Any hints would be appreciated.

like image 707
Walle Cyril Avatar asked Mar 27 '18 14:03

Walle Cyril


1 Answers

A possible workaround is to use a MutationObserver while the page is loading. There you can get a list of all elemets of section and give the last element an special class last. The MutationObserver is called on every dom change, even if the page is still loading. When the page is loaded you can remove this observer.

"use strict";
const http = require(`http`);

const PORT = 8080;

const htmlStart = `<!DOCTYPE html>
<html lang="en"><head>
    <meta charset="utf-8">
    <title>html streaming</title>
    <meta name="viewport" content="width=device-width">
    <style>

section {
    position: absolute;
    left: 0;
    right: 0;
}

section:not(:last-of-type) {
    animation: comout 1.4s ease 0s;
    left: -100%;
    opacity: 0;
}

section:last-of-type, section.last {
    animation: comin 1.4s ease 0s;
    left: 0;
    opacity: 1;
}

@keyframes comin {
    0% {
    left: 100%;
    }
    100% {
    left: 0;
    }
}

@keyframes comout {
    0% {
    left: 0;
    opacity: 1;
    }
    100% {
    left: -100%;
    opacity: 0;
    }
}
</style>
    <script>
        var observer = new MutationObserver(function() {
            var list = document.querySelectorAll("section");
            if (list.length === 0) return;
            for (var i = 0; i < list.length; i++) {
                list[i].classList.remove("last");
            }
            list[list.length - 1].classList.add("last");
        });
        var headLoaded = Date.now();
        document.addEventListener("DOMContentLoaded", function() {
            console.log((Date.now() - headLoaded) / 1000);
            observer.disconnect();
            var list = document.querySelectorAll("section");
            for (var i = 0; i < list.length; i++) {
                list[i].classList.remove("last");
            }
        });
    </script>
</head>
<body>
    <script>
        observer.observe(document.body, { attributes: true, childList: true })
    </script>
    <h1>last-of-type test</h1>

    <section>
        <h2>First slide</h2>
        <p>Some text 111111111</p>
    </section>

    <section>
        <h2>2</h2>
        <p>22222222222222</p>
    </section>


`;

const htmlEnd = `
<p>html finised loading</p>
</body></html>`;


const INTERVAL = 8000; // ms
const server = http.createServer((request, response) => {
response.setHeader(`Content-Type`, `text/html`);
response.writeHead(200);
response.write(htmlStart);
setTimeout(function () {
        response.write(`<section>
            <h2>3</h2>
            <p>33333333333</p>
        </section>`);
}, INTERVAL);
setTimeout(function () {
        response.end(htmlEnd);
}, 3 * INTERVAL);
});

server.listen(PORT);
console.log(`Listening on ${PORT}`);

The observer must be connected after the <bod> tag. Otherwise document.body is null.

I put the css selector section:not(:last-of-type) before the positive match to avoid the usage of !important directives.

like image 69
pixix4 Avatar answered Sep 30 '22 14:09

pixix4