Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CSS3 animation doesn't work if div:before is floating (WebKit)

I want my text to blink using CSS3 only. It works fine. However, if I add float:left to the div's :before selector, it stops the animation from working on WebKit (Safari/Chrome).

For demonstration, open JSFiddle on WebKit, remove the float:left to see it working.

CSS:

.blink_me:before {
  content: "Blink";
}

.blink_me {
  -webkit-animation: blinker 1.5s linear infinite;
  -moz-animation: blinker 1.5s linear infinite;
  -o-animation: blinker 1.5s linear infinite;
  animation: blinker 1.5s linear infinite; 
}
@keyframes blinker {  
  50% { opacity: 0.0; }
}

HTML:

<span class="blink_me"> </span>

How can I make it work with the float on the selector?

BOUNTY info: just rewarding an existing answer which certainly deserves more upvotes.

like image 767
Henrik Petterson Avatar asked Mar 24 '16 15:03

Henrik Petterson


People also ask

Do you need WebKit for animation?

Only WebKit (Safari), and not Chromium, based browsers supports the -webkit-animation media feature. No browsers support animation , without the prefix, as a media query. Use the @supports (animation) feature query instead.

Why is my animation CSS not working?

Even if you've assigned a keyframes rule to an element, it still may not appear to play if you haven't set an animation-duration. By default, a CSS animation cycle is zero seconds long. To override this, add an animation-duration rule to your targeted element with a seconds value, in the same block as animation-name.

Why is animation-delay not working CSS?

Possible issue #2 - you are not using the correct values the specified animation is applied to the specific element. The value can be either seconds (s) or milliseconds (ms). Some examples of how you can declare the animation-delay property are as follows. animate immediately when the animation is applied.

What is @- WebKit keyframes in CSS?

The @keyframes rule specifies the animation code. The animation is created by gradually changing from one set of CSS styles to another. During the animation, you can change the set of CSS styles many times.


1 Answers

Reason:

It definitely seems to be because of layer creation and accelerated rendering process in Webkit. Have a look at all the demos in this answer after enabling "Show Paint Rects" and "Show Composited Layer Borders" option in Dev Tools.

When any one demo is run, you'd see some green and orange boxes. The green boxes are the paint rectangles whereas the orange boxes are the compositing layers that are created by rendering engine for accelerated rendering. During the rendering process, Webkit (and Blink) do not always repaint the entire page. Only affected areas (layers) of the page gets repainted (for performance).

With Float:

In this snippet, you'd see that the rendering engine creates one paint rectangle, a compositing layer for the page and one more paint rectangle, compositing layer for the span element (the "Some Content"). Since the span is an inline element, it doesn't generate a principal block-level box that contains its descendant's boxes and generated contents. This (per my understanding) makes the pseudo-element be floated with respect to the root element. It also means that the pseudo-element's position on-screen is not dependant on the parent span element (in fact, if you give a negative margin to the span you'd notice that the contents overlap whereas if display: block is set for span a negative margin moves the pseudo-element's content also to the left). Since the floated element's state doesn't affect the span and since it doesn't get its own compositing layer also, its opacity is not changed by the animation.

.blink_me:before {
  content: "Blink";
  float: left;
}
.blink_me {
  -webkit-animation: blinker 1.5s linear infinite;
  -moz-animation: blinker 1.5s linear infinite;
  -o-animation: blinker 1.5s linear infinite;
  animation: blinker 1.5s linear infinite;
}
@keyframes blinker {
  50% {
    opacity: 0.0;
  }
}
<span class="blink_me">Some content</span>

Without Float:

