csswg-drafts icon indicating copy to clipboard operation
csswg-drafts copied to clipboard

[css-shared-element-transitions-1] User input should be ignored when rendering is suppressed.

Open khushalsagar opened this issue 2 years ago • 1 comments

#7784 details how rendering should be suppressed during a transition. We also need to ignore user input (irrespective of how rendering is suppressed) in this state. That's because the visual state of the Document displayed to the user doesn't align with the DOM state.

Note that the user agent may need to dispatch synthetic events to ensure the input stream observed by the developer makes sense. For instance, the user puts their finger down when rendering is suppressed which causes a touchstart event. If rendering is unsuppressed while the user finger is still down script would directly see a touchmove event. It makes sense to dispatch a synthetic touchstart event in this case. But this detail can be left up to the user agent.

khushalsagar avatar Sep 26 '22 22:09 khushalsagar

Proposed Resolution: User input is discarded for each Document with transition suppress rendering set.

khushalsagar avatar Oct 19 '22 16:10 khushalsagar

https://user-images.githubusercontent.com/93594/205042084-dd6f02b6-3068-4734-bf80-b51bd47ff5a2.mp4

There are three states in terms of what the user sees:

  • The real DOM
  • Paused render
  • The transition pseudos

With the real DOM, input goes to the real DOM. With the pseudos, input goes to the document element. I don't think we should add a third behaviour for the paused render case.

Throwing input away could cause more of a 'glitch' in the gesture example, as dropped events would mean the initial position could be out of sync with actual pointer position.

Doing a hit-test on the real DOM seems risky, as elements might be in completely different places.

So, I vote that we do the same as in the "transition pseudos" state, and send events to the document element.

jakearchibald avatar Dec 01 '22 11:12 jakearchibald

With the pseudos, input goes to the document element.

Input doesn't necessarily go to the document element for the transition pseudos case. Try the following example:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
  <style>
    .before {
      background: grey;
    }
    .after {
      background: lightblue;
    }
   
    #target {
      view-transition-name: target;
      contain: layout;
    }
    html {
      view-transition-name: none;
    }
    html::view-transition-new(*) {
      animation-duration: 5s;
    }
  </style>
</head>
<body>
  <script>
    function flip() {
      direct.classList.toggle("before");
      direct.classList.toggle("after");
    }
   
    function doTransition() {
      document.startViewTransition(() => {
        target.classList.toggle("before");
        target.classList.toggle("after");
      });
    }
  </script>
  <button class="before" onclick="doTransition()" id="target">Animate</button>
  <button class="before" onclick="flip()" id="direct">Flip</button>
</body>
</html>

Event handlers on DOM elements not participating in the transition continue to work throughout the transition. This is handy if your transitions changes a subset of the page.

The spec intent was to handle the pseudo DOM case the same as we would for hit-testing on any other pseudo-element. For example if the pointer location of the event is ::before, the event is dispatched to its originating element. But pointer-events: none set on the pseudo-element will cause the hit-test to continue to the next possible target. Same way if the pointer location of the event is on any of the generated pseudos, the event is dispatched on their ultimate originating element (the document element). Otherwise it can resolve to a DOM element, if the location is different or the developer adds pointer-events: none to the pseudos.

That said, I'm ok with dispatching events to the document element while rendering is paused instead of dropping them entirely. I was hard pressed to think of a use-case where the developer could do anything meaningful with the events. Rendering is paused so its not like visuals can be updated in response to the event. But, if the transition is being driven by a gesture then the curve of the gesture could be used to influence the animation. The developer would need all touch move events to keep track of the curve.

khushalsagar avatar Jan 03 '23 21:01 khushalsagar

I'm ok with dispatching events to the document element while rendering is paused instead of dropping them entirely.

A point worth clarifying here is that this is only relevant if a hit-test is needed while rendering is paused. There could be a case where the user input starts a scroll gesture, which has locked a target node, and a transition is initiated. The target node for the gesture shouldn't change for the duration of the scroll gesture.

khushalsagar avatar Jan 16 '23 15:01 khushalsagar

That's fair. I think I'm happy with either:

Option 1: Delay events until rendering is unblocked, then hit-test against what's now under the pointer. I think this will be hard to spec 😄

Option 2: Target events to the document element. I know this isn't 100% like it is when the pseudos are there, as you've detailed above, but I expect cases where the underlying DOM is revealed to be rare, especially once we have scoped view transitions.

jakearchibald avatar Feb 15 '23 14:02 jakearchibald

Summary for CSSWG:

During a view transition, elements involved in the transition are hidden from the real DOM. In most cases, this means every element is hidden, because every element is somehow involved. However, if you set root's view-transition-name to none, you can scope a transition down to particular elements.

Clicks during a view transition will almost always hit one of the pseudos, because ::view-transition fills the viewport. This means pointer events will apply to the document element. However, developers could change this by altering the size of ::view-transition, or applying pointer-events: none.

But, what should we do while the document.startViewTransition(callback) callback has been called, but its returned promise hasn't yet settled. We freeze rendering during this, so what the user's seeing might not represent the elements beneath a particular point.

What should we do with clicks during this period?

Currently on the web platform, if you block the event loop, eg with a JavaScript while loop, it will also block rendering. If the user clicks anywhere, tasks are queued to perform hit-testing and events. If the DOM is changed just before unblocking the event loop, those clicks will now hit-test against a different document structure to the one the user felt they performed clicks against. It's not great, but it's what happens.

Option 1: Queue user inputs

We prevent tasks being taken from the user input task source during this period. It isn't a full event loop block, it just queues up input-related tasks.

