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

How would one do animations using virtual-dom? #112

Open
paldepind opened this Issue Nov 5, 2014 · 9 comments

Comments

Projects
None yet
5 participants
@paldepind

paldepind commented Nov 5, 2014

I've seen talk about hooks and discussions about the possibility for a tombstone feature. But what is the current suggested practice with regards to animations? I don't expect virtual-dom to do the heavy lifting. But is there a clean way to control enter animations, delayed removal, reorder animations, etc. by hand?

@TimBeyer

This comment has been minimized.

TimBeyer commented Nov 5, 2014

IMO the best way to do animations is to use CSS animations triggered by class changes on re-render. The DOM node will be diffed, updated, the class attached and the animation starts.

@paldepind

This comment has been minimized.

paldepind commented Nov 5, 2014

Sure! CSS animations triggered by class adding and removing classes – that's how I'd do it by hand.

So one should simply to do something like this if wanting to remove an element from a list:

  1. Add a, lets say ,fade-out-animation class to the element to be removed.
  2. Setup a callback listening for the transitionend event.
  3. In the callback actually remove the element from the virtual-dom, diff and patch.

But there is nothing in virtual-dom to assist with animations (not that having such a thing is a necessity)?

@Matt-Esch

This comment has been minimized.

Owner

Matt-Esch commented Nov 6, 2014

I think the concerns of animation sit beyond virtual-dom, simply because there is often state and timing associated with it. This state would be modeled independently of the virtual-dom rendering artifact. It's probably worth building a library to make animations easier.

@Raynos and I tried to get animations working in the 2048 example in mercury. I don't think it's quite there yet, and I concluded that we needed animation hooks to get it working properly. But I think it's also important for us to optimize hooks so that they aren't always executed globally (see the discussions around performance).

@paldepind

This comment has been minimized.

paldepind commented Nov 6, 2014

My main concern is that one of the huge advantages to using virtual-dom is how it enables me to simply update my data/models, forget about it, throw the data into a function building a new virtual-dom, do a diff and a patch. Then I know that the DOM is kept in sync and I wont have to worry exactly about which changes I made to the data. It's wildly convenient!

But if I manually have to trigger all animations and keep track of which data changes I need to apply delayed then I once again have to keep track of all my data changes and act on them in a view layer. So suddenly the advantage virtual-dom provided is gone and might as well just do the actual creation and deletion of elements myself as well since I'm handling all the animations anyway.

There might be something that I'm overlooking or an advantage with virtual-dom that I'm not getting. But I know how other libraries that handles the DOM for you, like React and Angular, provides tools for doing animations. I've used the animation system in Angular and while I'm no thrilled by it it does make certain types of animations trivial and having something is essential when you've given up direct control of updating the DOM.

@Raynos

This comment has been minimized.

Collaborator

Raynos commented Nov 6, 2014

function tween(observ, opts) {
    var beginValue = observ();
    var endValue = opts.endValue;
    var duration = opts.duration;
    var onFinish = opts.onFinish;

    var delta = (beginValue - endValue)  / (1000 / 16);
    var currValue = beginValue;

    var timer = setInterval(function () {
        duration = duration - 16;
        currValue = currValue - delta;

        observ.set(currValue);

        if (duration <= 0) {
            clearInterval(timer);
            onFinish();
        }
    }, 16);
}

function Modal() {
    var state = hg.struct({
        visible: hg.value(false),
        opacity: hg.value(1),
        handles: hg.value(null)
    });

    state.handles.set(hg.handles({
        close: function (state) {
            tween(state.opacity, {
                endValue: 0,
                duration: 1000,
                onFinish: function () {
                    state.visible.set(false);
                }
            });
        }
    }, state));
}

Modal.show = function (state) {
    state.visible.set(true);
};

Modal.render = function (state, opts) {
    var handles = state.handles;

    h('div.modal', {
        style: {
            display: state.visible ? 'block' : 'none',
            opacity: String(state.opacity)
        },
        'ev-click': hg.event(handles.close)
    }, [
        opts.content
    ]);
};

If you want an animation library to handle things for you. I recommend you write and maintain a tweening function. This makes animation some properties a lot easier.

Especially if you use the onFinish trick to do cleanup once you are done animating..

@paldepind

This comment has been minimized.

paldepind commented Nov 6, 2014

Doing animations with setInterval is not efficient – that doesn't achieve smooth animations on mobile. But obviously the tween function could be modified to use CSS transitions instead.

Your example above is how you'd do it in mercury as far I can see? But you're mixing view updating with state updating. Are you fine with that? From the mercury readme: "mercury encourages zero dom manipulation in your application code". But including an opacity value – that is strictly view related – along with the state seems awfully close to modifying the DOM just with a bit of indirection.

Obviously I'm not very familiar with mercury.

@Raynos

This comment has been minimized.

Collaborator

Raynos commented Nov 6, 2014

@paldepind the state is not "model state". Its application state. its all the state needed to render the visual scene.

This includes visibility booleans, this includes color strings, this includes opacity strings. You cannot escape the need to express the visual scene.

It should also be noted that the state I show here is the view state or the view model. in a large application your expected to have a separate set of state that is your model or your domain. This would have nothing to do with the UI.

I have toyed around with a convention that all temporary UI state should be nested under a viewState key or something similar to make things more obvouis.

@paldepind

This comment has been minimized.

paldepind commented Nov 6, 2014

Ok. Thanks for clearing that up. Yes, you'll indeed have to save the visual state somewhere. In my opinion keeping it separated under a viewState key sounds nice :)

I'll take a look at the source for 2024 in the 2024 branch!

@tcoats

This comment has been minimized.

tcoats commented Mar 26, 2015

Creating a Widget class seems to have enough extension points to manage your own transitions. If you want to do it independently of state changes and don't want to use css animations.

That's what I'm doing in odojs's hook component that can be used for animation as shown in the hook documentation. It's odojs specific but is built completely off virtual-dom so the concept could be extracted.

Unfortunately this puts a 'kink' in a nice vdom tree that stringify and other tree walks can't see through.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment