DOM Background color propagation flickering inheriting initial bg color in Google Chrome using theme toggle to overload body bg color
Asked Answered
T

3

7

I'm currently working on a theme toggle for my website that uses Javascript / jQuery to manipulate the Body.bg color using a lightmode()/ darkmode() function that is toggled by a button. What I want to do is create seamless transitioning between the body bg color with fade in and fade outs. I already have that made and created, but the problem is when the theme reads the storage type it will blink quickly in Chrome and Chrome Canary but in Safari, and Safari Tech Preview for Catalina it works seamlessly.

However, I keep running into an issue when the user switches to white and then clicks on the nav link which then causes a black blink of the dark mode theme. My site starts with dark mode enabled and body bg is = #0a0a0a, but when it switches to white and the storage is updated, it still inherits the black body - bg that is initially set in the style.less of my theme.

If I remove the background color, then the white flicker happens on the dark mode theme, and ideally my end goal is to create seamless page navigation without the page blinking for a quick second reading the initial bg color before the body loads so - white on white bg, black on black bg no blinking to cause visual disruption.

I've tried every work around I could come up with and can't figure out a solution. It works in Safari with no page blinking, but in Chrome it still blinks. I've included video links showing what I want to accomplish with the Safari render for Google chrome and the main.js code file.

Safari seamless transitioning with no blink

Safari - seamless transitioning

Chrome Canary

Chrome and Chrome Canary - watch for the blink on white transition. It's quick but super frustrating.

So what's going on?! The problem is the theme is now set to white, but the initial body - bg color is black in the style.less theme and it picks that up real quick before snapping back to the white theme.

When I audit the site in Canary, and run it at slow 3G simulated there is no blink, but when it ran normally or at Fast 3G in the audit the blink occurs. My hypothesis is to set a timeout on the body before loading it, but I tried that as a work around and still got blinking.

I've tried to create key stored in local storage for my storage and to also pause the body from loading, but so far I can't find a solution to this :/

So what I want to do is to stop the body bg color from flickering white or black based off the theme colors base color without blinking.

Thanks for taking the time to read my problem!

