CSS transition on body background

I’m trying to change background on body via JavaScript. Most of the info online use CSS :hover style transitions. What I want is doing it via JavaScript. The background-Color changes without animation, even when I have added transition. Why?


body {
        background-color: silver;
        color: white;
        font-family: Arial, Helvetica, sans-serif;
        overflow: hidden;
        transition: background-color 29s ease-in;
      }



document.body.style.backgroundColor = "red";

Can you paste your full code or a link to the example?


<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Test</title>
    <style>
    
      h1 {
        font-size: 4em;
        margin-top: 0em;
      }

      body {
        background-color: silver;
        color: white;
        font-family: Arial, Helvetica, sans-serif;
        overflow: hidden;
        transition: background-color 29s ease-in;
      }
    
      .main {
        width: 400px;
        padding: 2em;
      }
      .main h1 {
        margin-bottom: 0;
      }

      .fade-in {
        position: relative;
        animation: fadeIn ease 1s;
      }

      @keyframes fadeIn {
        0% {
          opacity: 0;
          left: 200px;
        }
        70% {
          left: 20px;
          transform: scale(1.2);
        }
        100% {
          opacity: 1;
          left: 0px;
        }
      }
    </style>
  </head>
  <body>
    <div class="main">
      <h1 class="fade-in">hello world</h1>
    </div>

    <script type="text/javascript">
      const hours = new Date().getHours();
      const isDarkmodeTime = hours >= 15 || hours <= 8;
      const body = document.querySelector("body");
      body.style.color = isDarkmodeTime ? "white" : "black";
      body.style.backgroundColor = isDarkmodeTime ? "black" : "white";
    </script>
  </body>
</html>

I like to think of it like this:

state 1 e.g. .open{}
state 2 e.g. .closed{}

The transition is between the two “states” or selectors.

Lets say you want to transition something 2s to open 2s to close.
You would have to apply the transition to both open and close selectors.

Transitions need both a value change and a CSS selector change to work.

In the above code you haven’t changed the class or selector just the color and by using JS directly on the element you have effectively “in-lined” the color so it kind of like doing <body style = "background-color: red" >

You’ve got 3 options:
-add the transition to your change on the element e.g.

document.body.style = "background-color: red; 
transition: background-color 29s ease-in"

-add a CSS class and change the body class e.g

 .redBody{ background-color: red; 
transition: background-color 29s ease-in;}
document.body.classList.add('redBody')
  • use WAAPI (web animations api)
document.body.animate([ { background-color: silver, easing: 'ease-in' },
                  { background-color: red, easing: 'ease-in' },
                  ],
                {duration: 29000, fill: 'forwards'});
1 Like

In addition to the excellent points Steve made, the other option might be to use a CSS animation and set it to not loop :slight_smile:

1 Like

BTW, it’s probably no big deal for a 29s animation, but if your doing fast animations directly on the element its probably best to use requestAnimationFrame e.g

window.requestAnimationFrame(() => 
document.body.style = "background-color: red; 
transition: background-color 29s ease-in"
 )

WAAPI does RAF by default apparently.
I think you still need to use it for classList changes.

I mostly use WAAPI or transitions now. WAAPI is pretty performant. :slightly_smiling_face:

1 Like

Applying this does not initiate the animation from silver til red:

document.body.style = "background-color: red; 
transition: background-color 29s ease-in;"

// Not working either

 .fadeBG{ background-color: red; 
transition: background-color 29s ease-in;}
document.body.classList.add('fadeBG')

My bad document.body.style is read only
try:
document.body.setAttribute("style", "background-color: blue; transition: background-color 29s ease-in");

.fadeBG{ background-color: red; 
transition: background-color 29s ease-in;}

This won’t work because body.style.backgroundColor = 'black'

The inline style is more specific than a ‘class’

You either set on the element or go class all the way :slightly_smiling_face:

why does not this work? document.body.classList.add()
document.body.setAttribute does not animate the body bg either

