w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.49k stars 662 forks source link

Disabling UA transitions for same-document navigations #8747

Open khushalsagar opened 1 year ago

khushalsagar commented 1 year ago

Smooth visual transitions as users navigate on the web can lower cognitive load by helping users stay in context. However, the user experience is bad if both the site author and the UA add these transitions: the transitions may conflict and cause confusion for the user.

The goal of this API is to enable authors to disable default UA transitions for a navigation in favour of custom transitions on their site. This explainer goes through the detailed thinking behind it, here is the TLDR:

The proposal is a new at-rule named same-document-ua-transition which can have the following values:

The value can be specified as follows:

@same-document-ua-transition: enable;

@media (min-width: 360px) {
  @same-document-ua-transition: disable-atomic;
}

Other options for this API are outlined here.

Other considerations when evaluating this proposal:

nickcoury commented 1 year ago

This is extremely useful, and hope it is able to land in some form! Some feedback based on the explainer:

Swipe Types

Not all swipe navigations are edge swipes. Specifically, UC Browser uses whole-page swipes for navigation as long as the element being touched doesn't have room to scroll, and doesn't prevent default.

Opt-in vs out

Opt-out seems like the preferred behavior. Most users already expect UA transitions to be available, and disabling them by default will unnecessarily remove a useful gesture from much of the web.

CSS vs JavaScript API

I am curious if a JavaScript API has been considered yet. It might be a more natural fit for the swipe case, with simpler semantics. For example:

A few other thoughts:

hasUAVisualTransition

On the JavaScript proposal for detecting hasUAVisualTransition, the mechanic seems in line with what I would expect. One possible enhancement is some sort of paint holding. In the ideal case this wouldn't be necessary and developers would immediately update to the end state of the transition without a user-visible flash. But depending on the website architecture, it's still possible for the predictive snapshot behind the swipe to show, then the live state of the site to show, then the desired end state to render (possibly progressively over multiple frames). There might be a reasonable mechanic to expose in the View Transition API, but it would be nice to have a similar mechanism even if developers do not use it. There still would likely be a hard timeout after which painting resumes regardless, but providing a short time window for a site to update rendering will reduce disorienting flashes in line with the overall goal of this proposal. E.g.

navigation.addEventListener("navigate", (event) => {
  if (event.hasUAVisualTransition)
    event.holdPaintUntil(new Promise(resolve => {
      updateDOM(event).then(resolve);
    });
  else
    updateDOMWithVisualTransition(event);
});
khushalsagar commented 1 year ago

Thanks for the feedback Nick, those are very good insights. Responses below.

CSS vs JavaScript API

The benefit of a CSS API is reducing latency of the gesture since its declarative. We can start the visual animation as soon as the event is received from the OS. A script based API will require us to dispatch an event (cross-process) before the visual animation can start.

The handling of this gesture is also different from how touch events usually work. The common pattern would be to provide the touch event to script, and if it preventDefaults then the UA stops handling the gesture. In this case we don't want authors to be able to override what a swipe from the edge does:

The control authors have should be limited to whether there is a visual animation involving the web content as the user swipes. The reason for this is that the swipe gesture could be the only way for a user to go back on a browser. If the page is allowed to override it, they can effectively prevent the user from leaving the page.

This is why I don't think dispatching touch events to script for this gesture is a good idea. Authors might assume they can take over the gesture using preventDefault and start animating content on the page. We'll eventually need an API to dispatch the event stream for authors that want to customize the visual transition, but it seems better to have an explicit API indicating that the gesture will trigger a navigation (and the target entry).

That said, I can see a JS API which complements the CSS API for this. Do you have any use-cases in mind which won't work with just the CSS version?

Is there a greater story where the CSS API is superior to JS, e.g. as part of interop with View Transitions?

For same-document navigations (which this proposal is targeting), it's agnostic to VT. Authors can use any other framework to do transitions during navigations.

For cross-document navigations, an API like this shouldn't be necessary. Since what this API conveys is explicit in that case. The only way to do a visual transition is using a primitive like View Transitions. VT would have a declarative opt-in, so the UA already knows that the site has a custom transition for atomic navigations.

The UA can also suppress the site's custom transition (in favour of a UA transition) by not executing the ViewTransition for swipe navigations (until there is a way for authors to customize it).

This effectively means the UA can do the right thing without the need for any new APIs. There is an open question here, if there are cases where a ViewTransition when the navigation commits is strictly better than the UA default. Did I miss any case?

I believe it would still require JavaScript, so it could still be more natural to determine UA transition cancelling in the same place as startViewTransition().

That would actually be too late, authors would call startViewTransition() in the navigate event. This is dispatched when the user gesture has ended in a state that the navigation should be triggered. There won't be a navigate event if the user swipes back. The decision for cancelling the UA transition is needed when the user starts swiping.

I'm not aware of any atomic default browser transitions

Me neither. :) We (Chrome) have discussed this and made sense to make the API future proof for that case.

