Skip to content

[css-scroll-snap-2] Add SnapEvent definition #9515

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

Merged
merged 18 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
333 changes: 232 additions & 101 deletions css-scroll-snap-2/Overview.bs
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ Snap Events {#snap-events}
████████ ███ ████████ ██ ██ ██ ██████
-->

{{snapChanged}} and {{snapChanging}} {#snapchanged-and-snapchanging}
{{snapchanged}} and {{snapchanging}} {#snapchanged-and-snapchanging}
--------------------------------------------

CSS scroll snap points are often used as a mechanism to
Expand All @@ -275,119 +275,250 @@ Snap Events {#snap-events}
<table class="data" id="eventhandlers">
<thead>
<tr>
<th>Event handler
<th>Event handler event type
<th>Event</th>
<th>Interface</th>
<th>Targets</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<th><dfn event>snapChanged</dfn>
<td>{{scroll!!event}}
<th><dfn for="snapchanged" event>snapchanged</dfn></th>
<td>{{SnapEvent}}</td>
<td>scroll containers</td>
<td>Fired at the scrolling element or {{Document}} at the end of a scroll (before a {{scrollend}} event)
or after a <a href="https://drafts.csswg.org/css-scroll-snap-1/#re-snap">layout snap</a>
if the element that the scrolling element or Document is snapped to changed.</td>
</tr>
<tr>
<th><dfn event>snapChanging</dfn>
<td>{{scroll!!event}}
<th><dfn for="snapchanging" event>snapchanging</dfn></th>
<td>{{SnapEvent}}</td>
<td>scroll containers</td>
<td>Fired at the scrolling element or {{Document}} during scrolling (before a {{scroll}} event),
if the element that the scrolling would cause the scroller to snap to is
different from the target reported by the last snapchanging event that was fired.</td>
</tr>
</tbody>
</table>
<h4>SnapEvents</h4>
<pre class="idl">
[Exposed=Window]
interface SnapEvent : Event {
constructor(DOMString type, optional SnapEventInit eventInitDict = {});
readonly attribute EventTarget? target;
readonly attribute SnapTargetList snappedTargets;
readonly attribute SnapTargetList snapTargets;
readonly attribute boolean invokedProgrammatically;
readonly attribute boolean smoothlyScrolled;
};

[Exposed=Window]
interface SnapTargetList {
readonly attribute SnapTargetArray x;
readonly attribute SnapTargetArray y;
<h4 for="snapchanged" id="snapchanged"> snapchanged </h4>
{{snapchanged}} indicates that the snap area to which a snap container is snapped along either axis has changed.
{{snapchanged}} is dispatched:

<ol>
<li>
when a scrolling operation is <a spec="cssom-view-1" lt="scroll completed">completed</a>
if, for either the block or inline axis, the
element which the snap container is snapped to is different from the element
it most recently snapped to in that axis. For snap containers with
''scroll-snap-type/proximity'' strictness, the scroll may result in the snap
container no longer being snapped to any element. [[css-scroll-snap-1#choosing]]
describes the method a UA follows when choosing between elements which are
<a spec="css-scroll-snap-1" lt="scroll snap area">snap areas</a>.
</li>
<li> if there is a change to a snap container's style such that it goes from
having a non-'none' value for [[css-scroll-snap-1#scroll-snap-type|scroll-snap-type]]
to having a 'none' value or vice versa.
</li>
<li> if, after a [[css-scroll-snap-1#re-snap|layout change]], the element to
which a snap container is snapped to changes, regardless of whether there is
a change in scroll position after the layout change.
</li>
</ol>

Scrolling operations always lead to {{scrollend}} events being fired. If a
scrolling operation also results in a {{snapchanged}} event being fired, the
{{snapchanged}} event should be fired before the {{scrollend}} event.

Each {{Document}} has an associated list of
<dfn export for=Document>pending snapchanged event targets</dfn>, initially empty.

Each
<a spec=css-scroll-snap lt="scroll snap container">snap container</a> has
one <dfn export>snapchangedTargetBlock</dfn> and one
<dfn export>snapchangedTargetInline</dfn> in the block and inline axes
respectively, each of which can either be null if the container is not
snapped in that axis or the {{Element}} to which the container is snapped.

When asked to <dfn export for=Document>update snapchanged targets</dfn>
for a <a spec=css-scroll-snap lt="scroll snap container">snap container</a>,
|snapcontainer|, run these steps:

1. Let <var>doc</var> be |snapcontainer|'s associated {{Document}}.
1. Let <var>blockTarget</var> be the <a>snapchangedTargetBlock</a> associated
with |snapcontainer|.
1. Let <var>inlineTarget</var> be the <a>snapchangedTargetInline</a> associated
with |snapcontainer|.
1. Let <var>blockSnapchangingTarget</var> be the <a>snapchangingTargetBlock</a>
associated with |snapcontainer|.
1. Let <var>inlineSnapchangingTarget</var> be the
<a>snapchangingTargetInline</a> associated with |snapcontainer|.
1. Let <var>snap targets changed</var> be a boolean flag that is initially false.
1. If <var>blockTarget</var> is not the same element as <var>blockSnapchangingTarget</var> or
1. Set the <a>snapchangedTargetBlock</a> associated with |snapcontainer| to
<var>blockSnapchangingTarget</var>.
1. Set <var>snap targets changed</var> to true.
1. If <var>inlineTarget</var> is not the same element as <var>inlineSnapchangingTarget</var>:
1. Set the <a>snapchangedTargetInline</a> associated with |snapcontainer| to
<var>inlineSnapchangingTarget</var>.
1. Set <var>snap targets changed</var> to true.
1. If <var>snap targets changed</var> is true:
1. If |snapcontainer| is not already in <var>doc</var>'s
<a>pending snapchanged event targets</a>:
1. Append |snapcontainer| to <var>doc</var>'s
<a>pending snapchanged event targets</a>.

Note: When snapping occurs on a scroller (either due to a layout change or a
scrolling operation) the <a>snapchangingTargetBlock</a> and <a>snapchangingTargetInline</a>
associated with that scroller are updated and represent the current snap targets
of that scroller. This allows the <a>update snapchanged targets</a> algorithm
to use these elements to determine whether a {{snapchanged}} event should be fired.

When asked to <dfn>dispatch pending snapchanged events</dfn> for a {{Document}},
<var>doc</var>, run these steps:
1. For each item <var>target</var> in |doc|'s <a>pending snapchanged event targets</a>:
1. Fire a {{SnapEvent}}, |snapevent|, named {{snapchanged}} at <var>target</var>
and let |snapevent|'s {{SnapEvent/snapTargetBlock}} and
{{SnapEvent/snapTargetInline}} attributes be the <a>snapchangedTargetBlock</a> and the
<a>snapchangedTargetInline</a>, respectively, that are associated with <var>target</var>.
1. Empty <var>doc</var>'s <a>pending snapchanged event targets</a>.

<h4 id="snapchanging"> snapchanging </h4>
{{snapchanging}} is dispatched:
* during a scrolling operation, if the element to which a
<a spec=css-scroll-snap lt="scroll snap container">snap container</a> would
<a spec="css-scroll-snap-1" lt="scroll snap">snap</a> (in either axis) changes, or
* if a [[css-scroll-snap-1#re-snap|layout change]] occurs such that a {{snapchanged}} event
is to be dispatched. In this case, as with the scrolling case, the {{snapchanging}} event
should be dispatched before the {{snapchanged}} event.

A scrolling operation might animate towards a particular position (e.g.
scrollbar arrow clicks, arrow key presses, "behavior: smooth" programmatic
scrolls) or might directly track a user's input (e.g. touch scrolling, scrollbar
dragging). In either case, the user agent [[css-scroll-snap-1#choosing|chooses]] an
<dfn>eventual snap target</dfn> in each axis to which the scroller will
<a spec="css-scroll-snap-1" lt="scroll snap">snap</a> after the scrolling operation
reaches its intended scroll position.
* In the former case, the intended scroll position is the scroll animation's
target scroll offset.
* In the latter case, the intended scroll position is the current scroll offset as
determined by the user's input.

{{snapchanging}} aims to let the web page know, as early as possible,
that the scrolling operation will result in a change in the element the snap
container is snapped to. The user agent should evaluate whether to trigger
{{snapchanging}} based on the <a>eventual snap target</a> to which the scroller would
<a spec="css-scroll-snap-1" lt="scroll snap">snap</a> were the scrolling operation
to reach its intended scroll position.

Note: Since snapchanging gives the web page hints about future snapping,
the snapping hinted at by a snapchanging event might not materialize since it
will be possible for subsequent scrolling input to further alter the snap
container's scroll position and result in a different eventual snap target.


{{snapchanging}} events are fired before {{scroll}} events.

Each {{Document}} has an associated list of
<dfn export for=Document>pending snapchanging event targets</dfn>, initially empty.

Each
<a spec=css-scroll-snap lt="scroll snap container">snap container</a> has
one <dfn>snapchangingTargetBlock</dfn>
and one <dfn>snapchangingTargetInline</dfn>in the block and inline axes
respectively, each of which can either be null if the container is not
snapping in that axis or the {{Element}} to which the container is snapping.

When asked to <dfn export for=Document>update snapchanging targets</dfn>
for a <a spec=css-scroll-snap lt="scroll snap container">snap container</a>,
|snapcontainer|, given an {{Element}} newBlockTarget and an {{Element}}
newInlineTarget, run these steps:

1. Let <var>doc</var> be |snapcontainer|'s associated {{Document}}.
1. Let <var>blockTarget</var> be the <a>snapchangingTargetBlock</a> that is
associated with |snapcontainer|.
1. Let <var>inlineTarget</var> be the <a>snapchangingTargetInline</a> that is
associated with |snapcontainer|.
1. If <var>newBlockTarget</var> is not the same element as <var>blockTarget</var>:
1. Set the <a>snapchangingTargetBlock</a> associated with |snapcontainer| to
<var>newBlockTarget</var>.
1. If |snapcontainer| is not already in <var>doc</var>'s
<a>pending snapchanging event targets</a>,
1. Append |snapcontainer| to <var>doc</var>'s
<a>pending snapchanging event targets</a>
1. If <var>newInlineTarget</var> is not the same element as <var>inlineTarget</var>:
1. Set the <a>snapchangingTargetInline</a> associated with |snapcontainer| to
<var>newInlineTarget</var>.
1. If |snapcontainer| is not already in <var>doc</var>'s
<a>pending snapchanging event targets</a>,
1. Append |snapcontainer| to <var>doc</var>'s
<a>pending snapchanging event targets</a>.

When asked to <dfn>dispatch pending snapchanging events</dfn> for a {{Document}},
<var>doc</var>, run these steps:
1. For each item <var>target</var> in |doc|'s <a>pending snapchanging event targets</a>:
1. Fire a {{SnapEvent}}, |snapevent|, named {{snapchanging}} at <var>target</var>
and let |snapevent|'s {{SnapEvent/snapTargetBlock}} and
{{SnapEvent/snapTargetInline}} attributes be the <a>snapchangingTargetBlock</a> and the
<a>snapchangingTargetInline</a>, respectively, that are associated with <var>target</var>.
1. Empty <var>doc</var>'s <a>pending snapchanging event targets</a>.

<h4 id="snap-events-on-layout-changes">Snap Events due to Layout Changes </h4>
When a <a spec=css-scroll-snap lt="scroll snap container">snap container</a>,
|snapcontainer|, [[css-scroll-snap-1#re-snap|re-snaps]], run these steps:

1. Let <var>newBlockTarget</var> be the element that |snapcontainer| has
<a spec="css-scroll-snap-1" lt="scroll snap">snapped</a> to
in the block axis or null if it did not snap to any element.
1. Let <var>newInlineTarget</var> be the element that |snapcontainer| has
<a spec="css-scroll-snap-1" lt="scroll snap">snapped</a> to
in the inline axis or null if it did not snap to any element.
1. Run the steps to <a>update snapchanging targets</a> with <var>newBlockTarget</var>
as newBlockTarget and <var>newInlineTarget</var> as newInlineTarget.
1. Run the steps to <a>update snapchanged targets</a> for |snapcontainer|.


SnapEvent interface
-------------------

<pre class="idl">
dictionary SnapEventInit : EventInit {
Node? snapTargetBlock;
Node? snapTargetInline;
};

[Exposed=Window]
interface SnapTargetArray {
readonly attribute unsigned long length;
getter EventTarget? item (unsigned long index);
interface SnapEvent : Event {
constructor(DOMString type, optional SnapEventInit eventInitDict = {});
readonly attribute Node? snapTargetBlock;
readonly attribute Node? snapTargetInline;
};
</pre>

dictionary SnapEventInit : EventModifierInit {
sequence&lt;EventTarget> snappedTargetsX = [];
sequence&lt;EventTarget> snappedTargetsY = [];
sequence&lt;EventTarget> snapTargetsListX = [];
sequence&lt;EventTarget> snapTargetsListY = [];
};
</pre>
<dl>
<div dfn-type=attribute class=attributes dfn-for="SnapEvent">
: <dfn>snapTargetBlock</dfn>
::
The element that the snap container is snapped to in the block axis
at the <a spec="css-scroll-snap-1" lt="scroll snap position">snap position</a>
for the associated snap event.
</div>
<div dfn-type=attribute class=attributes dfn-for="SnapEvent">
: <dfn>snapTargetInline</dfn>
::
The element that the snap container is snapped to in the inline axis
at the <a spec="css-scroll-snap-1" lt="scroll snap position">snap position</a>
for the associated snap event.
</div>

For {{snapchanged}} events, the snap position is the position already
realized by the snap container after a scroll snap. For {{snapchanging}}
events it is the snap position that the snap container will eventually
snap to when the scrolling operation ends.

<dl>
<dt><code>SnapEvent . target</code></dt>
<dd>
This is the scroll container of the snapped-to element.
</dd>
<dt><code>SnapEvent . snappedTargets</code></dt>
<dd>
An object with 2 keys for each axis, each key returns an array of snapped targets.
</dd>
<dt><code>SnapEvent . snapTargets</code></dt>
<dd>
An object with 2 keys for each axis, each key returns an array of the aggregated snap children.
</dd>
<dt><code>SnapEvent . invokedProgrammatically</code></dt>
<dd>
A boolean informing developers if a user or script invoked scroll that caused <a>snapChanged</a>.
</dd>
<dt><code>SnapEvent . smoothlyScrolled</code></dt>
<dd>
A boolean informing developers if the snap change was instant or interpolated.
</dd>
</dl>

<h4> snapChanged </h4>

The event is dispatched when a new snap target has been snapped to, providing what caused it.
It should be dispatched:

* if user scroll interaction has ended and a new item has been rested on. If a user is still touching the screen or the touchpad, this event should not fire, even if the scroll position is exactly at a snapped element's position.
* if animations or transitions change the snapped style of the container or children, IF they have in fact changed the snap target.

<table>
<tr><th>Type</th><td><strong><code>snapChanged</code></strong></td></tr>
<tr><th>Interface</th><td>{{SnapEvent}}</td></tr>
<tr><th>Sync / Async</th><td>Async</td></tr>
<tr><th>Bubbles</th><td>Yes</td></tr>
<tr><th>Trusted Targets</th><td><code>Element</code></td></tr>
<tr><th>Cancelable</th><td>No</td></tr>
<tr><th>Composed</th><td>Yes</td></tr>
<tr><th>Default action</th><td>None</td></tr>
<tr><th>Context<br/>(trusted events)</th><td><ul>
<li>{{Event}}.{{Event/target}} : scroll container of the snapped-to element</li>
<li>{{SnapEvent}}.{{snappedTargets}} : an object with 2 keys for each axis, each key returns an array of snapped targets</li>
<li>{{SnapEvent}}.{{snapTargets}} : an object with 2 keys for each axis, each key returns an array of the aggregated snap children</li>
<li>{{SnapEvent}}.{{invokedProgrammatically}} : a boolean informing developers if a user or script invoked scroll that caused <a>snapChanged</a></li>
<li>{{SnapEvent}}.{{smoothlyScrolled}} : a boolean informing developers if the snap change was instant or interpolated</li>
</ul></td></tr>
</table>

<h4> snapChanging </h4>

Should fire every time, and as soon as, the UA has determined a new snap child until the new child is snapped to.

<table>
<tr><th>Type</th><td><strong><code>snapChanging</code></strong></td></tr>
<tr><th>Interface</th><td>{{SnapEvent}}</td></tr>
<tr><th>Sync / Async</th><td>Async</td></tr>
<tr><th>Bubbles</th><td>Yes</td></tr>
<tr><th>Trusted Targets</th><td><code>Element</code></td></tr>
<tr><th>Cancelable</th><td>No</td></tr>
<tr><th>Composed</th><td>Yes</td></tr>
<tr><th>Default action</th><td>None</td></tr>
<tr><th>Context<br/>(trusted events)</th><td><ul>
<li>{{Event}}.{{Event/target}} : scroll container of the snapped-to element.</li>
<li>{{SnapEvent}}.{{snappedTargets}}
<li>{{SnapEvent}}.{{snapTargets}} : an object with 2 keys for each axis, each key returns an array of the aggregated snap children.</li>
<li>{{SnapEvent}}.{{invokedProgrammatically}} : a boolean informing developers if a user or script invoked scroll that caused <a>snapChanged.</a></li>
<li>{{SnapEvent}}.{{smoothlyScrolled}} : a boolean informing developers if the snap change was instant or interpolated.</li>
</ul></td></tr>
</table>

A {{SnapEvent}} should not bubble and should not be cancellable.
<!--
██ ███████ ██ ██ ██████ ██ ██ ███ ██ ██ ████████ ██████
██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██
Expand Down
Loading