I just opened up your doc in Chrome and in the console pasted in that, then hit enter
document.body.setAttribute("style", "background-color: blue; transition: background-color 29s ease-in")
and it turned blue after a while…

The class change wont work unless you remove:
body.style.backgroundColor = isDarkmodeTime ? "black" : "white";

"inline" styles on the element have more CSS specificity than anything except !important;

I personally would not set the <body> with JS.

You could try using CSS custom properties (vars) and set those with JS so you can still use class without !important

I modified your code to work with CSS custom properties.

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Test</title>
    <style>
      :root {
            --main-color: white;
            --bg-color: silver;
        }  
      h1 {
        font-size: 4em;
        margin-top: 0em;
      }
      .body {
        background-color: var(--bg-color);
        color: var(--main-color);
        font-family: Arial, Helvetica, sans-serif;
        overflow: hidden;
        transition: background-color 29s ease-in;
      }
    .fadeBG{ background-color: red;
        transition: background-color 29s ease-in;}
      .main {
        width: 400px;
        padding: 2em;
      }
      .main h1 {
        margin-bottom: 0;
      }
      .fade-in {
        position: relative;
        animation: fadeIn ease 1s;
      }
      @keyframes fadeIn {
        0% {
          opacity: 0;
          left: 200px;
        }
        70% {
          left: 20px;
          transform: scale(1.2);
        }
        100% {
          opacity: 1;
          left: 0px;
        }
      }
    </style>
    <script type="text/javascript">
      const hours = new Date().getHours();
      const isDarkmodeTime = hours >= 15 || hours <= 8;
      const root = document.documentElement;
      if(isDarkmodeTime){
        root.style.setProperty('--main-color', 'white');
        root.style.setProperty('--bg-color', 'black');
      }
      else{
        root.style.setProperty('--main-color', 'black');
        root.style.setProperty('--bg-color', 'white');
      }
      const fade = () => document.body.className = 'fadeBG'
    </script>
  </head>
  <body class = 'body'>
    <div class="main">
      <h1 class="fade-in">hello world</h1>
    </div>
    <br>
    <button onclick="fade()">Fade me</button>
  </body>
</html>

Just run that and it should be kind of what your after :slightly_smiling_face:

Thanks for the help but the animation does not load on page load/refresh. I have to add a button listener for it to animate. Something similar to @kirupa web animation book chapter 6 where you click a button to apply js animations. I guess I can add an event listener for onLoad and add the fadeIn animation.

Fade in on page onLoad did the trick with setAttribute(“style”,”…”)

If you just want to change the background color on page load, the CSS animation route is quite handy:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Change Background Color</title>

  <style>
    body {
      background-color: yellow;
      animation-name: change_color;
      animation-duration: 10s;
      animation-iteration-count: 1;
    }

    @keyframes change_color {
      100% {
        background-color: black;
      }
    }
  </style>
</head>
<body>
  
</body>
</html>
1 Like

Yeah like @kirupa said just use a simple animation.

If you want to do night mode just use css vars like above.

That way you are setting them on the <html> / root and you don’t have to put your script in the body.

They should work fine with your animation.

The vars will do the changes before the body loads so you shouldn’t get a FOUC. :slightly_smiling_face:

2 Likes

Check out the source on the main kirupa.com site on how dark mode is done. You’ll see a giant list of css vars at the very top. The script for loading the right set of vars runs before the rest of the page renders (just above the body element), and that ensures there is no FOUC with only a very small slowdown in rendering time since JS is running before the page can start displaying.

2 Likes

While in the process of recording a video on generating random colors, I stumbled upon another approach that I used for animating something with a transition on page load: kirupa/simple_random_color_changer.htm at master · kirupa/kirupa · GitHub

I use an empty CSS keyframes rule to trigger the animationiteration event!

2 Likes

That’s a great way to do it. It looks like the animation loop is infinite too. Thank you for sharing.

1 Like

I had fun just following along and messing around with this myself.

1 Like