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?

Joshua Smith
  • 3,689
  • 4
  • 32
  • 45
  • 3
    Have you tried `-webkit-backface-visibility: hidden;`? – alex Jun 07 '12 at 04:42
  • 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. – Joshua Smith Jun 11 '12 at 11:54
  • Probably an ios bug. Have you tried animating with jquery? – 3amsleep Mar 14 '13 at 06:54
  • 2
    Of course it's a bug. It doesn't matter how you do the animation. – Joshua Smith Mar 14 '13 at 15:01
  • 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) – Graham Robertson Mar 14 '13 at 19:33
  • 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 – Joshua Smith Mar 14 '13 at 20:47
  • Applw people are going to point it to the guys at webkit saying it's their problem. Unhelpful lot. – Shouvik Mar 15 '13 at 13:03
  • Unlikely. Webkit is not responsible for rendering, that is the responsibility of the browser that uses webkit. http://paulirish.com/2013/webkit-for-developers/ – Joshua Smith Mar 15 '13 at 13:57
  • Does translate(0) give you the same error as translateZ(0)? Here is a working example: http://jsfiddle.net/NSUA7/ – BingeBoy Apr 17 '13 at 16:29
  • @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... – Joshua Smith Apr 17 '13 at 17:49
  • Second time they ask this. It's an iOS bug. No doubts and no answer to it. – Santiago Baigorria Apr 28 '13 at 03:44
  • 2
    This is kind-of an old question, but [my answer here](http://stackoverflow.com/questions/17575624/how-can-i-avoid-animation-artifacts-on-my-touch-draggable-border-radius-element/17723401#17723401) 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? – Jordan Gray Jul 20 '13 at 16:02
  • Yes, please share the simple non-GL fix! – Joshua Smith Jul 22 '13 at 20:49
  • 3
    @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.) – Jordan Gray Jul 23 '13 at 14:55
  • 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. – Jordan Gray Jul 23 '13 at 14:58
  • That works. If you just repeat your comment as an answer, I'll give you the green check! – Joshua Smith Jul 23 '13 at 15:20
  • 1
    @JordanGray, that worked very well. I appreciate it. – myjobistobehappy Nov 25 '20 at 02:29

3 Answers3

66

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.

Community
  • 1
  • 1
Jordan Gray
  • 16,306
  • 3
  • 53
  • 69
  • 2
    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. – Joshua Smith Jul 24 '13 at 14:54
  • @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. – Jordan Gray Jul 24 '13 at 22:13
  • Since iOS 7 is in Beta, and under non-disclosure rules, I think we'll just have to wait. – Joshua Smith Jul 25 '13 at 13:44
  • 3
    I'm already using the box shadow, but another trick did it for me: Thanks to this post: http://stackoverflow.com/a/12352196/807397 I'm using `outline: 1px solid transparent;` which works like a charm! – Sebastian Mar 26 '15 at 10:33
  • 1
    @Sebastian 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. – Jordan Gray Mar 26 '15 at 16:50
  • 1
    Oh my god. This is amazing. Thank you! – Cari Jan 12 '16 at 15:37
  • outline made it worse, old `-webkit-backface-visibility: hidden` worked on Chrome v60 – nodws Aug 16 '17 at 06:25
  • @nodws 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! :) – Jordan Gray Sep 13 '17 at 10:37
  • yes tried that, I'm scratching my own back here this solution is just for my case, so dont try this in production folks :P – nodws Sep 13 '17 at 14:22
  • 2021 and the issue is still there. The `outline` solution worked for me. – Rodolphe May 21 '21 at 08:02
8

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.

Giacomo1968
  • 25,759
  • 11
  • 71
  • 103
Rafal Pastuszak
  • 3,100
  • 2
  • 29
  • 31
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.