Skip to content

Commit 027e2dd

Browse files
Merge pull request #523 from szager-chromium/v2-pr
[IntersectionObserver] #295 V2: visibility detection
2 parents ef83cd2 + 885a800 commit 027e2dd

1 file changed

Lines changed: 135 additions & 42 deletions

File tree

index.bs

Lines changed: 135 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,21 @@ urlPrefix: https://drafts.csswg.org/css-display/
6464
url: #containing-block-chain; type: dfn; text: containing block chain
6565
urlPrefix: http://www.w3.org/TR/css-masking-1/
6666
url: #propdef-clip-path; type:dfn; text: clip-path
67+
urlPrefix: https://drafts.csswg.org/css-overflow-3/
68+
url: #ink-overflow-rectangle; type:dfn; text: ink overflow rectangle
69+
url: #ink-overflow-region; type:dfn; text: ink overflow region
70+
url: #overflow-properties; type:dfn; text: overflow properties
71+
urlPrefix: https://drafts.csswg.org/css-transforms-1/
72+
url: #transformation-matrix; type:dfn; text: transformation matrix
73+
url: #serialization-of-the-computed-value; type:dfn; text: serialization
74+
url: #identity-transform-function; type:dfn; text: identity transform function
75+
url: #post-multiplied; type:dfn; text: post-multiplied
6776
urlPrefix: https://drafts.csswg.org/cssom-view-1/
6877
url: #pinch-zoom; type:dfn; text: pinch zoom
6978
urlPrefix: https://drafts.csswg.org/css2/visuren.html
7079
url: #viewport; type:dfn; text: viewport
71-
urlPrefix: https://drafts.csswg.org/css-overflow-3/
72-
url: #overflow-properties; type:dfn; text: overflow properties
80+
urlPrefix: https://drafts.fxtf.org/filter-effects/
81+
url: #funcdef-filter-blur; type:dfn; text: blur
7382
</pre>
7483

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

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

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

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

311330
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>.
@@ -401,6 +420,7 @@ interface IntersectionObserverEntry {
401420
readonly attribute DOMRectReadOnly boundingClientRect;
402421
readonly attribute DOMRectReadOnly intersectionRect;
403422
readonly attribute boolean isIntersecting;
423+
readonly attribute boolean isVisible;
404424
readonly attribute double intersectionRatio;
405425
readonly attribute Element target;
406426
};
@@ -411,6 +431,7 @@ dictionary IntersectionObserverEntryInit {
411431
required DOMRectInit boundingClientRect;
412432
required DOMRectInit intersectionRect;
413433
required boolean isIntersecting;
434+
required boolean isVisible;
414435
required double intersectionRatio;
415436
required Element target;
416437
};
@@ -428,8 +449,8 @@ dictionary IntersectionObserverEntryInit {
428449
rects (up to but not including {{IntersectionObserver/root}}),
429450
intersected with the <a>root intersection rectangle</a>.
430451
This value represents the portion of
431-
{{IntersectionObserverEntry/target}} actually visible
432-
within the <a>root intersection rectangle</a>.
452+
{{IntersectionObserverEntry/target}} that intersects with
453+
the <a>root intersection rectangle</a>.
433454
: <dfn>isIntersecting</dfn>
434455
::
435456
True if the {{IntersectionObserverEntry/target}} intersects with the
@@ -440,6 +461,10 @@ dictionary IntersectionObserverEntryInit {
440461
to intersecting with a zero-area intersection rect (as will happen with
441462
edge-adjacent intersections, or when the {{IntersectionObserverEntry/boundingClientRect}}
442463
has zero area).
464+
: <dfn>isVisible</dfn>
465+
::
466+
Contains the result of running the <a>visibility</a> algorithm
467+
on {{IntersectionObserverEntry/target}}.
443468
: <dfn>intersectionRatio</dfn>
444469
::
445470
If the {{IntersectionObserverEntry/boundingClientRect}} has non-zero area,
@@ -474,6 +499,8 @@ dictionary IntersectionObserverInit {
474499
DOMString rootMargin = "0px";
475500
DOMString scrollMargin = "0px";
476501
(double or sequence&lt;double>) threshold = 0;
502+
long delay = 0;
503+
boolean trackVisibility = false;
477504
};
478505
</pre>
479506

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

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

518555
<h2 dfn id='intersection-observer-processing-model'>
@@ -538,23 +575,38 @@ Element</h4>
538575
<dfn attribute for=Element>\[[RegisteredIntersectionObservers]]</dfn> slot,
539576
which is initialized to an empty list.
540577
This list holds <dfn interface>IntersectionObserverRegistration</dfn> records,
541-
which have an <dfn attribute for=IntersectionObserverRegistration>observer</dfn> property
542-
holding an {{IntersectionObserver}}, a <dfn attribute for=IntersectionObserverRegistration>previousThresholdIndex</dfn> property
543-
holding a number between -1 and the length of the observer's {{IntersectionObserver/thresholds}} property (inclusive), and
544-
a <dfn attribute for=IntersectionObserverRegistration>previousIsIntersecting</dfn> property holding a boolean.
578+
which have:
579+
* an <dfn attribute for=IntersectionObserverRegistration>observer</dfn> property
580+
holding an {{IntersectionObserver}}.
581+
* a <dfn attribute for=IntersectionObserverRegistration>previousThresholdIndex</dfn> property
582+
holding a number between -1 and the length of the observer's {{IntersectionObserver/thresholds}} property (inclusive).
583+
* a <dfn attribute for=IntersectionObserverRegistration>previousIsIntersecting</dfn> property
584+
holding a boolean.
585+
* a <dfn attribute for=IntersectionObserverRegistration>lastUpdateTime</dfn> property
586+
holding a {{DOMHighResTimeStamp}} value.
587+
* a <dfn attribute for=IntersectionObserverRegistration>previousIsVisible</dfn> property
588+
holding a boolean.
545589

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

549-
{{IntersectionObserver}} objects have internal
550-
<dfn attribute for=IntersectionObserver>\[[QueuedEntries]]</dfn> and
551-
<dfn attribute for=IntersectionObserver>\[[ObservationTargets]]</dfn> slots,
552-
which are initialized to empty lists and an internal
553-
<dfn attribute for=IntersectionObserver>\[[callback]]</dfn> slot
554-
which is initialized by {{IntersectionObserver(callback, options)}}</a>.
555-
They also have internal <dfn attribute for=IntersectionObserver>\[[rootMargin]]</dfn>
556-
and <dfn attribute for=IntersectionObserver>\[[scrollMargin]]</dfn> slots
557-
which are lists of four pixel lengths or percentages.
593+
{{IntersectionObserver}} objects have the following internal slots:
594+
* A <dfn attribute for=IntersectionObserver>\[[QueuedEntries]]</dfn> slot
595+
initialized to an empty list.
596+
* A <dfn attribute for=IntersectionObserver>\[[ObservationTargets]]</dfn> slot
597+
initialized to an empty list.
598+
* A <dfn attribute for=IntersectionObserver>\[[callback]]</dfn> slot
599+
which is initialized by {{IntersectionObserver(callback, options)}}.
600+
* A <dfn attribute for=IntersectionObserver>\[[rootMargin]]</dfn> slot
601+
which is a list of four pixel lengths or percentages.
602+
* A <dfn attribute for=IntersectionObserver>\[[scrollMargin]]</dfn> slot
603+
which is a list of four pixel lengths or percentages.
604+
* A <dfn attribute for=IntersectionObserver>\[[thresholds]]</dfn> slot
605+
which is initialized by {{IntersectionObserver(callback, options)}}.
606+
* A <dfn attribute for=IntersectionObserver>\[[delay]]</dfn> slot
607+
which is initialized by {{IntersectionObserver(callback, options)}}.
608+
* A <dfn attribute for=IntersectionObserver>\[[trackVisibility]]</dfn> slot
609+
which is initialized by {{IntersectionObserver(callback, options)}}.
558610

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

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

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

752+
<h4 id='calculate-visibility-algo'>
753+
Compute whether a Target is unoccluded, untransformed, unfiltered, and opaque.</h4>
754+
755+
To compute the <dfn>visibility</dfn> of a <a for="IntersectionObserver">target</a>, run these steps:
756+
1. If the |observer|'s {{IntersectionObserver/trackVisibility}} attribute is false, return false.
757+
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.
758+
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.
759+
4. If the <a for="IntersectionObserver">target</a>, or any element in its <a>containing block chain</a>, has any filters applied, return false.
760+
5. If the implementation cannot guarantee that the <a for="IntersectionObserver">target</a> is completely unoccluded by other page content, return false.
761+
762+
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.
763+
764+
6. Return true.
765+
766+
<h4 id='calculate-effective-transformation-matrix'>Calculate a <a for="IntersectionObserver">target</a>'s Effective Transformation Matrix</h4>
767+
To compute the <dfn>effective transformation matrix</dfn> of a <a for="IntersectionObserver">target</a>, run these steps:
768+
1. Let |matrix| be the <a>serialization</a> of the <a>identity transform function</a>.
769+
2. Let |container| be the target.
770+
3. While |container| is not the <a>intersection root</a>:
771+
1. Set |t| to |container|'s <a>transformation matrix</a>.
772+
2. Set |matrix| to |t| <a>post-multiplied</a> by |matrix|.
773+
3. If |container| is the root element of a <a>nested browsing context</a>,
774+
update |container| to be the <a>browsing context container</a> of |container|. Otherwise, update |container| to be the <a>containing block</a> of |container|.
775+
4. Return |matrix|.
776+
777+
694778
<h4 id='update-intersection-observations-algo'>
695779
Run the Update Intersection Observations Steps</h4>
696780

@@ -703,45 +787,54 @@ To <dfn export>run the update intersection observations steps</dfn> for a
703787
2. For each |observer| in |observer list|:
704788
1. Let |rootBounds| be |observer|'s <a>root intersection rectangle</a>.
705789
2. For each |target| in |observer|'s internal {{[[ObservationTargets]]}} slot, processed in the same order that {{observe()}} was called on each |target|:
706-
1. Let:
790+
1. Let |registration| be the {{IntersectionObserverRegistration}} record
791+
in |target|'s internal {{[[RegisteredIntersectionObservers]]}} slot
792+
whose {{IntersectionObserverRegistration/observer}} property is equal to |observer|.
793+
2. If <code>(|time| - |registration|.{{IntersectionObserverRegistration/lastUpdateTime}} < |observer|.{{IntersectionObserver/delay}})</code>, skip further processing for |target|.
794+
3. Set |registration|.{{IntersectionObserverRegistration/lastUpdateTime}} to |time|.
795+
4. Let:
707796
- |thresholdIndex| be 0.
708797
- |isIntersecting| be false.
709798
- |targetRect| be a {{DOMRectReadOnly}} with |x|, |y|, |width|, and |height| set to 0.
710799
- |intersectionRect| be a {{DOMRectReadOnly}} with |x|, |y|, |width|, and |height| set to 0.
711-
2. If the <a>intersection root</a> is not the <a>implicit root</a>,
800+
5. If the <a>intersection root</a> is not the <a>implicit root</a>,
712801
and |target| is not in the same {{document}} as the <a>intersection root</a>,
713802
skip to step 11.
714-
3. If the <a>intersection root</a> is an {{Element}},
803+
6. If the <a>intersection root</a> is an {{Element}},
715804
and |target| is not a descendant of the <a>intersection root</a>
716805
in the <a>containing block chain</a>, skip to step 11.
717-
4. Set |targetRect| to the {{DOMRectReadOnly}} obtained by <a>getting the bounding box</a> for
806+
7. Set |targetRect| to the {{DOMRectReadOnly}} obtained by <a>getting the bounding box</a> for
718807
|target|.
719-
4. Let |intersectionRect| be the result of running the <a>compute the intersection</a>
808+
8. Let |intersectionRect| be the result of running the <a>compute the intersection</a>
720809
algorithm on |target| and |observer|'s <a>intersection root</a>.
721-
5. Let |targetArea| be |targetRect|'s area.
722-
6. Let |intersectionArea| be |intersectionRect|'s area.
723-
7. Let |isIntersecting| be true if |targetRect| and |rootBounds| intersect or are edge-adjacent,
810+
9. Let |targetArea| be |targetRect|'s area.
811+
10. Let |intersectionArea| be |intersectionRect|'s area.
812+
11. Let |isIntersecting| be true if |targetRect| and |rootBounds| intersect or are edge-adjacent,
724813
even if the intersection has zero area (because |rootBounds| or |targetRect| have
725814
zero area).
726-
9. If |targetArea| is non-zero, let |intersectionRatio| be |intersectionArea| divided by |targetArea|.<br>
815+
12. If |targetArea| is non-zero, let |intersectionRatio| be |intersectionArea| divided by |targetArea|.<br>
727816
Otherwise, let |intersectionRatio| be <code>1</code> if |isIntersecting| is true, or <code>0</code> if |isIntersecting| is false.
728-
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}}.
729-
11. Let |intersectionObserverRegistration| be the {{IntersectionObserverRegistration}} record
730-
in |target|'s internal {{[[RegisteredIntersectionObservers]]}} slot
731-
whose {{IntersectionObserverRegistration/observer}} property is equal to |observer|.
732-
12. Let |previousThresholdIndex| be the |intersectionObserverRegistration|'s
817+
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}}.
818+
14. Let |isVisible| be the result of running the <a>visibility</a> algorithm on |target|.
819+
15. Let |previousThresholdIndex| be the |registration|'s
733820
{{IntersectionObserverRegistration/previousThresholdIndex}} property.
734-
13. Let |previousIsIntersecting| be the |intersectionObserverRegistration|'s
821+
16. Let |previousIsIntersecting| be the |registration|'s
735822
{{IntersectionObserverRegistration/previousIsIntersecting}} property.
736-
14. If |thresholdIndex| does not equal |previousThresholdIndex| or if
737-
|isIntersecting| does not equal |previousIsIntersecting|,
823+
17. Let |previousIsVisible| be the |registration|'s
824+
{{IntersectionObserverRegistration/previousIsVisible}} property.
825+
18. If |thresholdIndex| does not equal |previousThresholdIndex|,
826+
or if |isIntersecting| does not equal |previousIsIntersecting|,
827+
or if |isVisible| does not equal |previousIsVisible|,
738828
<a>queue an IntersectionObserverEntry</a>,
739829
passing in |observer|, |time|, |rootBounds|,
740-
|targetRect|, |intersectionRect|, |isIntersecting|, and |target|.
741-
15. Assign |thresholdIndex| to |intersectionObserverRegistration|'s
830+
|targetRect|, |intersectionRect|, |isIntersecting|,
831+
|isVisible|, and |target|.
832+
19. Assign |thresholdIndex| to |registration|'s
742833
{{IntersectionObserverRegistration/previousThresholdIndex}} property.
743-
16. Assign |isIntersecting| to |intersectionObserverRegistration|'s
834+
20. Assign |isIntersecting| to |registration|'s
744835
{{IntersectionObserverRegistration/previousIsIntersecting}} property.
836+
21. Assign |isVisible| to |registration|'s
837+
{{IntersectionObserverRegistration/previousIsVisible}} property.
745838

746839
<h3 id='lifetime'>
747840
IntersectionObserver Lifetime</h2>

0 commit comments

Comments
 (0)