8

I’ve written a little example page with elements that are draggable on touch devices (based on code by Peter-Paul Koch).

I have two draggable <div> elements: a green block, and a red ball (made ball-shaped with border-radius). The dragging is implemented with ontouchstart, ontouchmove and ontouchend, and the "animation" is done by changing the top and left CSS properties of these elements.

When I drag the green one, all is roses and ponycorns.

But when I drag the red one in Safari on an iPad 1 (running iOS 5.1.1) or an iPad 3 (6.0.1), I get little light red trails where the rear edges of the circle were (see screenshot below).

I don’t, however, see these trails on my iPhone 5 (6.1.4).

Artifacts on my element that uses border-radius

Is there a way to get rid of these trails?

(Bonus question: is there a term for this effect? “Ghosting”? “Artifacts”?)

Paul D. Waite
  • 96,640
  • 56
  • 199
  • 270
  • 1
    I think the proper term for this is "iOS rendering bug". – Pointy Jul 10 '13 at 16:13
  • @Pointy: I meant the general term for fragment of previous states of an animated object remaining visible for longer than they should, regardless of platform. – Paul D. Waite Jul 10 '13 at 16:16
  • Well I guess I'd use the term "artifact", but in this particular case it's clearly a platform bug. – Pointy Jul 10 '13 at 16:46
  • @Pointy: gotcha. Yup, this instance of this kind of bug is definitely an iOS bug. – Paul D. Waite Jul 10 '13 at 18:45
  • 1
    I have a strong suspicion about what's happening here. First, a test: could you try adding -webkit-transform: translateZ(0); to the #bawl { … } ruleset? – Jordan Gray Jul 18 '13 at 09:25
  • @JordanGray: absolutely. Here's a new test page, with `-webkit-transform: translateZ(0);` added to `#bawl`: http://www.pauldwaite.co.uk/test-pages/touchy/2/ (and, lo and behold, no artifacts on that one!) – Paul D. Waite Jul 18 '13 at 09:40
  • 1
    Excellent! This itself is a basic trick, but I have a rather long-winded suspicion about what exactly is happening. I'll add an answer over lunch with an explanation. :) – Jordan Gray Jul 18 '13 at 09:49
  • @JordanGray: superb - without wishing to jinx anything, you've got one hand on the bounty. – Paul D. Waite Jul 18 '13 at 09:51
  • 2
    At least in iOS 7 beta 3 your site works as intended, no artifacts. – luk2302 Jul 18 '13 at 12:30
  • @PaulD.Waite I've just posted my answer. Hopefully it doesn't strike you as patronising—you might already know plenty about how WebKit gets from a tree of DOM nodes to a rendered image, I just didn't want to make any assumptions! :) – Jordan Gray Jul 18 '13 at 12:31
  • @luk2302 Yeah, I think that later versions of iOS have the `--forced-compositing-mode` flag on by default. :) – Jordan Gray Jul 18 '13 at 12:32
  • 1
    @JordanGray: not patronising at all, and on Stack Overflow that doesn’t really matter anyway — answers are intended as much for future visitors as they are for the user who asked the question, so adding in details/sources/references is always good. – Paul D. Waite Jul 18 '13 at 12:34

2 Answers2

22

The fix

Add this rule to your #bawl { … } ruleset:

-webkit-transform: translateZ(0);

(If you need to avoid hardware acceleration, you can use outline: 1px solid transparent—for more details, see my answer to a similar question.)

This removes the trailing artefacts, but why? It's a combination of Quartz (the drawing engine in iOS) not clipping anti-aliased lines to the edges of a shape and how WebKit repaints a web page after part of it changes.

Drawing a webpage

First, a quick and (over)simplified run down of how WebKit gets from a tree of DOM nodes to a bitmap image representing the rendered webpage.

You already know that a webpage gets parsed into a tree of elements, called the DOM. Each node in the DOM is presented in a certain way depending on the styles applied to it. It is possible for nodes to overlap, or be overlapped by, other nodes depending on things like z-order, transparency, overflow, positioning etc.

This is broadly similar to how things work under the hood. WebKit maps each DOM node to a corresponding RenderObject, which has all the information it needs to paint an individual DOM node. Each RenderObject is mapped to either its own or an ancestor's RenderLayer, which is a conceptual way of dealing with how nodes are "layered"—i.e. painted over or under other nodes.

To render a webpage, each RenderLayer is painted from back-to-front by z-order. Children behind that layer painted are first, followed by the layer itself, followed by children in front of it. To paint itself, the RenderLayer calls the paint method on its corresponding RenderObjects.

