Skip to content

Commit

Permalink
Merge pull request #523 from szager-chromium/v2-pr
Browse files Browse the repository at this point in the history
[IntersectionObserver] #295 V2: visibility detection
  • Loading branch information
szager-chromium authored Jun 27, 2024
2 parents ef83cd2 + 885a800 commit 027e2dd
Showing 1 changed file with 135 additions and 42 deletions.
177 changes: 135 additions & 42 deletions index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,21 @@ urlPrefix: https://drafts.csswg.org/css-display/
url: #containing-block-chain; type: dfn; text: containing block chain
urlPrefix: http://www.w3.org/TR/css-masking-1/
url: #propdef-clip-path; type:dfn; text: clip-path
urlPrefix: https://drafts.csswg.org/css-overflow-3/
url: #ink-overflow-rectangle; type:dfn; text: ink overflow rectangle
url: #ink-overflow-region; type:dfn; text: ink overflow region
url: #overflow-properties; type:dfn; text: overflow properties
urlPrefix: https://drafts.csswg.org/css-transforms-1/
url: #transformation-matrix; type:dfn; text: transformation matrix
url: #serialization-of-the-computed-value; type:dfn; text: serialization
url: #identity-transform-function; type:dfn; text: identity transform function
url: #post-multiplied; type:dfn; text: post-multiplied
urlPrefix: https://drafts.csswg.org/cssom-view-1/
url: #pinch-zoom; type:dfn; text: pinch zoom
urlPrefix: https://drafts.csswg.org/css2/visuren.html
url: #viewport; type:dfn; text: viewport
urlPrefix: https://drafts.csswg.org/css-overflow-3/
url: #overflow-properties; type:dfn; text: overflow properties
urlPrefix: https://drafts.fxtf.org/filter-effects/
url: #funcdef-filter-blur; type:dfn; text: blur
</pre>

<pre class="link-defaults">
Expand Down Expand Up @@ -170,7 +179,7 @@ The IntersectionObserverCallback</h3>
callback IntersectionObserverCallback = undefined (sequence&lt;IntersectionObserverEntry> entries, IntersectionObserver observer);
</pre>

This callback will be invoked when there are changes to <a for="IntersectionObserver">target</a>'s
This callback will be invoked when there are changes to a <a for="IntersectionObserver">target</a>'s
intersection with the <a>intersection root</a>, as per the
<a>processing model</a>.

Expand All @@ -192,7 +201,7 @@ and it can observe any <a for="IntersectionObserver">target</a> {{Element}} that
{{IntersectionObserver/root}} in the <a>containing block chain</a>.
An {{IntersectionObserver}} with a <code>null</code> {{IntersectionObserver/root}}
is referred to as an <dfn for="IntersectionObserver">implicit root observer</dfn>.
Valid <a for="IntersectionObserver">targets</a> for an <a>implicit root observer</a> include
Valid <a for="IntersectionObserver">target</a>s for an <a>implicit root observer</a> include
any {{Element}} in the <a>top-level browsing context</a>,
as well as any {{Element}} in any <a>nested browsing context</a>
which is in the <a>list of the descendant browsing contexts</a> of the <a>top-level browsing context</a>.
Expand Down Expand Up @@ -225,6 +234,8 @@ interface IntersectionObserver {
readonly attribute DOMString rootMargin;
readonly attribute DOMString scrollMargin;
readonly attribute FrozenArray&lt;double&gt; thresholds;
readonly attribute long delay;
readonly attribute boolean trackVisibility;
undefined observe(Element target);
undefined unobserve(Element target);
undefined disconnect();
Expand All @@ -247,7 +258,7 @@ interface IntersectionObserver {

Note: {{MutationObserver}} does not implement {{unobserve()}}.
For {{IntersectionObserver}}, {{unobserve()}} addresses the
lazy-loading use case. After |target| becomes visible,
lazy-loading use case. After loading is initiated for |target|,
it does not need to be tracked.
It would be more work to either {{disconnect()}} all |target|s
and {{observe()}} the remaining ones,
Expand Down Expand Up @@ -306,6 +317,14 @@ interface IntersectionObserver {
If no |options|.{{IntersectionObserverInit/threshold}} was provided to the
{{IntersectionObserver}} constructor, or the sequence is empty, the value
of this attribute will be [0].
: <dfn>delay</dfn>
::
A number indicating the minimum delay in milliseconds
between notifications from this observer for a given target.
: <dfn>trackVisibility</dfn>
::
A boolean indicating whether this {{IntersectionObserver}} will track
changes in a target's <a>visibility</a>.
</div>

An {{Element}} is defined as having a <dfn for="IntersectionObserver">content clip</dfn> if its computed style has <a>overflow properties</a> that cause its content to be clipped to the element's <a>padding edge</a>.
Expand Down Expand Up @@ -401,6 +420,7 @@ interface IntersectionObserverEntry {
readonly attribute DOMRectReadOnly boundingClientRect;
readonly attribute DOMRectReadOnly intersectionRect;
readonly attribute boolean isIntersecting;
readonly attribute boolean isVisible;
readonly attribute double intersectionRatio;
readonly attribute Element target;
};
Expand All @@ -411,6 +431,7 @@ dictionary IntersectionObserverEntryInit {
required DOMRectInit boundingClientRect;
required DOMRectInit intersectionRect;
required boolean isIntersecting;
required boolean isVisible;
required double intersectionRatio;
required Element target;
};
Expand All @@ -428,8 +449,8 @@ dictionary IntersectionObserverEntryInit {
rects (up to but not including {{IntersectionObserver/root}}),
intersected with the <a>root intersection rectangle</a>.
This value represents the portion of
{{IntersectionObserverEntry/target}} actually visible
within the <a>root intersection rectangle</a>.
{{IntersectionObserverEntry/target}} that intersects with
the <a>root intersection rectangle</a>.
: <dfn>isIntersecting</dfn>
::
True if the {{IntersectionObserverEntry/target}} intersects with the
Expand All @@ -440,6 +461,10 @@ dictionary IntersectionObserverEntryInit {
to intersecting with a zero-area intersection rect (as will happen with
edge-adjacent intersections, or when the {{IntersectionObserverEntry/boundingClientRect}}
has zero area).
: <dfn>isVisible</dfn>
::
Contains the result of running the <a>visibility</a> algorithm
on {{IntersectionObserverEntry/target}}.
: <dfn>intersectionRatio</dfn>
::
If the {{IntersectionObserverEntry/boundingClientRect}} has non-zero area,
Expand Down Expand Up @@ -474,6 +499,8 @@ dictionary IntersectionObserverInit {
DOMString rootMargin = "0px";
DOMString scrollMargin = "0px";
(double or sequence&lt;double>) threshold = 0;
long delay = 0;
boolean trackVisibility = false;
};
</pre>

Expand Down Expand Up @@ -513,6 +540,16 @@ dictionary IntersectionObserverInit {
by <a>getting the bounding box</a> for <a for="IntersectionObserver">target</a>.

Note: 0.0 is effectively "any non-zero number of pixels".
: <dfn>delay</dfn>
::
A number specifying the minimum delay in milliseconds
between notifications from the observer for a given target.
: <dfn>trackVisibility</dfn>
::
A boolean indicating whether the observer should track <a>visibility</a>.
Note that tracking <a>visibility</a> is likely to be a more expensive operation
than tracking intersections. It is recommended that this option be used
only when necessary.
</div>

<h2 dfn id='intersection-observer-processing-model'>
Expand All @@ -538,23 +575,38 @@ Element</h4>
<dfn attribute for=Element>\[[RegisteredIntersectionObservers]]</dfn> slot,
which is initialized to an empty list.
This list holds <dfn interface>IntersectionObserverRegistration</dfn> records,
which have an <dfn attribute for=IntersectionObserverRegistration>observer</dfn> property
holding an {{IntersectionObserver}}, a <dfn attribute for=IntersectionObserverRegistration>previousThresholdIndex</dfn> property
holding a number between -1 and the length of the observer's {{IntersectionObserver/thresholds}} property (inclusive), and
a <dfn attribute for=IntersectionObserverRegistration>previousIsIntersecting</dfn> property holding a boolean.
which have:
* an <dfn attribute for=IntersectionObserverRegistration>observer</dfn> property
holding an {{IntersectionObserver}}.
* a <dfn attribute for=IntersectionObserverRegistration>previousThresholdIndex</dfn> property
holding a number between -1 and the length of the observer's {{IntersectionObserver/thresholds}} property (inclusive).
* a <dfn attribute for=IntersectionObserverRegistration>previousIsIntersecting</dfn> property
holding a boolean.
* a <dfn attribute for=IntersectionObserverRegistration>lastUpdateTime</dfn> property
holding a {{DOMHighResTimeStamp}} value.
* a <dfn attribute for=IntersectionObserverRegistration>previousIsVisible</dfn> property
holding a boolean.

<h4 id='intersection-observer-private-slots'>
IntersectionObserver</h4>

{{IntersectionObserver}} objects have internal
<dfn attribute for=IntersectionObserver>\[[QueuedEntries]]</dfn> and
<dfn attribute for=IntersectionObserver>\[[ObservationTargets]]</dfn> slots,
which are initialized to empty lists and an internal
<dfn attribute for=IntersectionObserver>\[[callback]]</dfn> slot
which is initialized by {{IntersectionObserver(callback, options)}}</a>.
They also have internal <dfn attribute for=IntersectionObserver>\[[rootMargin]]</dfn>
and <dfn attribute for=IntersectionObserver>\[[scrollMargin]]</dfn> slots
which are lists of four pixel lengths or percentages.
{{IntersectionObserver}} objects have the following internal slots:
* A <dfn attribute for=IntersectionObserver>\[[QueuedEntries]]</dfn> slot
initialized to an empty list.
* A <dfn attribute for=IntersectionObserver>\[[ObservationTargets]]</dfn> slot
initialized to an empty list.
* A <dfn attribute for=IntersectionObserver>\[[callback]]</dfn> slot
which is initialized by {{IntersectionObserver(callback, options)}}.
* A <dfn attribute for=IntersectionObserver>\[[rootMargin]]</dfn> slot
which is a list of four pixel lengths or percentages.
* A <dfn attribute for=IntersectionObserver>\[[scrollMargin]]</dfn> slot
which is a list of four pixel lengths or percentages.
* A <dfn attribute for=IntersectionObserver>\[[thresholds]]</dfn> slot
which is initialized by {{IntersectionObserver(callback, options)}}.
* A <dfn attribute for=IntersectionObserver>\[[delay]]</dfn> slot
which is initialized by {{IntersectionObserver(callback, options)}}.
* A <dfn attribute for=IntersectionObserver>\[[trackVisibility]]</dfn> slot
which is initialized by {{IntersectionObserver(callback, options)}}.

<h3 id='algorithms'>
Algorithms</h2>
Expand Down Expand Up @@ -584,7 +636,12 @@ and an {{IntersectionObserverInit}} dictionary |options|, run these steps:
8. If |thresholds| is empty, append <code>0</code> to |thresholds|.
9. The {{IntersectionObserver/thresholds}} attribute getter will return
this sorted |thresholds| list.
10. Return |this|.
10. Let |delay| be the value of |options|.{{IntersectionObserverInit/delay}}.
11. If |options|.{{IntersectionObserverInit/trackVisibility}} is true
and |delay| is less than <code>100</code>, set |delay| to <code>100</code>.
11. Set |this|'s internal {{[[delay]]}} slot to |options|.{{IntersectionObserverInit/delay}} to |delay|.
12. Set |this|'s internal {{[[trackVisibility]]}} slot to |options|.{{IntersectionObserverInit/trackVisibility}}.
13. Return |this|.

<h4 id='observe-target-element'>Observe a target Element</h4>

Expand All @@ -597,7 +654,8 @@ and an {{Element}} |target|, follow these steps:
an {{IntersectionObserverRegistration}} record
with an {{IntersectionObserverRegistration/observer}} property set to |observer|,
a {{IntersectionObserverRegistration/previousThresholdIndex}} property set to <code>-1</code>,
and a {{IntersectionObserverRegistration/previousIsIntersecting}} property set to <code>false</code>.
a {{IntersectionObserverRegistration/previousIsIntersecting}} property set to false,
and a {{IntersectionObserverRegistration/previousIsVisible}} property set to false.
3. Append |intersectionObserverRegistration|
to |target|'s internal {{[[RegisteredIntersectionObservers]]}} slot.
4. Add |target| to |observer|'s internal {{[[ObservationTargets]]}} slot.
Expand Down Expand Up @@ -691,6 +749,32 @@ run these steps:
6. Map |intersectionRect| to the coordinate space of the <a>viewport</a> of the {{document}} containing |target|.
7. Return |intersectionRect|.

<h4 id='calculate-visibility-algo'>
Compute whether a Target is unoccluded, untransformed, unfiltered, and opaque.</h4>

To compute the <dfn>visibility</dfn> of a <a for="IntersectionObserver">target</a>, run these steps:
1. If the |observer|'s {{IntersectionObserver/trackVisibility}} attribute is false, return false.
2. If the <a for="IntersectionObserver">target</a> has an <a>effective transformation matrix</a> other than a 2D translation or proportional 2D upscaling, return false.
3. If the <a for="IntersectionObserver">target</a>, or any element in its <a>containing block chain</a>, has an effective opacity other than 100%, return false.
4. If the <a for="IntersectionObserver">target</a>, or any element in its <a>containing block chain</a>, has any filters applied, return false.
5. If the implementation cannot guarantee that the <a for="IntersectionObserver">target</a> is completely unoccluded by other page content, return false.

Note: Implementations should use the <a>ink overflow rectangle</a> of page content when determining whether a <a for="IntersectionObserver">target</a> is occluded. For blur effects, which have theoretically infinite extent, the <a>ink overflow rectangle</a> is defined by the finite-area approximation described for the <a>blur</a> filter function.

6. Return true.

<h4 id='calculate-effective-transformation-matrix'>Calculate a <a for="IntersectionObserver">target</a>'s Effective Transformation Matrix</h4>
To compute the <dfn>effective transformation matrix</dfn> of a <a for="IntersectionObserver">target</a>, run these steps:
1. Let |matrix| be the <a>serialization</a> of the <a>identity transform function</a>.
2. Let |container| be the target.
3. While |container| is not the <a>intersection root</a>:
1. Set |t| to |container|'s <a>transformation matrix</a>.
2. Set |matrix| to |t| <a>post-multiplied</a> by |matrix|.
3. If |container| is the root element of a <a>nested browsing context</a>,
update |container| to be the <a>browsing context container</a> of |container|. Otherwise, update |container| to be the <a>containing block</a> of |container|.
4. Return |matrix|.


<h4 id='update-intersection-observations-algo'>
Run the Update Intersection Observations Steps</h4>

Expand All @@ -703,45 +787,54 @@ To <dfn export>run the update intersection observations steps</dfn> for a
2. For each |observer| in |observer list|:
1. Let |rootBounds| be |observer|'s <a>root intersection rectangle</a>.
2. For each |target| in |observer|'s internal {{[[ObservationTargets]]}} slot, processed in the same order that {{observe()}} was called on each |target|:
1. Let:
1. Let |registration| be the {{IntersectionObserverRegistration}} record
in |target|'s internal {{[[RegisteredIntersectionObservers]]}} slot
whose {{IntersectionObserverRegistration/observer}} property is equal to |observer|.
2. If <code>(|time| - |registration|.{{IntersectionObserverRegistration/lastUpdateTime}} < |observer|.{{IntersectionObserver/delay}})</code>, skip further processing for |target|.
3. Set |registration|.{{IntersectionObserverRegistration/lastUpdateTime}} to |time|.
4. Let:
- |thresholdIndex| be 0.
- |isIntersecting| be false.
- |targetRect| be a {{DOMRectReadOnly}} with |x|, |y|, |width|, and |height| set to 0.
- |intersectionRect| be a {{DOMRectReadOnly}} with |x|, |y|, |width|, and |height| set to 0.
2. If the <a>intersection root</a> is not the <a>implicit root</a>,
5. If the <a>intersection root</a> is not the <a>implicit root</a>,
and |target| is not in the same {{document}} as the <a>intersection root</a>,
skip to step 11.
3. If the <a>intersection root</a> is an {{Element}},
6. If the <a>intersection root</a> is an {{Element}},
and |target| is not a descendant of the <a>intersection root</a>
in the <a>containing block chain</a>, skip to step 11.
4. Set |targetRect| to the {{DOMRectReadOnly}} obtained by <a>getting the bounding box</a> for
7. Set |targetRect| to the {{DOMRectReadOnly}} obtained by <a>getting the bounding box</a> for
|target|.
4. Let |intersectionRect| be the result of running the <a>compute the intersection</a>
8. Let |intersectionRect| be the result of running the <a>compute the intersection</a>
algorithm on |target| and |observer|'s <a>intersection root</a>.
5. Let |targetArea| be |targetRect|'s area.
6. Let |intersectionArea| be |intersectionRect|'s area.
7. Let |isIntersecting| be true if |targetRect| and |rootBounds| intersect or are edge-adjacent,
9. Let |targetArea| be |targetRect|'s area.
10. Let |intersectionArea| be |intersectionRect|'s area.
11. 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).
9. If |targetArea| is non-zero, let |intersectionRatio| be |intersectionArea| divided by |targetArea|.<br>
12. If |targetArea| is non-zero, let |intersectionRatio| be |intersectionArea| divided by |targetArea|.<br>
Otherwise, let |intersectionRatio| be <code>1</code> if |isIntersecting| is true, or <code>0</code> if |isIntersecting| is false.
10. Set |thresholdIndex| to the index of the first entry in |observer|.{{thresholds}} whose value is greater than |intersectionRatio|, or the length of |observer|.{{thresholds}} if |intersectionRatio| is greater than or equal to the last entry in |observer|.{{thresholds}}.
11. Let |intersectionObserverRegistration| be the {{IntersectionObserverRegistration}} record
in |target|'s internal {{[[RegisteredIntersectionObservers]]}} slot
whose {{IntersectionObserverRegistration/observer}} property is equal to |observer|.
12. Let |previousThresholdIndex| be the |intersectionObserverRegistration|'s
13. Set |thresholdIndex| to the index of the first entry in |observer|.{{thresholds}} whose value is greater than |intersectionRatio|, or the length of |observer|.{{thresholds}} if |intersectionRatio| is greater than or equal to the last entry in |observer|.{{thresholds}}.
14. Let |isVisible| be the result of running the <a>visibility</a> algorithm on |target|.
15. Let |previousThresholdIndex| be the |registration|'s
{{IntersectionObserverRegistration/previousThresholdIndex}} property.
13. Let |previousIsIntersecting| be the |intersectionObserverRegistration|'s
16. Let |previousIsIntersecting| be the |registration|'s
{{IntersectionObserverRegistration/previousIsIntersecting}} property.
14. If |thresholdIndex| does not equal |previousThresholdIndex| or if
|isIntersecting| does not equal |previousIsIntersecting|,
17. Let |previousIsVisible| be the |registration|'s
{{IntersectionObserverRegistration/previousIsVisible}} property.
18. If |thresholdIndex| does not equal |previousThresholdIndex|,
or if |isIntersecting| does not equal |previousIsIntersecting|,
or if |isVisible| does not equal |previousIsVisible|,
<a>queue an IntersectionObserverEntry</a>,
passing in |observer|, |time|, |rootBounds|,
|targetRect|, |intersectionRect|, |isIntersecting|, and |target|.
15. Assign |thresholdIndex| to |intersectionObserverRegistration|'s
|targetRect|, |intersectionRect|, |isIntersecting|,
|isVisible|, and |target|.
19. Assign |thresholdIndex| to |registration|'s
{{IntersectionObserverRegistration/previousThresholdIndex}} property.
16. Assign |isIntersecting| to |intersectionObserverRegistration|'s
20. Assign |isIntersecting| to |registration|'s
{{IntersectionObserverRegistration/previousIsIntersecting}} property.
21. Assign |isVisible| to |registration|'s
{{IntersectionObserverRegistration/previousIsVisible}} property.

<h3 id='lifetime'>
IntersectionObserver Lifetime</h2>
Expand Down

0 comments on commit 027e2dd

Please sign in to comment.