-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Transitions/animations brain dump #1431
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
Comments
I just finished watching Edward Faulkner's talk. TBH, I'm not sure what was innovative about it other than maybe he's just simplifying a problem? To that end, this is an area I do want to see reduced to utter simplicity. Being one of those "story engineers" who has thought a lot about platforms and animation, I always want animation to be simpler than it is. And it's almost never easy. Even with Svelte's current powerful transition tools. I'd love it we could find that sweet spot. I think you've got a good starting list. (I admit I was getting a little thrown when you said "lists" since I was thinking ul/li, but then realized you meant arrays.) Let me know if you want a sounding board on it. One more thought. Sapper has the potential to "pack a punch" out of the box on this stuff. I wonder if the default setup ought to include a sample page that employs the power of this animation? Just a thought. |
Cross-route transitions might be made more difficult by nested routes - e.g., on the HTTP Status Dogs page, when you click on a specific dog, everything in the header above the I've never been super-confident about how I wanted to pull those kinds of transitions off, so I've never tried too hard :-x |
@TehShrike yes, you'd need to define transitions on the other elements if you didn't want them popping in and out. Though you do raise an issue that hadn't occurred to me, and which is a bit of a challenge: currently, transitions only run if the block they're an immediate child of is removed: {#if foo}
<div>
{#if bar}
<p transition:fade>
this will fade in and out when `bar` is toggled, but will
abruptly disappear when `foo` is toggled
</p>
{/if}
</div>
{/if} So if our list of dogs were inside an element with a transition, the dog wouldn't get 'captured' when it was clicked on, because the That's a semi-deliberate design decision: it certainly made the implementation easier, but it was justified by the fact that there are lots of situations where you don't want nested transitions: <!-- it would be weird if each <li> slid out while the <ul> itself was fading -->
{#if showList}
<ul transition:fade>
{#each list as item (item.id)}
<li transition:slide>{item.name}</li>
{/each}
</ul>
{/each} But it's going to make more sophisticated stuff a lot harder. So I think we perhaps need to rethink it. Perhaps outros in nested blocks should play by default, unless they are explicitly blocked with another directive: <!-- it would be weird if each <li> slid out while the <ul> itself was fading -->
{#if showList}
<ul transition:fade childtransition:false>
{#each list as item (item.id)}
<li transition:slide>{item.name}</li>
{/each}
</ul>
{/each} Here, Along with this, we would presumably want to default to outroing components. That would mean adding an component.outro().then(() => {...}); This would, of course, have a negative (albeit small, I think) impact on generated code size. But it would enable some things that are currently much too difficult, and so I think we should probably do it. One problem I haven't yet got my head round: how to handle components with slotted content that contains outros. |
Would the web animations API make it any easier or provide better results to reverse an in-flight animation? Such as an element inside an {#if} that toggles in the middle of animating. If not, I see no benefit to using it under the hood. Only benefit is people use it directly. |
Svelte already does that 😀 |
Going to close this as the bulk of the work has been done. Couple of demos:
|
Update: moved to #1480 for further discussion. Note: The following opinions are coming from a view that animations and transitions are most often used in web apps to call attention to changes in a page that is already loaded, and that the area of the page handling the change should be the one defining the transition. For example, the part that deals with changing the entire page in a SPA as you navigate might fade the entire page in (or might choose not to employ any transition), but elements on the page wouldn't fade themselves in unless their data was not loaded at the initial transition, in which case they might fade in after their portion of the data was loaded. Coordinating transitions throughout a page to run together should still be possible for advanced effects but should not be the default. Allowing child transitions to run will give fine-grained control and allow more robust use-cases. However, the vast majority of the time this will not be used. I propose the default be I believe transition boundaries should be at the block level with The default rule of thumb should be, If you want transitions to run across these boundaries then you can use {#if foo}
<div childtransition:true>
{#if bar}
<p transition:fade>
this will fade in and out when `bar` is toggled, but will
abruptly disappear when `foo` is toggled
</p>
{/if}
</div>
{/if} This allows the most expected behavior to be default and prevents confusion that would otherwise prevail when changes in an app (route changes and other large and small changes) cause animations to run unexpectedly. I believe this is the correct behavior for the majority of web apps and that the directives for |
Well, here are 2 cents - both vue-router, and ember's liquid fire, recognized potential value of being able to look into transition source and destination paths to deliver animations dependent on navigation path, not on route hierarchy. I am thrilled to see things like sveltejs/sapper#778 possible. Just want to share 2 things:
|
Fair warning: this will be a long rambly issue. I just want to get this stuff out of my head and into a place where the missing pieces will hopefully be a bit clearer.
Svelte's transition system is powerful, but only applies to a fairly narrow range of problems. (There are also some outstanding issues that need to be resolved one way or another — #544, #545, #547 and #1211, which is related to #547, spring readily to mind.)
We don't have a good way to achieve certain effects that are rightly gaining in popularity among web developers. Most of what I'm about to describe is almost certainly possible already, but I don't think it would be at all easy or declarative.
I urge anyone who is interested in this stuff to watch Living Animation by Edward Faulkner, which was recorded at EmberConf recently. I'm going to describe three particular scenarios based on examples from Edward's talk, and suggest the primitives that we need to add to Svelte in order to make them easy to do.
Scenario 1 — FLIPping a list
We have a list, which can shuffle. Items may enter or leave the list. We want to move elements smoothly to their new position using a FLIP-like mechanism. (See react-flip-move and Vue for prior art.)
But we don't want a fixed duration; we might want duration to be dependent on distance to travel, or on bounding box size (to simulate 'mass'). Maybe we don't want to travel in a straight line; maybe we want to change scale along the way — in other words we need fine-grained parameterisation and programmatic control, not just CSS transitions. (But we want them to be powered by CSS animations under the hood for the perf benefits etc.)
Stretch goal: we want to preserve momentum, or simulate springiness. Stretch goal 2: z-index should preserve the apparent z relationships between elements if a reshuffle happens during an animation.
What's missing
We have a concept of transitions, but we don't have a concept of animating something from one place on the page to another. We do, however, have a primitive that lets us reorder things on the page — the keyed each block. Vue appears to have reached the same conclusion — FLIP animations can only happen inside
<transition-group>
, and each element must have a:key
.Proposal: we add an
animate
directive. Whenever a keyed each block updates, the following sequence of events happens:animate
directive is measuredposition: absolute
on those elements and usetransform
to make it look as though they haven't moved, and only then run the outroanimation
object for each element that moved, containing layout information (we can probably get away with just the change inx
andy
value, plus maybe some other stuff like actual distance moved). The animation function —flip
, in the example below — is responsible for using thatanimation
object to calculate CSS. It returns the same kind of object that transition functions do — i.e. if it returns acss
function, that will be used to create a CSS keyframe animation that smoothly animates the element to its home position.delay
,duration
andeasing
would also be respected, just like with transitions.As an aside,
css: t => {...}
is all well and good but you often find yourself doing1 - t
inside those functions. It would certainly make my life easier if that value was supplied, so I suggest we change the signature tocss: (t, u) => {...}
('time', 'until').A challenge: How do we ensure that content below the animated list doesn't jump around?
Scenario 2 — transferring items between lists
Now suppose we have two lists. Clicking on an item in either sends it to the opposite list. As before, we need to consider how items move within the list, but we now also need to handle 'sent' and 'received' items.
What's missing
I think we're actually almost there. We could create
send
andreceive
transitions using the existing API, and those could talk to each other — the sent nodes could be used as the basis for the received node's transition functions (using something like the technique from this sadly abandoned library: https://github.com/Rich-Harris/ramjet).But there's no guarantee that we'd be able to register nodes as having been sent before they were 'claimed' by the receiver. I think the way around this is to allow transitions to return a function:
The transition manager would run all the transition functions, and if any of them returned new functions, it would resolve a promise (easy way to wait for work to complete without waiting for a new turn of the event loop, which would result in visual glitchiness) then call those functions.
An example of how that might work is shown below. I wouldn't expect people to actually write that code; we would have helper functions that did the heavy lifting, which I'll show in scenario 3.
Scenario 3 — cross-route transitions
This is actually pretty similar to the previous example, except that we don't have any animations in this one — just transitions. This time, we'll use an imaginary helper function to handle all the book-keeping and matching.
We have a list of items on one page (could be keyed, could be unkeyed) — let's say it's the HTTP status dogs. Clicking on one takes you to a dedicated page. The selected image moves smoothly (and enlarges) to its new home; the others fly off to the left or something. Navigating back reverses the transition (even if it happens in-flight).
If anyone made it this far, kudos. I would love to know if there are parts to this that I've overlooked, or any trickier use cases to test the ideas against, or if you have improvements to the above proposals.
To recap, the bits I think we should add:
animate
directive that works inside keyed each blockscss: (t, u) => {...}
One thing I haven't considered here is the Web Animations API; I've been assuming that we'd use CSS animations which I think are probably more suitable. But I'm open to persuasion if you think I've got that wrong.
The text was updated successfully, but these errors were encountered: