filter on an ancestor cancels an in-progress touch scrollReduced test case for
WebKit bug 322849
(rdar://problem/186671580). Affects iOS Safari. Not reproducible in
Chrome on Android, or in any desktop browser including desktop Safari.
The box below scrolls. Its parent — never the scroller
itself — has a filter. With the filter present, touch drags inside
the box scroll nothing at all about ninety-five per cent of the time; with it
gone, every drag scrolls. The page scores itself: drag a few
times in each mode and read the verdict.
| Pixels scrolled, this gesture | 0 |
|---|---|
| Gestures that scrolled | 0 |
| Gestures that did nothing | 0 |
| Median pixels per gesture | — |
| Off (control) | 0 of 50 gestures failed |
|---|---|
On :active | 49 of 50 failed |
| Changed during the drag | 47 of 50 failed |
Both filter modes fail about as often as each other — 49 and 47 out of 50 is the same result within sampling noise — and the control does not fail at all. The third mode was added on the theory that re-setting the filter throughout the gesture would remove the timing question and make it fail every time. It did not, so that theory is wrong: whatever decides the outcome is not simply how much of the gesture the filter change overlaps.
The fourth mode adds contain: contents to the element the filter
is applied to — shorthand for layout, paint and style containment. The reasoning,
suggested by u/maven_peace
on r/webdev: containment establishes the containing block and stacking context up
front, so gaining a filter no longer restructures the layers above the scroller,
and there is nothing to make the UIScrollView be removed and
re-added. That fits the mechanism described in the bug exactly. Whether it holds
in practice is what the mode is for; if it does, it is a workaround real pages
can use today.
Both modes are kept because that null result is itself worth having. What is solid is the contrast with the control: fifty clean gestures with no filter on the ancestor, and ninety-odd per cent failure with one.
/* the reported case, and the stronger of the two */
.host { transition: filter 150ms ease-out; }
.host:active { filter: brightness(0.95); }
/* the other shape, which fails at the same rate */
host.style.filter = 'brightness(' + (0.95 + n) + ')'; // on every touchmove
:active and drag five times the
same way.Five gestures per mode is enough to see it. The numbers above came from fifty each.
Filed as WebKit bug 322849,
imported to Radar as rdar://problem/186671580. Written up in
Safari dropped my scroll because of a CSS fade on an ancestor,
which is where it came from — a reader called
ReadKinetic that draws real typeset PDF pages in the browser.