Closed GrandSchtroumpf closed 1 month ago
I'm going to point the other two issues here since they all want more or less the same thing.
The View Transitions API isn't supported by WebKit yet but according to the tracking issue they are ok with it.
Manu made https://pics-qwik.pages.dev/ last year using the ::view-transition
CSS API.
It uses this CSS which assigns the "picture" transition to the images.
So, do we just recommend to use this API or do we implement async callbacks in navigation to allow doing things manually?
I think the discussion should go beyond navigation.
The ViewTransition API can be used for any kind of state changes that involves a mutation of the DOM, not only navigation.
Since rendering is async this is very difficult to run the view transition callback at the correct time, especially when animating with WAPI.
An idea would be to have something like track
that would add run view transition when the state changes :
useViewTransition(async ({ track, transition }) => {
if (!transition) return track(state);
await transition.ready;
document.documentElement.animate(...);
})
@GrandSchtroumpf so for animations you need to know which old DOM elements will be transformed to which new elements right? And with the ViewTransition API that is done via CSS.
Suppose we decide to only support the ViewTransition API, what is still missing in Qwik?
One thing we could do is put QRL attribures on DOM elements that are awaited before/after performing some DOM manipulation. Using sync$
they would even be instantly available. However, I'm not sure what we would need.
Since this issue gathers all View Transition problems, let's split it into two part
The ViewTransition API expect a callback that would work like that :
startViewTransition
transition.ready
promise & start transition in the next frameThis is an imperative API, while Qwik is reactive, so we don't know when DOM is updated
Here is a an example that illustrate the problem
const hidden = useSignal(true);
useVisibleTask$(({ track }) => {
track(() => hidden.value);
// Case 1: flaky since the DOM might not have been updated at this point
document.startViewTransition(() => waitForNextFrame());
// Case 2: slow because it waits for 200ms to start the transition
document.startViewTransition(() => waitFor200ms());
})
In React they suggest to use flushSync
inside the callback to force rerendering. It's a ok-ish workaround since the main thread might be already busy, which might create junky transitions.
Solution 1
One solution Qwik could integrate is a onNextRender
function that would resolve when DOM has been rerendered.
const hidden = useSignal(true);
useVisibleTask$(({ track }) => {
track(() => hidden.value);
document.startViewTransition(() => onNextRender());
})
Note: Here we might not want to startViewTransition when state initializes.
Solution 2 Create a hook into qwik-city that would do that :
useViewTransition(({ track }) => track(() => hidden.value));
The hook would:
startViewTransition
callbackNote: This solution might be better designed to allow dev to select in which case run or not the transition.
The API has two faces: CSS & JS.
CSS :
JS:
Example in my initial comment
Solution 1: Provide a way to hook into a transition :
const location = useLocation();
useViewtransition(({ track, before, ready, finished }) => {
track(() => location.url);
before(async () => {
// Do something before `startViewTransition` is called
});
ready(async () => {
// Hooked into the `transition.ready` promise callback
// run WAPI animation
});
finished(async () => {
// Hooked into the `transition.finished` promise callback
// cleanup style or classes needed for the transition
})
});
useViewTransition(({ track }) => track(() => hidden.value));
The benefit of this API is that we can listen on page transition or local state transition with different behaviors. The issue is that it's not easy to have different behavior depending on the Link
Solution 2:
Maybe we can plug useViewTransition
with <Link/>
:
const transition = useViewtransition(({ before, ready, finished }) => {
before(async () => {
// Do something before `startViewTransition` is called
});
ready(async () => {
// Hooked into the `transition.ready` promise callback
// run WAPI animation
});
finished(async () => {
// Hooked into the `transition.finished` promise callback
// cleanup style or classes needed for the transition
})
});
return <Link transition={transition} />
This could be used in any UI library which wants to leverage the View Transition API power inside Qwik :
const TabGroup = component$(({ transition }) => {
const selected = useState(null);
const select = $((e, el) => {
console.log(transition.id);
transition.start(() => selected.value = el.ariaControls)
});
return (
<button role="tab" onClick$={select} aria-controls="tab-panel-1">...</button>
<div role="tabpanel" id="tab-panel-1">...</div
)
})
We can have something like that, and use it inside the <Link />
component
const useViewtransition = (params) => {
const initialized = useSignal(false);
const id = useId();
const start = $(async (cb = (() => {})) => {
await params.before();
const viewTransition = await new Promise((res, rej) => {
const transition = document.startViewTransition(async () => {
await cb();
await nextRendering();
res(transition);
})
});
viewTransition.read.then(params.ready);
viewTransition.finished.then(params.finished)
});
useTask$(({ track }) => {
if (!params.track) return;
track(params.track);
if (!initialized.value) return initialized.value = true;
if (!isServer) start();
})
return { id, start }
}
Note that we could have a QRL prop on DOM nodes that gets awaited before it gets changed by Qwik. Would that be enough?
I'm not sure, like a onViewTransitionstart$
, onViewTransitionReady$
, and onViewTransitionFinished$
?
The startViewTransition
is based on an action that will change the dom (page navigation, tab, popover, form,...). In a reactive framework like qwik the DOM usually changes with state update. So for me, it makes more sense to me to build around the state changes process and not a DOM element.
No I meant like onBeforeDomNodeWillChange
We moved this issue to qwik-evolution
repo to create a RFC discussion for this.
Here is our Qwik RFC process thanks.
Is your feature request related to a problem?
I'm trying a animate a list of item on page navigation. For that I'm animating with Javascript, but there is no way to hook into the transition from the link.
Describe the solution you'd like
Ideally the
Link
element &useNavigation
would expose a callback for that :Describe alternatives you've considered
We cannot set the animation inside
style
because this is a pseudo element ofhtml
. As we cannot update the pseudo element with Javascript and we cannot use css variable like that::view-transition-old(var(--name))
. We cannot set a<style>
on each element because it'll be removed before the view transition is ready.The only solution is to add
<style>
insidehead
with all the::view-transition-old()
pseudo class and remove them:Note: I'm not using
::view-transition-old(*)
because I want to target specific elements (not all) and I want to add delay.Additional context
No response