Not all swipe navigations are edge swipes. Specifically, UC Browser uses whole-page swipes for navigation as long as the element being touched doesn't have room to scroll, and doesn't prevent default.

That's interesting. In this particular case the browser is choosing between using the gesture for page content vs navigation. Even if the author doesn't preventDefault, I'd expect the same-document-ua-transition setting to suppress the visual animation same as an edge swipe would. Wdyt?

One possible enhancement is some sort of paint holding.

That's an interesting idea. FWIW, Chrome will keep showing the preview until DOM changes after the navigate event are painted. I expect every UA implementation will need to do that to avoid a flash of pre-navigation content if the preview is dismissed sooner.

The API you mentioned is useful if there are cases where the frame after the navigate event needs to block on an async event. That sounds like generalizing the rendering suppression concept in VT. If there are compelling use-cases, we can add an API like you proposed to allow authors to suppress rendering outside of VT.

nickcoury commented 1 year ago

All that makes sense. A few followups:

We'll eventually need an API to dispatch the event stream for authors that want to customize the visual transition, but it seems better to have an explicit API indicating that the gesture will trigger a navigation (and the target entry).

I think this answers my questions below as this describes my use case, but adding the questions for the sake of discussion.

In this case we don't want authors to be able to override what a swipe from the edge does: ...

I'm wondering if this is a hard requirement. What about using an edge swipe to dismiss a full-width overlay/modal element that may or may not be a browser navigation? For better or worse it's already possible to prevent navigation this way, though unreliable as has been pointed out. In many browser implementations, users are also unable to swipe when carousels are full width, though I suppose that's different since they generally aren't full-height and it only applies when they aren't scrolled to the end.

Do you know cases where edge swipes are the only way to navigate backward? To my knowledge, browsers always provide a back button affordance even if swipe is more common.

Do you have any use-cases in mind which won't work with just the CSS version?

Most use cases would be possible with the CSS version, though some cases get complicated. Web carousels can do this natively e.g. Amazon.com on an iPhone. Instagram on iOS has something similar, though thier edge swipe does override the behavior and it only works for middle of page swipes. So the dismissal is dynamic based on which part of the screen is swiped and which element is below the swipe, which would be difficult to do with just the CSS API before receiving touch data.

I'd expect the same-document-ua-transition setting to suppress the visual animation same as an edge swipe would. Wdyt?

I agree.

giveap commented 2 months ago

As someone who has developed web apps with same-page navigation, the ability to control the browser's swipe navigation animation in favor of our own is very welcome indeed. The problem is the swipe reveals a previously cached (static) page and getting the current page to match without a flash is impossible with dynamic apps. For instance, why can't an author request that a swipe back perform the same as the back button? The painting of a static page is at odds with same page navigation.

Similarly, an author should be able to disable default swipe navigation in instances (for example on carousels) where user touch can accidentally cause a navigation instead of horizontal scrolling or whatever the intended action might be.