document.addEventListener("DOMContentLoaded", function() {
document.body.style.backgroundColor = "inherit";

if (document.readyState === 'complete') {

if(lightmodeON == true) {
  $('body').css({background: "#FFF"});
  console.log('loading white bg');
}

if(lightmodeON == false) {
  $('body').css({background: "#0a0a0a"});
  console.log('loading black bg');
}
}


if (typeof (Storage) !=="undefined") {
if (localStorage.themepref == 1 ) {
  lightmode();
}
else {
  darkmode();
  localStorage.themepref = 2;
}

if(lightmodeON == true) {
  $('body').css({background: "#FFF"});
  console.log('loading fffwhite bg');
}

if(lightmodeON == false) {
  $('body').css({background: "#0a0a0a"});
  console.log('loading black bg');
}
}

Here is my version of trying to make a work around.

var clickDelay = false;
var themeType = -1;
var lightmodeON = false;


window.onload = function() {
  console.log('First');

  if (event.target.readyState === 'interactive') {

      $('body').css({ background: ''});
      document.body.style.backgroundColor = "inherit";

   if(lightmodeON == true) {
     $('body').css({background: "#FFF"});
       document.body.style.backgroundColor = "#FFF";
   }

   if(lightmodeON == false) {
     $('body').css({background: "#0a0a0a"});
       document.body.style.backgroundColor = "#0a0a0a";
   }
}
    overloadBG();
};


document.addEventListener("DOMContentLoaded", function() {
  document.body.style.backgroundColor = "inherit";

  if (document.readyState === 'complete') {

    if(lightmodeON == true) {
      $('body').css({background: "#FFF"});
      console.log('loading white bg');
    }

    if(lightmodeON == false) {
      $('body').css({background: "#0a0a0a"});
      console.log('loading black bg');
    }
  }


  if (typeof (Storage) !=="undefined") {
    if (localStorage.themepref == 1 ) {
      lightmode();
    }
    else {
      darkmode();
      localStorage.themepref = 2;
    }

    if(lightmodeON == true) {
      $('body').css({background: "#FFF"});
      console.log('loading fffwhite bg');
    }

    if(lightmodeON == false) {
      $('body').css({background: "#0a0a0a"});
      console.log('loading black bg');
    }
  }



});

window.addEventListener('beforeunload', function (e) {
  $('body').css({ background: ''});

  if(lightmodeON == true) {
    $('body').css({background: "#FFF"});
      document.body.style.backgroundColor = "#FFF";
  }

  if(lightmodeON == false) {
    $('body').css({background: "#0a0a0a"});
      document.body.style.backgroundColor = "#0a0a0a";
  }

  document.body.style.backgroundColor = "inherit";
  overloadBG();
  
});

window.onbeforeunload = function () {
  //FUCK YOU BLINK
  //$('body').css({ background: 'none'});
  $('body').css({ background: ''});
    document.body.style.backgroundColor = "";

  if (typeof (Storage) !=="undefined") {
    if (localStorage.themepref == 1 ) {
        localStorage.themepref = 1;
        lightmode();
    }
    else {
      darkmode();
      localStorage.themepref = 2;
    }
  }

}

document.onreadystatechange = function() {


    $('body').css({ background: ''});
    document.body.style.backgroundColor = "transparent";

    if (event.target.readyState === 'interactive') {
        console.log('interactive');

             if(lightmodeON === true) {
               $('body').css({background: "#FFF"});
             }

             if(lightmodeON === false) {
               $('body').css({background: "#0a0a0a"});
             }
      }

      if (event.target.readyState === 'loading') {

          $('body').css({ background: ''});

       if(lightmodeON == true) {
         $('body').css({background: "#FFF"});
         $("body").css("cssText", "background: #FFF !important;");
       }

       if(lightmodeON == false) {
         $('body').css({background: "#0a0a0a"});
         $("body").css("cssText", "background: #0a0a0a !important;");
       }
    }
Trappings answered 27/8, 2019 at 0:7 Comment(0)
S
5

By the time DOMContentLoaded will fire, the browser may already have painted your page's background.

Here is a demo which does reload the page indefinitely, be sure to click "hide results" when you're done watching it, it may make your computer use some CPU resources (but no network ones).

/* 
  We use an iframe to avoid making real network requests 
  Below is the HTML content of this iframe
*/

const content = `<!DOCTYPE html>
<html>
  <head>
    <style>
      /* default bg color */
      body { background: red; }
    </style>
    <script>
      document.addEventListener('DOMContentLoaded', e => {
        document.body.style.backgroundColor = 'green';
        location.reload() // infinite reload...
      });
    <\/script>
  </head>
  <body>
    <div>
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi blandit cursus orci ut maximus. Phasellus pharetra lobortis pellentesque. Pellentesque et lacus et lorem facilisis ultrices. Maecenas sed ultricies nisi. Suspendisse feugiat finibus justo, id consectetur turpis aliquam ac. Proin sit amet laoreet velit. Nullam felis lectus, commodo imperdiet mollis in, ullamcorper eget tortor. Nullam at metus non diam faucibus aliquam. Vestibulum eu maximus risus, vitae elementum justo. Fusce commodo lacus a augue lobortis, quis ornare odio gravida. Quisque ultrices tempus tellus, vitae pulvinar est rutrum in. Duis ante erat, placerat sit amet imperdiet vitae, facilisis non mauris. Integer eu ex sapien.</p>
      <p>Morbi porttitor justo eu sodales efficitur. Integer ut suscipit libero, sed dapibus velit. Vestibulum laoreet neque ac odio consequat, a suscipit arcu tristique. Curabitur tempor, nisl eu porttitor feugiat, nibh lorem laoreet massa, ut porta tellus augue accumsan metus. Suspendisse sed venenatis neque. Aliquam non justo in tortor dictum suscipit. Duis eu lectus eu dui placerat luctus. Etiam et volutpat diam, nec ullamcorper tellus. Nullam nibh dui, bibendum a ipsum et, elementum tempor mi. Maecenas ut eros eu sem malesuada tincidunt. Aenean fermentum sit amet augue quis vulputate. Vivamus commodo pellentesque purus rhoncus suscipit. Proin et enim vel ipsum vulputate mollis venenatis ut enim. Curabitur eget velit mollis, luctus sem at, aliquam est. Donec quis elit erat. Nullam facilisis lorem nisl, a luctus purus tristique vel.</p>
      <p>Donec in magna at ante mollis sodales ac vitae mauris. Aliquam condimentum ligula nulla, scelerisque cursus neque consequat quis. Fusce vestibulum nisi vitae ipsum venenatis, a pharetra diam tempus. Aenean maximus enim orci, quis mollis neque sollicitudin et. Quisque viverra ipsum vitae magna varius, id ornare justo dictum. Quisque eleifend magna ac congue dignissim. Duis eu volutpat quam, quis placerat tellus. Pellentesque felis mi, imperdiet eu semper vel, hendrerit sit amet ex.</p>
    </div>
  </body>
</html>
`;
frame.src = URL.createObjectURL(new Blob([content], {type: 'text/html'}));
<iframe id="frame" widht="500" height="500"></iframe>

To workaround this issue, move your scripts at the top of the <body> and set the initial <body> color from Storage as soon as possible.
You will still be able to wait for the Document is ready before attaching other events.

/* 
  We use an iframe to avoid making real network requests 
  Below is the HTML content of this iframe
*/

const content = `<!DOCTYPE html>
<html>
  <head>
    <style>
      /* default bg color */
      body { background: red; }
    </style>
  </head>
  <body>
    <!-- move inside <body> -->
    <script>
      // first retrieve from Storage
      // const lightModeOn = locaStorage.getItem('lightmode') === "1";
      // StackSnippets don't allow Storage...
      const lightModeOn = true;
      // set directly the body's style, we're in so we don't need to wait, it's already available
      document.body.style.backgroundColor = 'green';
      // then wait for DOMContentLoaded if you wish to add listeners to other events
      document.addEventListener('DOMContentLoaded', e => {
      // $('.toggle').on('input', switchLight);

      // just to demonstrate it works
      location.reload();
   });
      
    <\/script>

    <div>
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi blandit cursus orci ut maximus. Phasellus pharetra lobortis pellentesque. Pellentesque et lacus et lorem facilisis ultrices. Maecenas sed ultricies nisi. Suspendisse feugiat finibus justo, id consectetur turpis aliquam ac. Proin sit amet laoreet velit. Nullam felis lectus, commodo imperdiet mollis in, ullamcorper eget tortor. Nullam at metus non diam faucibus aliquam. Vestibulum eu maximus risus, vitae elementum justo. Fusce commodo lacus a augue lobortis, quis ornare odio gravida. Quisque ultrices tempus tellus, vitae pulvinar est rutrum in. Duis ante erat, placerat sit amet imperdiet vitae, facilisis non mauris. Integer eu ex sapien.</p>
      <p>Morbi porttitor justo eu sodales efficitur. Integer ut suscipit libero, sed dapibus velit. Vestibulum laoreet neque ac odio consequat, a suscipit arcu tristique. Curabitur tempor, nisl eu porttitor feugiat, nibh lorem laoreet massa, ut porta tellus augue accumsan metus. Suspendisse sed venenatis neque. Aliquam non justo in tortor dictum suscipit. Duis eu lectus eu dui placerat luctus. Etiam et volutpat diam, nec ullamcorper tellus. Nullam nibh dui, bibendum a ipsum et, elementum tempor mi. Maecenas ut eros eu sem malesuada tincidunt. Aenean fermentum sit amet augue quis vulputate. Vivamus commodo pellentesque purus rhoncus suscipit. Proin et enim vel ipsum vulputate mollis venenatis ut enim. Curabitur eget velit mollis, luctus sem at, aliquam est. Donec quis elit erat. Nullam facilisis lorem nisl, a luctus purus tristique vel.</p>
      <p>Donec in magna at ante mollis sodales ac vitae mauris. Aliquam condimentum ligula nulla, scelerisque cursus neque consequat quis. Fusce vestibulum nisi vitae ipsum venenatis, a pharetra diam tempus. Aenean maximus enim orci, quis mollis neque sollicitudin et. Quisque viverra ipsum vitae magna varius, id ornare justo dictum. Quisque eleifend magna ac congue dignissim. Duis eu volutpat quam, quis placerat tellus. Pellentesque felis mi, imperdiet eu semper vel, hendrerit sit amet ex.</p>
    </div>
  </body>
</html>
`;
frame.src = URL.createObjectURL(new Blob([content], {type: 'text/html'}));
<iframe id="frame" widht="500" height="500"></iframe>
Spotter answered 29/8, 2019 at 2:26 Comment(7)
I almost have it, I found that your solution helps diagnose the issue with chrome painting the BG too quickly but I sometimes still get a black flicker and have tried implementing event listeners for loading / complete. What I do is -> document.body.style.backgroundColor = ""; if (localStorage.themepref == 1 ) { document.body.style.backgroundColor = "#FFF"; lightmode(); } else { document.body.style.backgroundColor = "#0a0a0a"; darkmode(); localStorage.themepref = 2; } at the top of my main.js script and follow your guide.Trappings
jsfiddle.net/msstenq/cqd1hxyt/3 here's a look at what I'm doing. Maybe I'm doing something wrong?Trappings
Sorry for late reply. Could you create a plnkr rather than a jsfiddle, will be easier to see what you really do.Spotter
Hey, here's a plunker link embed.plnkr.co/MDvcG4NCacf9JLbUp4h1Trappings
@Trappings Have you tried playing around with backface-visibility: visible|hidden|initial|inherit; values in your body selector? I know it sounds silly but that sometimes helps solving the flickering issues in Chrome/Canary browsers.Arctogaea
@MarkafaCreativeSolutions and / Kaiido thanks for the help and feedback. I've think ive made a solution based off your feedback and have tested the workaround and so far so good! Much appreciated and many thanks! Hope this helps others who get the same problem.Trappings
@Trappings I'm glad you came up with a solution. Your best bet would be to remove the background color from CSS all together and set it with JQuery/Javascript but can be solved in other ways, tooArctogaea
L
3
<html>
<head>
    <title></title>
    <script src="https://code.jquery.com/jquery-1.10.2.js"></script>
</head>
<body>
<header>
    <nav class="menu">
        <label for="darkmode"><input type="radio" name="theme" value="1" id="darkmode" class="themechange" />Dark Mode</label>
        <label for="lightmode"><input type="radio" checked="checked" name="theme" value="0" class="themechange" id="lightmode" />Light Mode</label>
    </nav>
</header>
<div class="content">
    <p>CSS (Cascading Style Sheets) is a representation style sheet language used for describing the look and formatting of HTML (Hyper Text Markup Language), XML (Extensible Markup Language) documents and SVG elements including (but not limited to) colors, layout, fonts, and animations. It also describes how elements should be rendered on screen, on paper, in speech, or on other media.</p>
    <img src="https://farm8.staticflickr.com/7632/16990947835_3894284fd8_b.jpg">

    <p>CSS (Cascading Style Sheets) is a representation style sheet language used for describing the look and formatting of HTML (Hyper Text Markup Language), XML (Extensible Markup Language) documents and SVG elements including (but not limited to) colors, layout, fonts, and animations. It also describes how elements should be rendered on screen, on paper, in speech, or on other media.</p>


    <p>CSS (Cascading Style Sheets) is a representation style sheet language used for describing the look and formatting of HTML (Hyper Text Markup Language), XML (Extensible Markup Language) documents and SVG elements including (but not limited to) colors, layout, fonts, and animations. It also describes how elements should be rendered on screen, on paper, in speech, or on other media.</p>
</div>
<style>
nav{background:#ddd; height:40px;}
img{width:350px}
body{margin:0px; padding:0px; color:#111; transition:all 0.5s;}
body.dark{color:#fff; background:#000;}
body.dark nav{background:#cc0000;transition:all 0.5s;}
</style>




<script>
$(function(){
    $("#lightmode").click(function(){
            $("body").removeClass("dark");
    })
    $("#darkmode").click(function(){
            $("body").addClass("dark");
    })
})
</script>
</body>
</html>

Toggle the class name on the selection of theme and add transition:all 0.5s in css code to make transition smooth.

Lorenzen answered 3/9, 2019 at 7:7 Comment(0)
A
1

Here's what I would suggest for a quick solution: I would first remove the background-color property from the CSS all together from the element which causes this clashing property renderings.

Then, add a function to my JS file which checks the status of the visitor's setting according the theme and apply the background directly from JS.

if(lightmodeON == true) {
  $('body').css({background: "#FFF"});
  console.log('loading white bg');
}

if(lightmodeON == false) {
  $('body').css({background: "#0a0a0a"});
  console.log('loading black bg');
}
}

And maybe include a fallback within the PHP code (in the samples you posted on jsfiddle I saw that you're coding in PHP) in case the javascript is disabled/not supported in a visitor's browser and echo out the CSS code, after the tags, not before because browsers and DOM model acts as a cued sheet of commands, not like object oriented code and it'll always load from top to bottom. So the loading hierarchy will affect how things work, and sometimes may cause clashes with what you're trying to achieve:

<script src="yourScriptHere.js">
//Some parameters if needed (Such as state of theme preference retrieved from the session and passed into a variable). For the sake of this example:
var lightmodeON = <?php echo $_SESSION('themePreference');?>;
</script>
<style>/*Then your style here*/</style>
<!--OR-->
<link href="yourStyleSheets.css" rel="stylesheet" />

There is no reason to include the property in the CSS if it causes clashes with your completed product. But always make sure to add a comment in the CSS file for you to see later that you've excluded that property and where you put it in order not to suffer in future edits like so:

body {
    /* background-color property is set in JS file and excluded from CSS */
    margin: 0;
    [...]
}

I'm assuming excluding things from CSS in this case won't hurt since it seems like you will be using the HTML/CSS/JS as a complete application package here to assure full functionality anyways.

Other than this, you can also try playing around with background-visibility property within the CSS file to the elements where the flickering occurs, as this sometimes weirdly helps solving some flickering problems.

I'd also recommend you to create an Ajax call and store the theme preference setting in a session object so this way you pass and can access it through JS in other pages, and/or visitor comes back to the site after closing the browser window.

I hope this helps. Cheers!

Arctogaea answered 3/9, 2019 at 14:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.