3

I have made this loading placeholder css animation. And on white bacground it looks correct because the animated/moving gradient is white with 20% opacity.

However sometimes the placeholder will be on a different coloured background and the the moving part also becomes visible on the background instead of just on the coloured darkened (which is undesired). please see snippet below:

.ph-item {
  position: relative;
  margin-bottom: 10px;
  overflow: hidden;
  border-radius: 2px;
}

.ph-item,
.ph-item *,
.ph-item ::after,
.ph-item ::before {
  box-sizing: border-box;
}

.ph-item::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 50%;
  z-index: 1;
  width: 500%;
  margin-left: -250%;
  animation: phAnimation 0.8s linear infinite;
  background: linear-gradient(to right, rgba(255, 255, 255, 0) 46%, rgba(255, 255, 255, 0.35) 50%, rgba(255, 255, 255, 0) 54%) 50% 50%;
}

.ph-item > * {
  padding-right: 10px;
}

.ph-row {
  margin-bottom: 5px;
}

.ph-row div {
  border-radius: 3px;
  height: 10px;
  background-color: rgba(0, 0, 0, 0.2);
}

.ph-row .standard,
.ph-row.big div {
  height: 20px;
  margin-right: 10px;
}


.ph-float-right {
  float: right;
  padding-left: 10px;
  margin-right: auto;
}

.ph-col-2 {
  width: 16.66667%;
  display: inline-block;
}

.ph-col-4 {
  width: 33.33333%;
  display: inline-block;
}

.ph-col-6 {
  width: 50%;
  display: inline-block;
}

.ph-col-8 {
  width: 66.66667%;
  display: inline-block;
}

.ph-col-10 {
  width: 83.33333%;
  display: inline-block;
}

.ph-col-12 {
  width: 100%;
  display: inline-block;
}

.ph-avatar {
  position: relative;
  width: 100%;
  min-width: 50px;
  background-color: rgba(0, 0, 0, 0.2);
  border-radius: 50%;
  overflow: hidden;
}

.ph-avatar::before {
  content: "";
  display: block;
  padding-top: 100%;
}

@keyframes phAnimation {
  0% {
    transform: translate3d(-30%, 0, 0);
  }

  100% {
    transform: translate3d(30%, 0, 0);
  }
}
<div style="width: 500px; height: 50px; background-color: darkblue; padding: 20px;">
  <div class="ph-item" style="max-width: 360px;">
    <div class="ph-col-2">
      <div class="ph-avatar"></div>
    </div>
    <div class="ph-col-10 ph-float-right">
      <div class="ph-row">
        <div class="ph-col-6 standard"></div>
        <div class="ph-col-10"></div>
      </div>
    </div>
  </div>
</div>
<div style="width: 500px; height: 50px; background-color: white; padding: 20px;">
  <div class="ph-item" style="max-width: 360px;">
    <div class="ph-col-2">
      <div class="ph-avatar"></div>
    </div>
    <div class="ph-col-10 ph-float-right">
      <div class="ph-row">
        <div class="ph-col-6 standard"></div>
        <div class="ph-col-10"></div>
      </div>
    </div>
  </div>
</div>

My question is, is it possible somehow to mask the animation to only be rendered on top of the darkened parts (ph-avatar and ph-col-xx) ?

And how is this achieved?

Rasmus Puls
  • 3,009
  • 7
  • 21
  • 58
  • a similar effect but different idea: https://stackoverflow.com/a/55710038/8620333 – Temani Afif Jun 05 '19 at 09:44
  • 1
    @TemaniAfif looks like a duplicate to me even if the initial question is not in the same terms. the final result requires the same method to be fully and easy working . – G-Cyrillus Jun 05 '19 at 12:16

2 Answers2

4

edit This is actually a duplicate of Background animation performance

Another approach could to use the after pseudo with the linear gradient reset with background-position. to keep each pseudo with coherent pieces of gradient, background-attachment will put things together. background-size will help also.

Demo of the idea below. CSS commented where updated or modified

.ph-item {
  position: relative;
  margin-bottom: 10px;
  overflow: hidden;
  border-radius: 2px;
}

.ph-item,
.ph-item *,
.ph-item ::after,
.ph-item ::before {
  box-sizing: border-box;
}
   /* selector removed 

.ph-item::before 

       and replaced by : */
.ph-avatar::after,
.ph-row .standard::after,
.ph-row .ph-col-10::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 50%;
  z-index: 1;
  width: 500%;
  margin-left: -250%;
  animation: phAnimation 2s linear infinite;/* duration to set to your needs */
  background: linear-gradient(
      to right,
      rgba(255, 255, 255, 0) 46%,
      rgba(255, 255, 255, 0.35) 50%,
      rgba(255, 255, 255, 0) 54%
    )
    50% 50% 
    rgba(0, 0, 0, 0.2);/* move here & added */
  background-attachment: fixed;/*  added */
  background-size: 1000px auto;/*  added */
}

.ph-item > * {
  padding-right: 10px;
}

.ph-row {
  margin-bottom: 5px;
}

.ph-row div {
  border-radius: 3px;
  height: 10px;
 /*background-color: rgba(0, 0, 0, 0.2); or move animation here from pseudo*/
}

.ph-row .standard,
.ph-row.big div {
  height: 20px;
  margin-right: 10px;
}

.ph-float-right {
  float: right;
  padding-left: 10px;
  margin-right: auto;
}

.ph-col-2 {
  width: 16.66667%;
  display: inline-block;
}

.ph-col-4 {
  width: 33.33333%;
  display: inline-block;
}

.ph-col-6 {
  width: 50%;
  display: inline-block;
  position: relative;/*  added */
  overflow: hidden;/*  added */
}

.ph-col-8 {
  width: 66.66667%;
  display: inline-block;
}

.ph-col-10 {
  width: 83.33333%;
  display: inline-block;
  position: relative;/*  added */
  overflow: hidden;/*  added */
}

.ph-col-12 {
  width: 100%;
  display: inline-block;
}

.ph-avatar {
  position: relative;
  width: 100%;
  min-width: 50px;
  border-radius: 50%;
  overflow: hidden;
}

.ph-avatar::before {
  content: "";
  display: block;
  padding-top: 100%;
}

@keyframes phAnimation {
  0% {
    background-position: -1000px 0;/*  modified */
  }

  100% {
    background-position: 1000px 0;/*  modified */
  }
}
<div style="width: 500px; height: 50px; background-color: darkblue; padding: 20px;">
  <div class="ph-item" style="max-width: 360px;">
    <div class="ph-col-2">
      <div class="ph-avatar"></div>
    </div>
    <div class="ph-col-10 ph-float-right">
      <div class="ph-row">
        <div class="ph-col-6 standard"></div>
        <div class="ph-col-10"></div>
      </div>
    </div>
  </div>
</div>
<div style="width: 500px; height: 50px; background-color: white; padding: 20px;">
  <div class="ph-item" style="max-width: 360px;">
    <div class="ph-col-2">
      <div class="ph-avatar"></div>
    </div>
    <div class="ph-col-10 ph-float-right">
      <div class="ph-row">
        <div class="ph-col-6 standard"></div>
        <div class="ph-col-10"></div>
      </div>
    </div>
  </div>
</div>
<div style="width: 500px; height: 50px; background-color: tomato; padding: 20px;">
  <div class="ph-item" style="max-width: 360px;">
    <div class="ph-col-2">
      <div class="ph-avatar"></div>
    </div>
    <div class="ph-col-10 ph-float-right">
      <div class="ph-row">
        <div class="ph-col-6 standard"></div>
        <div class="ph-col-10"></div>
      </div>
    </div>
  </div>
</div>
<div style="width: 500px; height: 50px; background-color: gold; padding: 20px;">
  <div class="ph-item" style="max-width: 360px;">
    <div class="ph-col-2">
      <div class="ph-avatar"></div>
    </div>
    <div class="ph-col-10 ph-float-right">
      <div class="ph-row">
        <div class="ph-col-6 standard"></div>
        <div class="ph-col-10"></div>
      </div>
    </div>
  </div>
