Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

isIntersecting definition doesn't match browsers when thresholds are involved. #432

Open
emilio opened this issue May 23, 2020 · 7 comments

Comments

@emilio
Copy link
Collaborator

emilio commented May 23, 2020

So the spec says that technically, you're supposed to set isIntersecting to true unconditionally, regardless of threshold:

https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo:

Let isIntersecting be true if targetRect and rootBounds intersect or are edge-adjacent, even if the intersection has zero area (because rootBounds or targetRect have zero area); otherwise, let isIntersecting be false.

It also says that we should schedule notifications whenever isIntersecting changes, regardless of the threshold change. However that doesn't match browsers, see:

Firefox initially implemented the spec to the letter but had a workaround of sorts to make it similar to other browsers:

Given no browser follows the spec, and that Firefox is pretty close to other browsers, except on the specific value of isIntersecting, I think we should change the spec and Firefox, probably... But it seems kind of an unfortunate situation to be in.

@emilio emilio changed the title isIntersecting definition doesn't match browsers. isIntersecting definition doesn't match browsers when thresholds are involved. May 23, 2020
moz-wptsync-bot pushed a commit to web-platform-tests/wpt that referenced this issue May 28, 2020
Note that no browser matches the spec (see
w3c/IntersectionObserver#432), but that our
behavior is reasonably close to them. So do this to match them.

Differential Revision: https://phabricator.services.mozilla.com/D76603

bugzilla-url: https://bugzilla.mozilla.org/show_bug.cgi?id=1611204
gecko-commit: 0dba6079675375c8934301b57a284a0988804441
gecko-integration-branch: autoland
gecko-reviewers: mstange
moz-wptsync-bot pushed a commit to web-platform-tests/wpt that referenced this issue May 28, 2020
Note that no browser matches the spec (see
w3c/IntersectionObserver#432), but that our
behavior is reasonably close to them. So do this to match them.

Differential Revision: https://phabricator.services.mozilla.com/D76603

bugzilla-url: https://bugzilla.mozilla.org/show_bug.cgi?id=1611204
gecko-commit: 867528d1d35bdec48758f8aa899b0491d7ef10e5
gecko-integration-branch: autoland
gecko-reviewers: mstange
moz-v2v-gh pushed a commit to mozilla/gecko-dev that referenced this issue May 28, 2020
…ther browsers. r=mstange

Note that no browser matches the spec (see
w3c/IntersectionObserver#432), but that our
behavior is reasonably close to them. So do this to match them.

Differential Revision: https://phabricator.services.mozilla.com/D76603
moz-v2v-gh pushed a commit to mozilla/gecko-dev that referenced this issue May 28, 2020
…ther browsers. r=mstange

Note that no browser matches the spec (see
w3c/IntersectionObserver#432), but that our
behavior is reasonably close to them. So do this to match them.

Differential Revision: https://phabricator.services.mozilla.com/D76603
xeonchen pushed a commit to xeonchen/gecko that referenced this issue May 28, 2020
…ther browsers. r=mstange

Note that no browser matches the spec (see
w3c/IntersectionObserver#432), but that our
behavior is reasonably close to them. So do this to match them.

Differential Revision: https://phabricator.services.mozilla.com/D76603
xeonchen pushed a commit to xeonchen/gecko that referenced this issue May 29, 2020
…ther browsers. r=mstange

Note that no browser matches the spec (see
w3c/IntersectionObserver#432), but that our
behavior is reasonably close to them. So do this to match them.

Differential Revision: https://phabricator.services.mozilla.com/D76603
moz-wptsync-bot pushed a commit to web-platform-tests/wpt that referenced this issue May 29, 2020
Note that no browser matches the spec (see
w3c/IntersectionObserver#432), but that our
behavior is reasonably close to them. So do this to match them.

Differential Revision: https://phabricator.services.mozilla.com/D76603

bugzilla-url: https://bugzilla.mozilla.org/show_bug.cgi?id=1611204
gecko-commit: 867528d1d35bdec48758f8aa899b0491d7ef10e5
gecko-integration-branch: autoland
gecko-reviewers: mstange
bgrins pushed a commit to bgrins/gecko-dev that referenced this issue May 29, 2020
…ther browsers. r=mstange

Note that no browser matches the spec (see
w3c/IntersectionObserver#432), but that our
behavior is reasonably close to them. So do this to match them.

Differential Revision: https://phabricator.services.mozilla.com/D76603
bgrins pushed a commit to bgrins/gecko-dev that referenced this issue May 29, 2020
…ther browsers. r=mstange

Note that no browser matches the spec (see
w3c/IntersectionObserver#432), but that our
behavior is reasonably close to them. So do this to match them.

Differential Revision: https://phabricator.services.mozilla.com/D76603
@szager-chromium
Copy link
Collaborator

szager-chromium commented Sep 3, 2020

A bit of background...

isIntersecting was added to handle a very specific corner case: namely, for an observer with zero as one of its thresholds, you could not distinguish between a "not intersecting" notification and an "intersecting with zero-area intersection" notification. There are two ways that a zero-area intersection can occur: target and root are edge-adjacent; or target has zero area.

My initial attempt to rationalize this is #316:

Here's the language around when to send a notification:

  1. If:
    * |thresholdIndex| does not equal |previousThresholdIndex| or
    * |isIntersecting| does not equal |previousIsIntersecting| and the first value in |observer|.{{thresholds}} is 0
    queue an IntersectionObserverEntry,
    passing in |observer|, |time|, |rootBounds|,
    |boundingClientRect|, |intersectionRect|, |isIntersecting|, and |target|.

To understand this, it's helpful to consider two possibilities: either observer.thresholds contains a zero, or it does not.

If observer.thresholds does not contain zero, then a change in isIntersecting alone will not trigger a notification; only crossing a threshold will do it. Note that this means that when a notification is sent because the intersection has fallen below the lowest threshold, isIntersecting can be either true or false. Chromium does not currently implement this correctly; it always sets isIntersecting to false when the intersection falls below the lowest non-zero threshold.

If observer.thresholds does contain zero, then the second condition means that the transition from "not intersecting" to "intersecting with zero-area intersection" will trigger a notification to be sent as intended.


Thinking about this further, I would probably prefer that isIntersecting had a reliable value for the case where there is no zero threshold. To that end, I think the above PR could be modified to read:

  1. Let |isIntersectingForNotification| be true if |thresholdIndex| > 0, false otherwise.
  2. If:
    * |thresholdIndex| does not equal |previousThresholdIndex| or
    * |isIntersectingForNotification| does not equal |previousIsIntersecting|
    queue an IntersectionObserverEntry,
    passing in |observer|, |time|, |rootBounds|,
    |boundingClientRect|, |intersectionRect|, |isIntersectingForNotification|, and |target|.
  3. Assign thresholdIndex to intersectionObserverRegistration’s previousThresholdIndex property.
  4. Assign isIntersectingForNotification to intersectionObserverRegistration’s previousIsIntersecting property.

The net effect of this would be that isIntersecting would always be false if the lowest threshold was not met. In the special case of zero threshold, it would be true if there was an intersection (either zero-area or non-zero-area), and false otherwise. This is what chromium currently does, and I think this is in keeping with the intuitive sense of what isIntersecting should mean.

@szager-chromium
Copy link
Collaborator

szager-chromium commented Sep 10, 2020

One small modification -- the condition for queueing an entry can be simplified:

  1. If |thresholdIndex| does not equal |previousThresholdIndex|, queue an IntersectionObserverEntry ...

The condition on isIntersectingForNotification becomes redundant, because it can only change value when thresholdIndex also changes.

@szager-chromium
Copy link
Collaborator

... and this would actually allow us to get entirely rid of the previousIsIntersecting property on IntersectionObserverRegistration, which would be a nice simplification.

@szager-chromium
Copy link
Collaborator

@emilio -- friendly ping, this is an important one to resolve.

@ambar
Copy link

ambar commented Oct 22, 2020

A demo for isIntersecting=true, intersectionRatio=0
https://codesandbox.io/s/inview-intersection-ratio-zero-sph9o

@thebuilder
Copy link

It would be great if we would could get an agreement on this.
In the https://github.com/thebuilder/react-intersection-observer implementation I'm forced to check both isIntersecting and intersectionRatio to determine if the element is inside the viewport.

If users make their own implementation, and expect that can just rely on the observer behaving logically with isIntersecting, then they are very likely to run into subtle bugs in different browsers - something they most likely wont know until users report a bug.

@emilio
Copy link
Collaborator Author

emilio commented Jan 15, 2022

The net effect of this would be that isIntersecting would always be false if the lowest threshold was not met. In the special case of zero threshold, it would be true if there was an intersection (either zero-area or non-zero-area), and false otherwise. This is what chromium currently does, and I think this is in keeping with the intuitive sense of what isIntersecting should mean.

I think that would be sensible, and it being what Chromium does makes it probably not-problematic to change for Firefox.

Do you know if there are WPTs that test this edge case? Looking at wpt.fyi I don't think I found any. But landing your PR with this tweak and a WPT test would be awesome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants