Webkit animation is leaving junk pixels behind on the screen
Asked Answered
C

3

38

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?

Counterpunch answered 2/4, 2012 at 20:41 Comment(17)
Have you tried -webkit-backface-visibility: hidden;?Lowis
That's equivalent to using translateZ(0). It puts the images into the hardware pipeline, which solves the issue, but causes out-of-memory in my app.Counterpunch
Probably an ios bug. Have you tried animating with jquery?Delude
Of course it's a bug. It doesn't matter how you do the animation.Counterpunch
Perhaps it's a weird bug with the interpretation of the CSS. Try changing the ".panel {" in your CSS to ".panel, .panel_hide {" and then change ".panel.hide {" to ".panel_hide {". I'm curious to see if it's doing a full refresh of the css class "panel" when you perform "this.setAttribute('class', 'panel hide')". Perhaps change it to: "this.className = 'panel_hide';". Let me know how that works! (I don't have an iOS device T_T)Natality
No, it's not a CSS problem. It's a simple bug in the compositing engine. In some cases they forget to redraw edge pixels when things are animating. Since I posted this question last year, I've seen several more cases just like this one. Both in iOS and in desktop Safari. Somebody at Apple needs to learn about fencepost conditions, they are redrawing pixels <w, <h instead of <=w, <=h.Counterpunch
Applw people are going to point it to the guys at webkit saying it's their problem. Unhelpful lot.Reasonable
Unlikely. Webkit is not responsible for rendering, that is the responsibility of the browser that uses webkit. paulirish.com/2013/webkit-for-developersCounterpunch
Does translate(0) give you the same error as translateZ(0)? Here is a working example: jsfiddle.net/NSUA7Postmark
@BingeBoy, I wouldn't be able to tell from a fiddle, since the problem with using translate is that it cause the real application to put far too many things into the hardware pipe, causing an out of memory condition. So the question is, whether animating a webkit transform instead of animating "left", would cause all that stuff to go into the pipeline, and whether it would fix the problem. I'll have to get back to you on that...Counterpunch
Second time they ask this. It's an iOS bug. No doubts and no answer to it.Anglicanism
This is kind-of an old question, but my answer here explains why. In short, Quartz anti-aliasing paints a faint row of semi-transparent pixels that lie outside the calculated height and width of the element, so they never get cleared on a repaint and build up every animation frame. I can suggest a simple fix that doesn't rely on hardware acceleration if you want?Transistorize
Yes, please share the simple non-GL fix!Counterpunch
@jesmith Great! My idea, simply, is to force WebKit to repaint the extra pixels by increasing the calculated dimensions of the box by one pixel in every direction. Unfortunately I don't have an older iOS device to test on, so would you mind trying out a few suggestions for me? First suggestion: add box-shadow: 0 0 1px rgba(0,0,0,.05); to the .panel ruleset. The visual impact should be minimal, but it will force an extra row of pixels to be painted on every edge. (If that doesn't work, try increasing the alpha a touch.)Transistorize
Further notes: box-shadow doesn't force hardware acceleration, and while it can have performance implications it is generally only problematic with a larger blur radius.Transistorize
That works. If you just repeat your comment as an answer, I'll give you the green check!Counterpunch
@JordanGray, that worked very well. I appreciate it.Radiograph
T
67

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.

Transistorize answered 23/7, 2013 at 23:42 Comment(9)
This works. I saw no performance degradation. Note that in chasing down the links in your answer, I learned that iOS 7 has allegedly fixed the underlying quartz bug.Counterpunch
@jesmith I saw someone observe as much in a comment on the other question, though I couldn't find a good link to reference—if you have one, I'd be really grateful! As an aside, I ventured a little deeper into the WebKit source and read up on Quartz anti-aliasing to see if I could narrow it down further, but I'm not yet at a point where I could give a confident and accurate exposition.Transistorize
Since iOS 7 is in Beta, and under non-disclosure rules, I think we'll just have to wait.Counterpunch
I'm already using the box shadow, but another trick did it for me: Thanks to this post: https://mcmap.net/q/410698/-jquery-ui-draggable-element-leaves-weird-traces-in-chrome I'm using outline: 1px solid transparent; which works like a charm!Hilda
@Hilda That's neat, and more concise—thanks for the heads up! I considered that outline might work, but never got around to testing it. Perhaps I should update my answer to link to that solution.Transistorize
outline made it worse, old -webkit-backface-visibility: hidden worked on Chrome v60Vamp
@Vamp Hmm, interesting! Did you try the box-shadow approach? Setting backface-visibility will certainly remove the artifacts, but it does so by enabling hardware acceleration which the OP hoped to avoid. If that's not a problem for you, it makes perfect sense! :)Transistorize
yes tried that, I'm scratching my own back here this solution is just for my case, so dont try this in production folks :PVamp
2021 and the issue is still there. The outline solution worked for me.Sherise
B
9

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.

Busey answered 30/4, 2013 at 14:23 Comment(2)
-webkit-backface-visibility: hidden worked like a charm! Many thanks.Silky
Thanks to this one, I earned myself a Mango Milk Tea from my friend. LOL!Syllabub
N
2

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.

Nectarine answered 14/4, 2022 at 1:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.