WebKit has two code paths for rendering a given RenderLayer: the software path and the hardware accelerated path. The software path paints each RenderObject directly to the image of the rendered webpage that you see in your browser, whereas the hardware path allows certain RenderLayers to be designated as compositing layers, which means that it and its children are drawn separately and finally composited into a single image by the GPU. The hardware path is used whenever one or more of the RenderLayers on a page require hardware acceleration, or when it is explicitly required by a flag in the browser (e.g. Chrome).

Updating part of a webpage

When an animation or some other event changes how part of a page looks, you don't want to redraw the entire webpage. Instead, WebKit draws a box around the changed area—the damage rect—and only redraws bits of each RenderLayer that overlap with that box. A RenderLayer that doesn't overlap the damage rect is skipped entirely.

If you're rendering via the software path, WebKit directly recomposes the damage rect onto the bitmap image of the full rendered page, leaving the rest of the image unchanged. The hardware path, however, repaints each compositing layer separately and recomposites them into a new image.

What is going wrong

The translateZ fix applies a 3D transform to the element. This forces the RenderLayer that paints the ball to require hardware acceleration, meaning that WebKit will use the hardware path instead of the software path. This implies that the problem is related to using the software path.

When the ball is painted, the border radius means that the edges are anti-aliased. Because of a known issue related to Quartz, the edges of the shape are anti-aliased such that some pixels fall outside of the damage rect calculated when you change the ball's position on the page. Using the software path, the browser redraws only the changed region of the rendered page image, leaving the rest of the image untouched. The semi-transparent pixels that fall outside of this area won't be updated, which accounts for the artefacts left behind when you move the ball.

By contrast, the hardware path redraws that layer (and its child layers) separately then recomposites the page. No "ghost pixels" are left over from the last time that layer was rendered.

TL;DR

When WebKit uses the software rendering path, changes to part of a page are made directly to the image representing the whole page. For performance reasons, WebKit only redraws those parts of an image that have changed. However, when Quartz draws a rounded rectangle, it anti-aliases the edges such that some pixels fall outside the region that WebKit knows to redraw. The fix is to force that layer to require hardware acceleration by applying a 3D transform, meaning that element is drawn separately from the rest of the page and recomposed after.

Community
  • 1
  • 1
Jordan Gray
  • 16,306
  • 3
  • 53
  • 69
0

Had a similiar issue on all webkit browsers (mobile and desktop).

I cleared cache and everything, after that the error never occured. It also seems that i only could see it. I posted it here and got plenty of downvotes because nobody encountered that issue too...

However id try that, may work

David Fariña
  • 1,536
  • 1
  • 18
  • 28
  • I've tried it on two different devices, so I'm not sure if clearing the browser's cache is likely to have an effect. (And even if it did, are my users meant to clear their cache after visiting my page for the first time?) Sorry to hear your question got down-voted though. – Paul D. Waite Jul 16 '13 at 13:08
  • Thats what i said. The other ones couldnt see the problem because they were never on my site. So if a new user comes to your page, he wont see it. Btw: i cant see your issue on my iphone 5 with iOS 6.1.4. Maybe just try it out, wont hurt you much – David Fariña Jul 16 '13 at 13:13
  • I don't understand what you mean. You think a cached file from elsewhere on your site that *wasn't* on the problem page was causing the problem? – Paul D. Waite Jul 16 '13 at 13:14
  • Anyhoo: for reference, I've cleared Safari's cache on my iOS 5 iPad 1 (Settings > Safari > Clear Cookies and Data), and the problem persists. – Paul D. Waite Jul 16 '13 at 13:15
  • No, the problem was that google chrome rendered my page in a very special way. Everything looked good except of a colourful noise all over the page. It looked pretty strange to me and asked here. However after clearing the cache it never appeared again... Which version is your device running? – David Fariña Jul 16 '13 at 13:17
  • This is in Safari, not Chrome. – Paul D. Waite Jul 16 '13 at 13:19
  • But its both webkit and it looks pretty similiar to what i had. However i cant reproduce your issue. What versions and devices exactly do you use? – David Fariña Jul 16 '13 at 13:22
  • I did list those in the question - I see the issue on an iPad 1 (running iOS 5.1.1) and an iPad 3 (6.0.1), but I don't see it on an iPhone 5 (6.1.4). Are you trying to reproduce in Chrome? – Paul D. Waite Jul 16 '13 at 13:24
  • No tried it on my iPad 2 version 5.1 and iPhone5 6.1.4, iPad 2 simulator 6.1 & iPad 3 simulator also 6.1. Couldnt reproduce it even on my own iPad running an even older version than you. Seems to be a very strange one – David Fariña Jul 16 '13 at 13:29
  • Huh, interesting it doesn't show up on your iPad 2. I shall try cache clearing on the iPad 3 I have access to here, and re-test. – Paul D. Waite Jul 16 '13 at 13:33
  • I've reproduced the issue on the iPad 3 (6.0.1) after clearing Safari's cache. – Paul D. Waite Jul 16 '13 at 13:52