Then, once the transition starts, the task source will be unblocked, and hit-testing will happen as normal.

There's a risk of deadlock. Eg, if startViewTransition is called on pointerdown, and the developer waits until pointerup or pointermove before resolving the callback promise.

This option also feels incompatible with scoped transitions, a future planned feature where multiple view transitions can run concurrently if they're scoped to independent elements.

Option 2: Target pointer events to the document element

This is what will happen during the transition in most cases anyway, except in the edge case mentioned above, where the root's view-transition-name is set to none, and the clicks don't hit the pseudos. In that case, click may hit the document element, where they would hit a more specific element during the transition.

jakearchibald avatar Mar 15 '23 12:03 jakearchibald

The CSS Working Group just discussed [css-view-transitions-1] User input should be ignored when rendering is suppressed., and agreed to the following:

  • RESOLVED: target pointer events to the document element
The full IRC log of that discussion <JakeA> https://github.com/w3c/csswg-drafts/issues/7797#issuecomment-1469931231
<khush> Jake's getting 'em all! :)
<fantasai> s/Topic/Subtopic/
<fantasai> s/topic: break//
<emeyer> JakeA: During a transition, elements involved are hidden from the DOM
<Rossen_> q
<emeyer> …That usually means every element is hidden, because the root element has a transition name
<emeyer> …Clicks during a transition will almost always hit the pseudo and fall to the document root
<emeyer> …Authors could change this by changing the root’s transition name to none
<emeyer> …Question is: what do we do with clicks while promises haven’t settled and the transition is frozen?
<emeyer> …Similar case: if you block the event loop using a while loop, it will block rendering
<emeyer> …If a user clicks anywhere, those events are queueing up
<emeyer> …Once the freeze is thawed, the events and hit tests and such are resolved then, which may resolve hits against things that moved
<emeyer> …We can’t do a full event block, but we could take from tasksources and then resolve when the transition starts
<emeyer> …Deadlocks are still possible in this option, which may also conflict with scoped transitions
<emeyer> …We don’t want to block all input events while scoped transition is happening
<emeyer> …Option 2: we apply all events to the document element
<TabAtkins> q+
<emeyer> …In that case, a click may hit the document element whereas if we’d queued it, it might have hit a more specific element
<flackr> q+
<khush> +1 for option 2.
<emeyer> Rossen: I want to make sure we’re sticking to CSS
<emeyer> TabAtkins: +1 for option 2, which seems a little safer; also I hate when things resolve against DOM I wasn’t expecting; also makes things more predictable
<Rossen_> ack TabAtkins
<Rossen_> ack flackr
<emeyer> flackr: I think a common case is that by the time you’d dispatch the events in option 1, the elements will be where they should be
<khush> q+
<emeyer> …Worried that we’re seen cases transitions to move small pieces of sub-documents
<emeyer> …I strongly prefer option 1
<Rossen_> ack khush
<emeyer> khush: If we decide to buffer until rendering is done, the first question is do we dispatch before or after creating the pseudo-DOM?
<emeyer> …Why not just give it to the document?
<emeyer> JakeA: I think flackr’s point is that if you have a pseudo-element is in a certain place, option 2 raises the chances of a thing not being where it was when the click was made
<emeyer> khush: So this is for the case of rootless transitions?
<emeyer> JakeA: This is all about rootless transitions
<emeyer> flackr: If I start a transition on mousedown, as long as my pseudo is pointer-events: none, I can still get that click
<emeyer> …I also feel like you have a halting issue regardless, since there’s no requirement a transition ever finish
<chrishtr> q+
<emeyer> JakeA: Should have clarified that this deadlocks are already limited to four seconds
<emeyer> flackr: So this isn’t new, it’s just we could make them more likely
<emeyer> …If a developer doesn’t finish setting up for a view transition, there will be problems
<emeyer> chrishtr: With option 2, if the dev starts a transition but then does nothing and we wait four seconds, if the user clicks in that 4s window, is the click lost?
<emeyer> JakeA: Yeah, in option 2, the click won’t work
<Rossen_> ack chrishtr
<emeyer> chrishtr: So the default UA behaviors are suppressed
<khush> q+
<emeyer> flackr: In option 1, we wait for the transition to fail to start, the events are processed after the transition is canceled
<emeyer> chrishtr: Could we do an option 3 where user events are still processed and applied to the original target?
<Rossen_> ack khush
<emeyer> khush: Option 1 is very hard to reason about with scoped transitions, whichis why I’m advocating option 2
<emeyer> …The easiest thing is to dispatch an event at the root of the subtree,
<vmpstr> +1
<Rossen_> ack khush
<emeyer> flackr: An event targeted at the document provdes XY coords, yes?
<emeyer> khush: Yes
<emeyer> Rossen: We seem to be sliding to option 2; flackr, you were the main proponent of 1
<emeyer> flackr: I like that option 2 supports scoped transitions better
<emeyer> …I think it’s okay
<emeyer> Rossen: Any other opinions or objections to resolved on option 2?
<emeyer> (silence)
<JakeA> Target pointer events to the document element
<JakeA> This is what will happen during the transition in most cases anyway, except in the edge case mentioned above, where the root's view-transition-name is set to none, and the clicks don't hit the pseudos. In that case, click may hit the document element, where they would hit a more specific element during the transition.
<emeyer> RESOLVED: target pointer events to the document element
<flackr> Or to the root of the transition for future compat with scoped transitions!

css-meeting-bot avatar Mar 15 '23 16:03 css-meeting-bot