Joshua Smith
Joshua Smith

Reputation: 3809

Webkit animation is leaving junk pixels behind on the screen

The following code puts a white box on the screen. If you run this on an iPad (you can adjust the pixels to run it on an iPhone, too), when you touch the box, it will scoot off the screen, and leave a trail of white-ish pixels along its bottom edge.

<!DOCTYPE HTML>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-height, user-scalable=no, maximum-scale=1, minimum-scale=1" />
    <title>Line Bug Demo</title>
    <style>
body {
  background: black;
}
.panel {
  background: white;
  position: absolute;
  z-index: 2;
  width: 1000px;
  height: 500px;
  top: 34px;
  left: 12px;
  -webkit-border-radius: 20px;
  -webkit-transition: left 0.333s ease-in-out;
}
.panel.hide {
  left: -1000px;
}
    </style>
  </head>
  <body>
    <div class="panel" onclick="this.setAttribute('class', 'panel hide')"></div>
  </body>
</html>

The key to getting the bug is using a border radius, and doing animation. If you just pop it off the screen, no trail. If there is no border radius, no trail.

Here are the work-arounds I've found so far:

.panel.hide { -webkit-border-radius: 0; }

Ugly, and not really practical for my application, because I'm animating the panel both in and out, and I really want the rounded corners when it is on screen.

Another:

.panel { -webkit-transform: translateZ(0); }

That puts the panel into the hardware pipeline, which does the compositing correctly. Although this works with this simple demo, using the hardware pipeline in my real web app causes out-of-memory errors. (Of the drastic, huge, immediate variety.)

Any other ideas of how I might get rid of this trail?

Upvotes: 38

Views: 19894

Answers (3)

Chase Johnson
Chase Johnson

Reputation: 81

For me this was fixed by adding

will-change: transform;

to the CSS for any element that is going to move. You can change transform to any other attribute that you might be changing.

Other answers such as:

outline: 1px solid transparent;

also seemed to do the trick, but in my case I had to make the outline up to 50px because it was leaving a lot behind. If you're finding yourself needing to do that then maybe my first answer might fix your issue. After doing this the outline was no longer necessary for me.

Upvotes: 3

Rafal Pastuszak
Rafal Pastuszak

Reputation: 3160

What I would do:

  • -webkit-backface-visibility: hidden
  • animate with -webkit-transform:translateX(left value here) // or translate-3d(x,y,z), left should be disabled [*]

Be sure to check if enabling hardware acceleration on parent does make any difference.

There are also simple ways to force repaint - let me know if you would need info about them as well.

[*] relying on 3d transforms is a hack and should be used with caution, and it's a tradeoff between GPU and memory, in some cases it might cause animation jank or memory issues (think - mobile, forcing GPU acceleration on large areas).

CSS will-change property will be a correct place to mark properties that could be optimised in advance.

Upvotes: 9

Jordan Gray
Jordan Gray

Reputation: 16499

The solution

box-shadow: 0 0 1px rgba(0, 0, 0, 0.05);

You can use the background colour of your box as the box-shadow colour if you feel this is too noticeable.

Alternatively, according to this answer on a similar issue in Chrome (thanks to Sebastian in the comments for the tip-off), you may want to try:

outline: 1px solid transparent;

What's going on?

I've given a fairly lengthy explanation elsewhere, but here's the short version. For performance reasons, WebKit only repaints those part of a page that it thinks might have changed. However, the iOS (pre-7) Safari implementation of border radius anti-aliases a few pixels beyond the calculated dimensions of a box. Since WebKit doesn't know about these pixels, they don't get redrawn; instead, they are left behind and build up on each animation frame.

The usual solution—as I suggested in my other answer—is to force that element to require hardware acceleration so that it gets painted as a separate layer. However, too many small elements or a few large ones will result in a lot of tiles getting pushed to the GPU, with obvious performance implications.

Using box-shadow solves the problem more directly: it extends the repaint dimensions of the box, forcing WebKit to repaint the extra pixels. The known performance implications of box-shadow in mobile browsers are directly related to the blur radius used, so a one pixel shadow should have little-to-no effect.

Upvotes: 67

Related Questions