</div>
<div style="width: 500px; height: 50px; background-color: purple; padding: 20px;">
  <div class="ph-item" style="max-width: 360px;">
    <div class="ph-col-2">
      <div class="ph-avatar"></div>
    </div>
    <div class="ph-col-10 ph-float-right">
      <div class="ph-row">
        <div class="ph-col-6 standard"></div>
        <div class="ph-col-10"></div>
      </div>
    </div>
  </div>
</div>
<div style="width: 500px; height: 50px; background-color: turquoise; padding: 20px;">
  <div class="ph-item" style="max-width: 360px;">
    <div class="ph-col-2">
      <div class="ph-avatar"></div>
    </div>
    <div class="ph-col-10 ph-float-right">
      <div class="ph-row">
        <div class="ph-col-6 standard"></div>
        <div class="ph-col-10"></div>
      </div>
    </div>
  </div>
</div>
<div style="width: 500px; height: 50px; background-color: gray; padding: 20px;">
  <div class="ph-item" style="max-width: 360px;">
    <div class="ph-col-2">
      <div class="ph-avatar"></div>
    </div>
    <div class="ph-col-10 ph-float-right">
      <div class="ph-row">
        <div class="ph-col-6 standard"></div>
        <div class="ph-col-10"></div>
      </div>
    </div>
  </div>
</div>
<div style="width: 500px; height: 50px; background-color: teal; padding: 20px;">
  <div class="ph-item" style="max-width: 360px;">
    <div class="ph-col-2">
      <div class="ph-avatar"></div>
    </div>
    <div class="ph-col-10 ph-float-right">
      <div class="ph-row">
        <div class="ph-col-6 standard"></div>
        <div class="ph-col-10"></div>
      </div>
    </div>
  </div>
</div>

Note , the background-animation can be set directly inside elements , pseudo are not necessary unless they have another purpose. possible option https://codepen.io/gc-nomade/pen/byJOJa

G-Cyrillus
  • 101,410
  • 14
  • 105
  • 129
  • Hey, thanks a lot. I don't however see how these two questions are related. I'm asking for a visual result using css, the other question is about performance of a product that though is quite similar to what I am trying to achieve with my result. I think this solution is very smooth, and i do not worry about its performance, as I will only be showing one instance and only for a brief period. Thanks a lot :) – Rasmus Puls Jun 05 '19 at 13:23
  • @RasmusPuls it is only related about the method used (mainly background-attachment) , the performance is extra and good to know too ;) The op also thought about transform but did not go through the idea. He thought too *Gradient should be relative to screen, not to container* which brings us here ;) – G-Cyrillus Jun 05 '19 at 13:31
2

perhaps one option would be to mask two svg images and animate the properties of the mask. I am not really familiar with that technique personally, but maybe this helps:

https://tympanus.net/codrops/css_reference/mask-composite/

however, if you want to do it in-html (which I would prefer personally as well), you can achieve that effect via a bit of trickery:

.ph-item {
  position: relative;
  margin-bottom: 10px;
  overflow: hidden;
  border-radius: 2px;
}

.ph-item,
.ph-item *,
.ph-item ::after,
.ph-item ::before {
  box-sizing: border-box;
}

.highlighted {
  position: relative;
  overflow: hidden;
}

.highlighted::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  z-index: 1;
  width: 100px;
  animation: 1s linear infinite;
  animation-name: phAnimation;
  background: red;
/*   background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.35) 50%, rgba(255, 255, 255, 0) 100%) 50% 50%; */
}

.ph-avatar.highlighted::before {
  animation-name: phAnimationAvatar;
}

.ph-item > * {
  padding-right: 10px;
}

.ph-row {
  margin-bottom: 5px;
}

.ph-row div {
  border-radius: 3px;
  height: 10px;
  background-color: rgba(0, 0, 0, 0.2);
}

