View Transition #315
Replies: 10 comments 6 replies
-
|
ok nice. I would only implement the class stuff once Safari & Firefox promise to have it. So the only thing required from the framework is that Paging @Varixo for that. |
Beta Was this translation helpful? Give feedback.
-
const add = $(() => {
startViewTransition({
update: () => percent.value++,
type: ['increment']
});
})I had a similar type of API experimenting this week. My thoughts at the time were that we could avoid a lot of the closure craziness with something like const add = transition$(() => {
percent.value++;
});
onClick$={add}I like the idea of allowing multiple jsx nodes to share view transitions with the And if it's a basic view transition like an enter and exit animation I don't think a name is needed, the framework or API could generate the name for you. That's another benefit of Transition types could be handled like this, then there is no need for a hook at all, and you can place the function anywhere export default component$(() => {
const count = useSignal(0);
const increment = transition$(() => {
count.value++;
}, { types: ['increment'] });
const decrement = transition$(() => {
count.value--;
}, { types: ['decrement'] });
return (
<div>
<div>{count.value}</div>
<button onClick$={increment}>+</button>
<button onClick$={decrement}>-</button>
</div>
);
});Then later in CSS: /* Slide up when incrementing */
html:has-view-transition-type(increment) ::view-transition-new(root) {
animation: slide-up 0.3s;
}
/* Slide down when decrementing */
html:has-view-transition-type(decrement) ::view-transition-new(root) {
animation: slide-down 0.3s;
} |
Beta Was this translation helpful? Give feedback.
-
|
I agree, |
Beta Was this translation helpful? Give feedback.
-
|
Here is a stackblitz to test
|
Beta Was this translation helpful? Give feedback.
-
|
Forwarding a discussion from discord: View Transition API DesignProposals Discussed**1.
2.
3.
Key ArgumentsJack's Position (transition$ alone):
Grand's Position (transition$ + Derived transition state):
Critical Technical Points
Approaches ConsideredApproach A: Start with // Imperative
const add = transition$(() => {
todos.value = [...todos.value, newTodo.value];
});
// Reactive via composition
useTask$(async ({ track }) => {
track(() => messages.value);
await handleNewMessage();
});
Approach B: Ship both // Imperative
const add = transition$(() => {...});
// Reactive computed
const filtered = useComputedView$(() => list.value.filter(...));
Approach C: Explore signal-level transitions
Open Questions
|
Beta Was this translation helpful? Give feedback.
-
|
I did some test with const logChanges = () => {
const before = document.querySelectorAll('li').length;
setTimeout(() => {
const after = document.querySelectorAll('li').length;
console.log(`${before}, ${after}`);
}, 10);
}
export default component$(() => {
const list = useStore<number[]>([]);
const add = $(() => {
list.push(Math.random())
});
useTask$(({ track }) => {
track(list);
if (isBrowser) {
document.startViewTransition(logChanges); // logs 1,1 2,2 3,3
logChanges(); // logs 0,1 1,2 2,3
}
return list;
})
return (
<div>
<ul>
{list.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
<button onClick$={add}>Add</button>
</div>
);
});Which also mean that updating state through I think interface ViewTransitionOptions {
update?: () => any;
types?: string[];
root?: string || Element;
}
export function startViewTransition(options: ViewTransitionOptions = {}) {
const {
update = () => null,
types = [],
root = document
} = options;
if ('startViewTransition' in document) {
let transition;
const element = typeof root === 'string' ? document.getElementById(root) : root;
try {
transition = element.startViewTransition({
update: async () => {
if (update) await update();
await nextDOMUpdate();
},
types: ['qwik', ...types],
});
} catch {
// For browsers that don't support types
transition = document.startViewTransition(async () => {
if (update) await update();
await nextDOMUpdate();
});
}
document.dispatchEvent(
new CustomEvent('qViewTransition', { detail: transition })
);
return transition;
} else {
// For browsers that don't support view transition api
if (update) return update();
}
}
function viewTransitionQrl<T extends any[]>(
qrl: QRL<(...args: T) => any>,
options?: Omit<ViewTransitionOptions, update>
) {
return $((...args: T) => {
return startViewTransition({ update: () => qrl(...args), ...options });
});
}
export const viewTransition$ = implicit$FirstArg(viewTransitionQrl);I still think that a reactive API would be a better experience, like the Unfortunately, it would mean having a task blocking rendering : useBlockingTask$(async ({ track }) => {
track(signal);
return new Promise(res => document.startViewTransition(res));
});This kind of API would open the door to a LOT of animation capabilities (not only ViewTransition). But I understand this might be a big update. Which leads to my conclusion : |
Beta Was this translation helpful? Give feedback.
-
|
useTask$ already blocks rendering the moment a promise is returned / awaited. It's parallel if you do not await the promise. This is by design from Misko. There is a in-depth video on it you could ask Shai for. That is why the pattern I was proposing: useTask$(async ({ track }) => {
track(() => count.value);
// if is adding
await add(); <--- add being transition$ / whatever we want to name it
// if is removing
// do remove
});Just works, no setTimeout needed. If that isn't clear then I think we need some sort of linter error on floating promises if we don't already have one. Also list is a store object, I don't think you can track store objects 🤔 |
Beta Was this translation helpful? Give feedback.
-
|
Ok, so it works on v1 but not on v2. useViewTransitionTask$(({ track, runTransition }) => {
track(list);
// Optional callback to run transition with JS
runTansition(async (transition) => {
await transition.ready;
document.documentElement.animate(...);
})
}, {
types: ['increment']
})And here is the implementation: interface ViewTransitionTask {
track: (fn: (() => Object) | Object) => any;
runTransition: (fn: (transition: any) => any) => any;
}
export function useViewTransitionTaskQrl(
qrl: QRL<(task: ViewTransitionTask) => void>,
options: Omit<ViewTransitionOptions, 'update'> = {}
) {
const runTransitionFn = useConstant<any>({ call: undefined });
useTask$(async (task) => {
await new Promise<void>((res) => {
const runTransition = (fn: (transition: any) => any) => {
runTransitionFn.call = fn;
};
const track = (tracker: (() => Object) | Object) => {
const next = task.track(tracker);
if (next === null) return res();
const transition = startViewTransition({
update: res,
types: options.types,
root: options.root,
});
if (runTransitionFn.call) runTransitionFn.call(transition);
};
qrl({ runTransition, track });
});
});
}
export const useViewTransitionTask$ = implicit$FirstArg(
useViewTransitionTaskQrl
);Let me know if you've got some feedback on the naming or API design. |
Beta Was this translation helpful? Give feedback.
-
|
@thejackshelton @wmertens the APIs are working on v1 (blocked by a bug in v2) with no update required on core.
|
Beta Was this translation helpful? Give feedback.
-
|
I'm personally more a fan of transition$ and useTransitionTask$ since you're already using javascript so it is implied that it is a view transition. Also the point of abstracting in the first place is making the view transitions api easier to deal with. I think the fact that it's view transitions are more of an implementation detail. Also how often is it that someone is going to use the WAAPI to animate something in a task? I can't imagine that would be a common use case. And also we have two different names for the same thing here Isn't the callback to useTransitionTask$ supposed to be calling runTransition under the hood? If not I don't see the point of useTransitionTask$ over doing it with a useTask$, it is syntactic sugar. useTransitionTask$(({ track }) => {
track(list);
// Transition happens automatically when list changes
}, { types: ['increment'] }); |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
What is it about?
Provide an ViewTransition API integrated into Qwik
What's the motivation for this proposal?
Problems you are trying to solve:
ViewTransition needs to run before DOM is updated & wait until DOM is updated, so it's difficult to achieve a good API outside of the framework itself
Goals you are trying to achieve:
Create an Qwik API for ViewTransition.
Any other context or information you want to share:
React just released an API for ViewTransition, let's do the same, but better :)
Proposed Solution / Feature
What do you propose?
3 things :
This function takes a callback that changes the state and wait for next DOM update
How to use it :
This hook will listen on signal, props or store changes and startViewTransition before the DOM is update.
How to use it :
This is a shortcut to simplify the use of view-transition-name & view-transition-class
How to use it:
Currently Safari & Firefox don't support the
attr(... type()), but it's still possible to usestyle.Animate with JS
Since
startViewTransition&useViewTransitionwould both emit theqViewTransitionevent, we can animate withuseOnDocument:Links / References
ViewTransition API doc:
https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API
React API :
https://react.dev/reference/react/ViewTransition
Qwik current integration:
https://qwik.dev/docs/cookbook/view-transition/
Beta Was this translation helpful? Give feedback.
All reactions