Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to animate linear-gradient without using background-position?

I'm trying to create content loader but got a performance problem with background animation. It's smooth when there are only a few elements on the screen but dramatically drops fps while increasing stub elements count to 20-30. Now I know that animating background-position property is a bad idea and it is better to use transforms for this. but how can I do this? I'd like to keep seamless animation. Gradient should be relative to screen, not to container.

Here is some code:

const cardsRoot = document.getElementById('cards')
const addButton = document.getElementById('add')
const card = document.getElementsByClassName('card')[0]
let cardsCount = 1

addButton.addEventListener('click', () => {
  cardsRoot.innerHTML = ''
  cardsCount++
  for (let i = 0; i < cardsCount; i++) {
    let cardClone = card.cloneNode(true)
    cardsRoot.appendChild(cardClone)
  }
})
body {
  padding: 40px;
}

.card {
  display: flex;
  margin-top: 20px;
}

.stub {
  width: 300px;
  height: 12px;
  margin: 8px;
  border-radius: 8px;
  background: linear-gradient(to right, rgba(0, 0, 0, 0.04), rgba(0, 0, 0, 0.1) 10%, rgba(0, 0, 0, 0.04) 20%) fixed;
  animation: stub 1.3s linear infinite;
  margin-bottom: 8px;
}

.circle {
  width: 40px;
  height: 40px;
  margin-right: 12px;
  border-radius: 20px;
}

@keyframes stub {
  0% { background-position: 0vw; }
  100% { background-position: 100vw; }
}
<button id="add">
  ADD CARD
</button>

<div id="cards">
  <div class="card">
    <div>
      <div class="stub circle"></div>
    </div>
    <div>
      <div class="stub"></div>
      <div class="stub"></div>
      <div class="stub"></div>
    </div> 
  </div>
</div>

And demo: https://jsfiddle.net/3da4uzm2/57/

like image 887
Anton Avatar asked Apr 16 '19 13:04

Anton


People also ask

Is it possible to add a gradient background to an element without using an image?

You can also easily add a gradient background to a div (or any other HTML element) without using images, using the following CSS rules. BTW, I've set the gradient to start at red and end in blue.

Can you animate linear gradient CSS?

Animating backgrounds with CSS can be a great way to grab a user's attention or bring a design to be more modern. We can acheive this by using a combination of linear-gradient CSS function and @keyframes . The basic idea is that we have a huge background with 4 colours that transition to create the gradient background.

Does linear gradient work with background color?

Because <gradient> s belong to the <image> data type, they can only be used where <image> s can be used. For this reason, linear-gradient() won't work on background-color and other properties that use the <color> data type.


1 Answers

You can replace the animation using pseudo element where you apply translation. The trick is to consider fixed element to replace background-attachment:fixed, then you make the element twice bigger as the screen and you translate it from left to right.

const cardsRoot = document.getElementById('cards')
const addButton = document.getElementById('add')
const card = document.getElementsByClassName('card')[0]
let cardsCount = 1

addButton.addEventListener('click', () => {
  cardsRoot.innerHTML = ''
  cardsCount++
  for (let i = 0; i < cardsCount; i++) {
    let cardClone = card.cloneNode(true)
    cardsRoot.appendChild(cardClone)
  }
})
body {
  padding: 40px;
}

.card {
  display: inline-flex;
  margin-top: 20px;
}

.stub {
  width: 150px;
  height: 12px;
  margin: 8px;
  border-radius: 8px;
  margin-bottom: 8px;
  position:relative;
  z-index:0;
  /*overflow:hidden; this is no more working, using mask instead */
  -webkit-mask:linear-gradient(#fff 0 0);
  /* OR clip-path:inset(0) */
}
.stub:before {
  content:"";
  position:fixed;
  z-index:-1;
  top:0;
  right:0;
  width:200vw;
  bottom:0;
  background: 
    linear-gradient(rgba(0, 0, 0, 0.04),rgba(0, 0, 0, 0.04)) left/50% 100%,
    linear-gradient(to right, rgba(0, 0, 0, 0.04), rgba(0, 0, 0, 0.1) 10%, rgba(0, 0, 0, 0.04) 20%) right/50% 100%;
  background-repeat:no-repeat;
  animation: stub 1.3s linear infinite;
  pointer-events:none;
}

.circle {
  width: 40px;
  height: 40px;
  margin-right: 12px;
  border-radius: 20px;
}

@keyframes stub {
  0% { transform:translate(0); }
  100% { transform:translate(50%); }
}
<button id="add">
  ADD CARD
</button>

<div id="cards">
  <div class="card">
    <div>
      <div class="stub circle"></div>
    </div>
    <div>
      <div class="stub"></div>
      <div class="stub"></div>
      <div class="stub"></div>
    </div> 
  </div>
  <div class="card">
    <div>
      <div class="stub circle"></div>
    </div>
    <div>
      <div class="stub"></div>
      <div class="stub"></div>
      <div class="stub"></div>
    </div> 
  </div>
</div>

To better understand what is happening here is a simplified version with only one element where I also changed the gradient colors.

body:before {
  content:"";
  position:fixed; /*relative to the screen*/
  z-index:-1;
  top:0;
  right:0;
  width:200vw; /*2x100vw*/
  bottom:0;
  background: 
    /*will cover the left area while sliding*/
    linear-gradient(red,red) left/50% 100%, /*the red should be green*/
    /*the main gradient*/
    linear-gradient(to right, green, blue 10%, green 20%) right/50% 100%;
  background-repeat:no-repeat;
  animation: stub 3s linear infinite;
}
@keyframes stub {
  0% { transform:translate(0); }
  100% { transform:translate(50%); } /*50% will be 200vw/2 = 100vw*/
}

Related to understand the trick behind the background values: Using percentage values with background-position on a linear-gradient

like image 86
Temani Afif Avatar answered Oct 05 '22 12:10

Temani Afif