Here also the engine creates two layers + two paint rectangles but since there is no float, the pseudo-element is also inline and is a part of the parent span (you'd see one box covering the "Blink" and "Some content"). Now since the pseudo's content is also a part of the parent's layer, the animation on the parent affects the pseudo-element's content also.

.blink_me:before {
  content: "Blink";
}
.blink_me {
  -webkit-animation: blinker 1.5s linear infinite;
  -moz-animation: blinker 1.5s linear infinite;
  -o-animation: blinker 1.5s linear infinite;
  animation: blinker 1.5s linear infinite;
}
@keyframes blinker {
  50% {
    opacity: 0.0;
  }
}
<span class="blink_me">Some content</span>

Solutions:

Doing any one of the following things are resulting in the pseudo-element's content being treated as a part of the parent element's layer and hence the animation on parent is affecting the child also. When any one of these setting is applied, you'd again notice the orange border covering the span element's content and the pseudo-element's content.

  • Setting any position on the pseudo-element (relative or absolute or even fixed).

    .blink_me:before {
      content: "Blink";
      float: left;
      position: relative;
    }
    .blink_me {
      -webkit-animation: blinker 1.5s linear infinite;
      -moz-animation: blinker 1.5s linear infinite;
      -o-animation: blinker 1.5s linear infinite;
      animation: blinker 1.5s linear infinite;
    }
    @keyframes blinker {
      50% {
        opacity: 0.0;
      }
    }
    <span class="blink_me">Some content</span>
  • Setting a opacity other than 1 on the pseudo-element (like 0.99 etc).

    .blink_me:before {
      content: "Blink";
      float: left;
      opacity: 0.99;
    }
    .blink_me {
      -webkit-animation: blinker 1.5s linear infinite;
      -moz-animation: blinker 1.5s linear infinite;
      -o-animation: blinker 1.5s linear infinite;
      animation: blinker 1.5s linear infinite;
    }
    @keyframes blinker {
      50% {
        opacity: 0.0;
      }
    }
    <span class="blink_me">Some content</span>
  • Setting transform: translateZ(0px); on the pseudo-element.

    .blink_me:before {
      content: "Blink";
      float: left;
      transform: translateZ(0px);
    }
    .blink_me {
      -webkit-animation: blinker 1.5s linear infinite;
      -moz-animation: blinker 1.5s linear infinite;
      -o-animation: blinker 1.5s linear infinite;
      animation: blinker 1.5s linear infinite;
    }
    @keyframes blinker {
      50% {
        opacity: 0.0;
      }
    }
    <span class="blink_me">Some content</span>

Or, the other solution would be to set the animation directly on the pseudo-element because it then gets its own compositing layer and only that layer gets affected.

.blink_me:before {
  content: "Blink";
  float: left;
  -webkit-animation: blinker 1.5s linear infinite;
  -moz-animation: blinker 1.5s linear infinite;
  -o-animation: blinker 1.5s linear infinite;
  animation: blinker 1.5s linear infinite;
}
@keyframes blinker {
  50% {
    opacity: 0.0;
  }
}
<span class="blink_me"> </span>

Another option that is working is to set display as inline-block or block to parent span. This is also making the pseudo-element be a part of the parent element's compositing layer and hence it also is affected by the animation.

.blink_me:before {
  content: "Blink";
  float: left;
}
.blink_me {
  display: inline-block;
  -webkit-animation: blinker 1.5s linear infinite;
  -moz-animation: blinker 1.5s linear infinite;
  -o-animation: blinker 1.5s linear infinite;
  animation: blinker 1.5s linear infinite;
}
@keyframes blinker {
  50% {
    opacity: 0.0;
  }
}
<span class="blink_me"> </span>

Summary

In the second link that is provided under the References, you'd see how the rendering process works in WebKit (and Blink) right from Nodes to Render Objects to Render Layers to Compositing Layers.

The below is a summary of how those apply to all the demos in this answer and why it makes each of them work the way they are.

When there is no float on the pseudo-element:

Element | Node  | Render Object | Render Layer | Compositing Layer
-----------------------------------------------------------------------------------------
Root    | Yes   | Yes           | Yes          | Yes (Descendant is a compositing layer)
span    | Yes   | Yes           | Yes          | Yes
:before | Yes   | Yes           | No           | N/A

Here the span gets a render layer as soon as animation starts because it is semi-transparent (due to opacity) and it gets a compositing layer because it has opacity animation. The pseudo gets no render layer of its own because it doesn't satisfy any criteria required and so doesn't get a compositing layer either. It becomes a part of the first ancestor's render + compositing layer. During the compositing, the pseudo's content is also affected because it is also a part of the layer.

When there is float on the pseudo-element:

Element | Node  | Render Object | Render Layer | Compositing Layer
-----------------------------------------------------------------------------------------
Root    | Yes   | Yes           | Yes          | Yes (Descendant is a compositing layer)
span    | Yes   | Yes           | Yes          | Yes
:before | Yes   | Yes           | No           | N/A

Same as previous but since there is a float and it is not part of the span, the pseudo is not part of its compositing layer and hence not modified during the compositing operation.

When the pseudo-element is positioned:

Element | Node  | Render Object | Render Layer | Compositing Layer
-----------------------------------------------------------------------------------------
Root    | Yes   | Yes           | Yes          | Yes (Descendant is a compositing layer)
span    | Yes   | Yes           | Yes          | Yes
:before | Yes   | Yes           | Yes          | No

When the psuedo is positioned, it gets a render layer of its own (as it matches the criteria) but doesn't get a compositing layer because it doesn't match the criteria required for that. Also, the positioning on it means that its position on-screen is affected by any transforms on the span. It looks as though this makes the pseudo-element also become a part the span's compositing layer and hence gets modified as part of the compositing operation.

When the pseudo-element has opacity less than 1:

Element | Node  | Render Object | Render Layer | Compositing Layer
-----------------------------------------------------------------------------------------
Root    | Yes   | Yes           | Yes          | Yes (Descendant is a compositing layer)
span    | Yes   | Yes           | Yes          | Yes
:before | Yes   | Yes           | Yes          | No

Similar case to the previous one. Here the pseudo having an opacity less than 1 means that it needs to change when a layer below it changes (otherwise, transparency will be spoiled). Because of that, this seems to get moved to the compositing layer and so is modified during compositing.

When the pseudo-element has a transform:

Element | Node  | Render Object | Render Layer | Compositing Layer
-----------------------------------------------------------------------------------------
Root    | Yes   | Yes           | Yes          | Yes (Descendant is a compositing layer)
span    | Yes   | Yes           | Yes          | Yes
:before | Yes   | Yes           | Yes          | Yes

Here, the pseudo also get its own compositing layer because it has a 3D transform on it and since it is the child of the span, its layer is above the span's layer. This means that during compositing, both the span and the pseudo-element's layers are modified and thus animation affects the pseudo here also.

When pseudo-element is directly animated:

Element | Node  | Render Object | Render Layer | Compositing Layer
-----------------------------------------------------------------------------------------
Root    | Yes   | Yes           | Yes          | Yes (Descendant is a compositing layer)
span    | Yes   | Yes           | No           | N/A
:before | Yes   | Yes           | Yes          | Yes

Here, the span gets no render or compositing layer because the opacity animation is on the pseudo element. Since pseudo gets its own compositing layer, here also it is affected during compositing.

When span has display as block or inline-block:

Element | Node  | Render Object | Render Layer | Compositing Layer
-----------------------------------------------------------------------------------------
Root    | Yes   | Yes           | Yes          | Yes (Descendant is a compositing layer)
span    | Yes   | Yes           | Yes          | Yes
:before | Yes   | Yes           | No           | N/A

This is very similar to the case where the pseudo element is floated but since here the span is a block level element, it generates the principal block level box for its descendants and generated content. So, the pseudo gets to be part of the span's compositing layer and thus is affected during compositing.

Note: The whole rendering process is very complex as you could see from the reference links and I've tried my best to explain the process. There are chances that I could have some of the intricate details wrong but on the overall you'd find that the explanation tallies with the Dev tools output.


References:

You can find more information about how to enable the "Show Paint Rects", "Show Composited Layer Borders" options and also on how the accelerated rendering process works by referring to the below links:

  • HTML5 Rocks - Accelerated Rendering in Chrome
  • GPU Accelerated Compositing in Chrome.
like image 57
Harry Avatar answered Oct 18 '22 02:10

Harry