Webkit animation is leaving junk pixels behind on

2020-02-02 11:47发布

问题:

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?

回答1:

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.



回答2:

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.