tmg0 / hero-motion

🌊 A shared layout animation tool for vue like framer motion.
MIT License
41 stars 3 forks source link

Scale correction and nested children #207

Closed NoelDeMartin closed 3 weeks ago

NoelDeMartin commented 4 weeks ago

I mentioned in https://github.com/vueuse/motion/issues/226 that I've been working on a library to implement layout animations, and @tmg0 asked for some reproduction of what is missing in Hero Motion.

I think there are 2 things missing from Hero Motion at the moment, but they are related so I think we can use this issue to track both. Basically, it has to do with doing scale correction in children elements, and orchestrating nested animations. I have some examples of how this works in my library in this playground: https://noeldemartin.github.io/vivant/ (in particular, the examples talking about "scale correction" and "nested animations").

I could reproduce the scale correction problem in the Hero Motion example replacing the box in the "Toggle Size Animation" example with this:

<div
    v-if="isLarge"
    v-hero
    layout-id="box"
    class="bg-red-600 flex items-center justify-center rounded font-semibold tracking-wider text-white"
    style="width:200px;height:100px"
>
    <span>Some text</span>
</div>

<div
    v-else
    v-hero
    layout-id="box"
    class="bg-red-600 flex items-center justify-center rounded font-semibold tracking-wider text-white"
    style="width:100px;height:100px"
>
    <span>Some text</span>
</div>

In particular, this is equivalent to this example in Vivant: https://noeldemartin.github.io/vivant/morphing-with-scale-correction

I also used tailwind for my UI, so it should be easy to implement the other examples in Hero Motion if you want to reproduce more examples (the most notable being https://noeldemartin.github.io/vivant/morphing-with-nested-animations).

I don't think this is easy to implement, and this is why I say that my library is just a "proof of concept" at the moment, because I'm sure there are many edge cases I haven't taken care of. But in case you're interested, the core of how I implement layout animations can be found in these files:

tmg0 commented 4 weeks ago

Thank you very much for this! I have reproduced this issue in the example.

hero-motion implements transitions for size changes using scaling caused this error, and I have looked at the code in MorphAnimation, and If my understanding is incorrect, please let me know.

Vivant seems using querySelector to and [layout-id] attribute to filter the child doms under target element, and do the reverse animation for every children.

I'm not sure if I fully understand the implementation of the code, but it still very helpful for me, and I will try to use this approach in hero-motion

NoelDeMartin commented 4 weeks ago

Yes, Vivant uses [layout-id] to look for children that are in both layouts when "morphing". But it looks at the children automatically when the layout changes. I make a distinction between "morphing" (one element turning into another) and "layout animations" (one element changing some properties) because the implementations are different. I think in Framer Motion they call them "Shared Layout Animations" and "Layout Animations".

In any case, these are implementation details, you could choose to do it differently in your library :). The important part is that during the animation, children distortion is corrected. I learned a lot about this in this article, so check it out to understand how this works: Inside Framer's Magic Motion

tmg0 commented 4 weeks ago

Wow! This article looks great.

I am facing the issues related to Scale Timing, and it addresses those problems very well.

Thank you so much for your help!

NoelDeMartin commented 4 weeks ago

Yes, one of the reasons why I'm implementing this "manually" (updating the styles myself, rather than using vueuse/motion to transition the transforms) is that the inverse doesn't use the same animation curve. It would be nice to use the library for this, but I'm not sure it's possible without a lot of headaches :(. That's why I ended up doing some workaround to animate custom properties here: https://github.com/NoelDeMartin/vivant/blob/main/packages/vivant/src/lib/animate-styles.ts

tmg0 commented 4 weeks ago

This seems to be because vueuse/motion does not expose methods like onUpdate or a reactive transform property, so we have to manually bind it through watch and define a custom motion instance.

Thank you very much for the implementation you provided; it helped me a lot.

And I need to think about how to achieve a similar effect in hero-motion

tmg0 commented 3 weeks ago

I tried using MutationObserver to watch for style changes on the target element and manually bind 1/scale to the children. This seems to work and avoids using CustomMotionInstance (but I'm not sure if this is a good way and if there are any performance issues).

If you're interested, my implementation can be found here:

https://github.com/tmg0/hero-motion/blob/main/packages/core/src/composables/use-style.ts

Thank you very much for the help and suggestions.

tmg0 commented 3 weeks ago

Scale error in children elements has been corrected in the latest version of hero-motion (>= v0.5.0)

Many thanks to @NoelDeMartin for the code, suggestions, and examples provided, and vivant also helps a lot.