vuejs / vue

This is the repo for Vue 2. For Vue 3, go to https://github.com/vuejs/core
http://v2.vuejs.org
MIT License
207.8k stars 33.67k forks source link

Feature Request: Improve State Transition API #4853

Closed sdras closed 7 years ago

sdras commented 7 years ago

Vue.js version

2.1.1

Reproduction Link

http://codepen.io/sdras/pen/OWZRZL

Suggestions

First of all, thank you for Vue! I'm so grateful for this framework, it's so elegant.

In the CodePen above, I'm using a watcher to transition state with GSAP. I've tried to comment the code well so it's clear what is going on. I think that though transitioning state is doable, it's a little bit clunky. For instance, you can see that if you change the transition from 1 second to 0.5 seconds, it gets a little jumpy. (Totally open to suggestions if you have ideas on how to make this better that I haven't explored.)

Even the examples in the guide, though interesting, aren't well broken-down and I think it's a barrier to entry for people. Improving the documentation there might make things more clear.

This might be a broad request, but it seems like the common pattern is the two parameters such as startingPoint and endingPoint or some variation, and then a little bit of heavy-lifting to actually tie that back to state in a way where it's not overwriting itself. Is there a way to expose something in the API that makes that binding a little more intuitive? Any feedback appreciated, even if you think it's out of scope for now and want to close this out. Thank you for your hard work.

posva commented 7 years ago

For instance, you can see that if you change the transition from 1 second to 0.5 seconds, it gets a little jumpy

I must be something missing. It's animating fine when changing it 0.5s

About the docs: @chrisvfritz wrote some of those examples, so he may have some insights to share Maybe the introduction of scoped slots can make up for better reusable transition state components I find the examples very nice, going from an easy version to an app-ready-reusable version of it. But It'd be nice to know what makes them feel not well broken-down in your opinion. There's always room for improvement 🙂

chrisvfritz commented 7 years ago

@sdras Thanks for the feedback! I'm certainly the one to blame for any lack of clarity in those examples. 😅 As a sidenote, you're one of my animation heroes and I really enjoy how you write about these topics, so I take your concerns to heart. If even you are encountering issues, I think there's definitely room for improvement - whether it's in the documentation, the API, or both.

I'll have some time tomorrow to take a look at your CodePen and find what might be going wrong, offer any suggestions, and probably ask some more questions. 🙂

sdras commented 7 years ago

@chrisvfritz your examples are awesome! I'm a huge fan of your work in the guides. I'd love to hear your thoughts on this- any help is appreciated and if you want to throw ideas around for docs improvement, I'm more than happy to offer some of my time if will help others. Thank you!

chrisvfritz commented 7 years ago

Just an update - I did confirm the strange behavior you were seeing. 🤔 I'm investigating now.

chrisvfritz commented 7 years ago

I'm not extremely familiar with how GSAP works under the hood, but it looks like it's out of sync with Vue's render cycle in this case. To avoid unnecessary re-renders, Vue waits for all synchronous state updates to finish. In this case, it seems to often be interpreting GSAP's use of requestAnimationFrame with a series of synchronous updates, so it's waiting for them to finish before rendering again.

This seemed to work as a temporary fix:

TweenMax.ticker.useRAF(false)

I think that forces GSAP to fall back to setTimeout. Before thinking through next steps though, can you confirm whether that does indeed solve the issues you noticed?

sdras commented 7 years ago

Apologies on the radio silence on my end- that does indeed fix it, nice work! That's interesting about the way that it handles the renderings, really smart. Makes sense. What do you think about offering an API hook for startingPoint and endingPoint? If that's not the direction you're interested in taking, totally understandable and I'll just close out the issue. Thanks for your time!

chrisvfritz commented 7 years ago

@sdras For an improved state transitions API, I'm not even sure we'd need to specify startingPoint and endingPoint, since we already track old/new values in the reactivity system. I'm imagining something like a transition option, which would be a sort of hybrid between watch and computed.

For example, to create a transitioning version of a total data property, we could include options like this:

data: function () {
  return {
    total: 0
  }
},
transition: {
  total: {
    as: 'transitioningTotal',
    duration: 300
  }
}

This would create a new transitioningTotal property, which would always contain the value of total, though would transition from the old value to the new value over 300 milliseconds.

To avoid bloat in Vue core however, this could probably only ever work with:

We could also use a default timing function - probably the default ease used by CSS. Then people could provide their own timing functions if they wanted, with a timing or easing option:

transition: {
  total: {
    as: 'transitioningTotal',
    duration: 300,
    // ease-in-out-quad
    timing: function (t) {
      return t < 0.5
        ? 2 * t * t
        : -1 + (4 - 2 * t) * t
    }
  }
}

I believe a delay option would also be fairly simple to implement. For more complex values, like animating SVG paths, these transitioning properties containing raw numbers could then be used in other computed properties. For example, in the example of an array of objects of x and y coordinates, you could pull parts of D3 into a component:

import { line as d3Line, curveBundle } from 'd3-shape'

const drawCurvedPath = d3Line()
  .x(d => d.x)
  .y(d => d.y)
  .curve(curveBundle.beta(0.8))

And then use the transitioning coordinates to also animate changes in the path:

computed: {
  animatedPath () {
    return drawCurvedPath(this.transitioningCoordinates)
  }
}

How would that kind of thing sound to you? Would it sufficiently improve the DX in the situations you're thinking of?

sdras commented 7 years ago

Wow, that would be amazing!

I agree that keeping an eye on bloat is really essential, and that interpolating numbers would really serve most use-cases. I also love that you're already thinking about pairing with d3- that also would be a pretty common use-case for this kind of animation.

This is really exciting, thank you so much. You all rock. 🤘🏼

chrisvfritz commented 7 years ago

Cool! Thank you for starting the discussion. 🙂 I think as a next step then, I'll try to create a proof-of-concept plugin that adds this API. I'm not sure when exactly I'll be able to carve out the time, but I'll keep this thread updated with my progress.

sdras commented 7 years ago

Sounds great, it's really appreciated, and take your time.

posva commented 7 years ago

@chrisvfritz I forgot to tell you, I started a poc similar to https://github.com/chenglou/react-motion that allows to transition between values by using springs. The api looks something like this:

<Motion :to="someValue" :spring="springConf">
  <template scope="val">
    <p>{{vall}} is changing smoothly</p>
  </template>
</Motion>

to could be an object of values too

You can change someValue as usual but val will adapt smoothly. It won't allow you to control easing, though.

This doesn't change anything about your plugin idea, but I wanted to share in case it may inspire you 🙂

chrisvfritz commented 7 years ago

@posva That's great! I'm a big fan of react-motion, so I'm really glad to see Vue getting a similar project. 😄

yyx990803 commented 7 years ago

Not entirely sure what's actionable here - imo pure state-based transitions is really about manipulating state variables in JavaScript and not a responsibility of Vue core. Easing curves, spring animations etc. should all be doable in userland and thus should be done in userland.

posva commented 7 years ago

I completely forgot about posting the project back here: https://github.com/posva/vue-motion