.ph-row .standard,
.ph-row.big div {
  height: 20px;
  margin-right: 10px;
}


.ph-float-right {
  float: right;
  padding-left: 10px;
  margin-right: auto;
}

.ph-col-2 {
  width: 16.66667%;
  display: inline-block;
}

.ph-col-4 {
  width: 33.33333%;
  display: inline-block;
}

.ph-col-6 {
  width: 50%;
  display: inline-block;
}

.ph-col-8 {
  width: 66.66667%;
  display: inline-block;
}

.ph-col-10 {
  width: 83.33333%;
  display: inline-block;
}

.ph-col-12 {
  width: 100%;
  display: inline-block;
}

.ph-avatar {
  position: relative;
  width: 100%;
  min-width: 50px;
  height: 50px;
  background-color: rgba(0, 0, 0, 0.2);
  border-radius: 50%;
  overflow: hidden;
}


@keyframes phAnimationAvatar {
  0% {
    transform: translate3d(-100px, 0, 0);
  }
  
  10% {
    transform: translate3d(-50px, 0, 0);
  }
  
  20% {
    transform: translate3d(0px, 0, 0);
  }
  
  30% {
    transform: translate3d(50px, 0, 0);
  }

  100% {
    transform: translate3d(100px, 0, 0);
  }
}

@keyframes phAnimation {
  0% {
    transform: translate3d(-100px, 0, 0);
  }
  
  11% {
    transform: translate3d(-100px, 0, 0);
  }

  50% {
    transform: translate3d(85px, 0, 0);
  }
  
  100% {
    transform: translate3d(335px, 0, 0);
  }
}
<div style="width: 500px; height: 50px; background-color: darkblue; padding: 20px;">
  <div class="ph-item" style="max-width: 360px;">
    <div class="ph-col-2">
      <div class="ph-avatar highlighted"></div>
    </div>
    <div class="ph-col-10 ph-float-right">
      <div class="ph-row">
        <div class="ph-col-6 standard highlighted"></div>
        <div class="ph-col-10 highlighted"></div>
      </div>
    </div>
  </div>
</div>
<div style="width: 500px; height: 50px; background-color: white; padding: 20px;">
  <div class="ph-item" style="max-width: 360px;">
    <div class="ph-col-2">
      <div class="ph-avatar highlighted"></div>
    </div>
    <div class="ph-col-10 ph-float-right">
      <div class="ph-row">
        <div class="ph-col-6 standard highlighted"></div>
        <div class="ph-col-10 highlighted"></div>
      </div>
    </div>
  </div>
</div>

the drawback is that you would need to recalibrate your animation for different placeholders, but its still better than using static svgs for each placeholder element.

EDIT: just a heads-up, there seems to be an issue of how safari renders this, though chrome and firefox work fine.

  • 1
    Yes it seems that safari is ignoring border radius / rounded shapes on the `before` element. Other than that it looks as desired. Only drawback is that it is not very generic. If I needed to place one more greyed div somewhere else, that would need its own unique animation as well to align the flow of the scrolling gradient element. Which is why I was hoping for some kind of masking property that would cause pixels to only be rendered on elements with this property while the animation runs in the parent. I'm newbie to css but this would be easy in most image editing software such as photoshop. – Rasmus Puls Jun 05 '19 at 11:13
  • 1
    yeah I know what you mean. I did test rendering a masked SVG within the HTML code (so that it would be easy to animate/modify its components later on) but the browsers do not seem to pick up on that masking. unfortunately the css mask property also seems to only work with external images, so I don't know if you could somehow add in-html svgs to it. – Eugene Ghanizadeh Khoub Jun 05 '19 at 11:17
  • Interesting. I wonder if all these placeholders (many sites uses them, facebook etc.) all would look silly with a different background colour, or if they managed to solve it somehow. Maybe another approach is to change colour of the overlaying element to match the background colour, instead of forcing a white semitransparent overlay. – Rasmus Puls Jun 05 '19 at 11:25