csscss-animationsbackground-positionbackground-size

How to animate background-position using percentages when background-size is 100%?


Take the following example:

html {
    height: 100%;
}

body {
    display: flex;
    justify-content: center;
    align-items: center;
    background-color: black;
    height: 100%;
    margin: 0;
}

#main {
    background: #222222;
    position: relative;
    flex: 640px 0 0;
    height: 360px;
}

@keyframes stars {
	0% {
        background-position: 0 0;
    }
	100% {
        background-position: -100% 0;
    }
}

#stars {
    animation: stars 10s linear infinite;
    background-image: url('https://i.imgur.com/nyFndCj.png');
    background-size: 100% 100%;
    background-repeat: repeat repeat;
    position: absolute;
    width: 100%;
    height: 100%;
}
<div id="main">
  <div id="stars"></div>
</div>

The idea here is to animate the stars moving from one side to the other by changing the background position using percentages. I can get this working using px, for instance, but that requires me to know the width in advance (in this case 640px) and if I want to change the width/height of #main I need to change the animation values and I want to avoid that, thus the percentages. Also, I want to acomplish this with CSS only, no JavaScript at all.


Solution

  • Make the size of the background smaller and use scale to rectify this by increasing the size of the container. Then you will be able to animte the background like you want:

    body {
      background-color: black;
      height: 100vh;
      margin: 0;
    }
    
    #main {
      background: #222222;
      position: relative;
      width: 100%;
      height: 360px;
      overflow: hidden;
    }
    
    #stars {
      animation: stars 10s linear infinite;
      background-image: url('https://i.imgur.com/nyFndCj.png');
      background-size: 50% 100%;
      position: absolute;
      left: 0;
      right: 0;
      height: 100%;
      transform: scaleX(2);
    }
    
    @keyframes stars {
      0% {
        background-position: left;
      }
      100% {
        background-position: right;
      }
    }
    <div id="main">
      <div id="stars"></div>
    </div>

    Here is another idea without scale where you also make the element twice bigger using right:-100% or left:-100% or width:200%

    body {
      background-color: black;
      height: 100vh;
      margin: 0;
    }
    
    #main {
      background: #222222;
      position: relative;
      width: 100%;
      height: 360px;
      overflow: hidden;
    }
    
    #stars {
      animation: stars 10s linear infinite;
      background-image: url('https://i.imgur.com/nyFndCj.png');
      background-size: 50% 100%;
      position: absolute;
      left: 0;
      right: -100%;
      height: 100%;
    }
    
    @keyframes stars {
      0% {
        background-position: left;
      }
      100% {
        background-position: right;
      }
    }
    <div id="main">
      <div id="stars"></div>
    </div>

    Here is another simplification considering pseudo element:

    body {
      background-color: black;
      height: 100vh;
      margin: 0;
    }
    
    #main {
      position: relative;
      width: 100%;
      height: 360px;
      overflow: hidden;
      z-index:0;
    }
    #main:before {
      content:"";
      position:absolute;
      z-index:-1;
      top:0;
      left:0;
      right:-100%;
      bottom:0;
      animation: stars 10s linear infinite;
      background: 
        url('https://i.imgur.com/nyFndCj.png') left/50% 100%,
        #222222;
    }
    
    @keyframes stars {
      100% {
        background-position: right;
      }
    }
    <div id="main">
    </div>

    In all the case, the trick is to avoid having 100% 100% in the background-size or it will be impossible to animate using percentage.


    I have used left/right for simplification which is equivalent to 0% 50%/100% 50%. Simply switch between both to change the direction.

    More details here: https://stackoverflow.com/a/51734530/8620333


    And since we have made the size of the container bigger, we can also animate it using translate to have better performance:

    body {
      background-color: black;
      height: 100vh;
      margin: 0;
    }
    
    #main {
      position: relative;
      width: 100%;
      height: 360px;
      overflow: hidden;
      z-index:0;
    }
    #main:before {
      content:"";
      position:absolute;
      z-index:-1;
      top:0;
      left:0;
      right:-100%;
      bottom:0;
      animation: stars 10s linear infinite;
      background: 
        url('https://i.imgur.com/nyFndCj.png') left/50% 100%,
        #222222;
    }
    
    @keyframes stars {
      100% {
        transform: translateX(-50%);
      }
    }
    <div id="main">
    </div>

    With scaling:

    body {
      background-color: black;
      height: 100vh;
      margin: 0;
    }
    
    #main {
      position: relative;
      width: 100%;
      height: 360px;
      overflow: hidden;
      z-index:0;
    }
    #main:before {
      content:"";
      position:absolute;
      z-index:-1;
      top:0;
      left:0;
      right:0;
      bottom:0;
      transform:scaleX(2);
      transform-origin:left;
      animation: stars 10s linear infinite;
      background: 
        url('https://i.imgur.com/nyFndCj.png') left/50% 100%,
        #222222;
    }
    
    @keyframes stars {
      100% {
        transform:scaleX(2) translateX(-50%);
      }
    }
    <div id="main">
    </div>

    In the other direction

    body {
      background-color: black;
      height: 100vh;
      margin: 0;
    }
    
    #main {
      position: relative;
      width: 100%;
      height: 360px;
      overflow: hidden;
      z-index:0;
    }
    #main:before {
      content:"";
      position:absolute;
      z-index:-1;
      top:0;
      left:0;
      right:0;
      bottom:0;
      transform:scaleX(2);
      transform-origin:right;
      animation: stars 10s linear infinite;
      background: 
        url('https://i.imgur.com/nyFndCj.png') left/50% 100%,
        #222222;
    }
    
    @keyframes stars {
      100% {
        transform:scaleX(2) translateX(50%);
      }
    }
    <div id="main">
    </div>