Aurelia.js: How do I animate an element when bound value changes?
Asked Answered
F

3

7

I am using Aurelia.js for my UI. Let's say I have the following view markup:

<tr repeat.for="item in items">
    <td>${item.name}</td>
    <td>${item.value}</td>
</tr>

Which is bound to a model "items". When one of the values in the model changes, I want to animate the cell where the changed value is displayed. How can I accomplish this?

Fortier answered 25/7, 2015 at 20:37 Comment(4)
How does the data changes? What changes is?Doloroso
@Doloroso View model gets it from websocket and updates an item in "items" field. Aurelia's binding then updates the DOM.Fortier
You need to use aurelia-animator-css module. I will create an example later if you don't figure it out yourself.Doloroso
@Doloroso Yeh, I installed it and made some examples from Rob's blog work, but I can't figure out how to solve my task yet. An example of yours would be helpful.Fortier
F
7

This can be done with Aurelia custom attributes feature.

Create a new javascript file to describe the attribute (I called the attribute "animateonchange"):

import {inject, customAttribute} from 'aurelia-framework';
import {CssAnimator} from 'aurelia-animator-css';

@customAttribute('animateonchange')
@inject(Element, CssAnimator)
export class AnimateOnChangeCustomAttribute {
    constructor(element, animator) {
        this.element = element;
        this.animator = animator;
        this.initialValueSet = false;
    }

    valueChanged(newValue){
        if (this.initialValueSet) {
            this.animator.addClass(this.element, 'background-animation').then(() => {
                this.animator.removeClass(this.element, 'background-animation');
            });
        }
        this.initialValueSet = true;
    }
}

It receives the element and CSS animator in constructor. When the value changes, it animates the element with a predefined CSS class name. The first change is ignored (no need to animate on initial load). Here is how to use this custom element:

<template>
    <require from="./animateonchange"></require>
    <div animateonchange.bind="someProperty">${someProperty}</div>
</template>

See the complete example in my blog or on plunkr

Fortier answered 28/7, 2015 at 18:56 Comment(1)
The problem with this is the display changes before the animation.The correct order would be : animateOut > changeValue > animateIn. So you would not see the value changing in screen. This solution does : changeValue > animateOut > animateInJaddan
C
5

The creator of the crazy Aurelia-CSS-Animator over here :)

In order to do what you want you simply need to get hold of the DOM-Element and then use Aurelia's animate method. Since I don't know how you're going to edit an item, I've just used a timeout inside the VM to simulate it.

attached() {
  // demo the item change
  setTimeout( () => {
    let editedItemIdx = 1;

    this.items[editedItemIdx].value = 'Value UPDATED';
    console.log(this.element);
    var elem = this.element.querySelectorAll('tbody tr')[editedItemIdx];

    this.animator.addClass(elem, 'background-animation').then(() => {
      this.animator.removeClass(elem, 'background-animation')
    });
  }, 3000);
}

I've created a small plunkr to demonstrate how that might work. Note this is an old version, not containing the latest animator instance, so instead of animate I'm using addClass/removeClass together.

http://plnkr.co/edit/7pI50hb3cegQJTXp2r4m

Also take a look at the official blog post, with more hints http://blog.durandal.io/2015/07/17/animating-apps-with-aurelia-part-1/

Hope this helps

Creolacreole answered 27/7, 2015 at 16:49 Comment(4)
Thanks you, the crazy creator. I got it working. Don't you find this way of doing animation too verbose and hacky? To me it doesn't work towards Aurelia's intention to produce clean code. I would expect some simple syntax with binding/event/whatever instead of direct DOM manipulations... And plunkr link seems to be just Hello World, I don't see any animation thereFortier
well there isn't actually any sort of DOM manipulation involved. All you need is the element you want to animate. There are alternative ways to this like using the ref-attribute, but the above example was just depicted since I didn't know your exact requirements. I'd recommend either creating a more specific SO question or joining the Aurelia Gitter to discuss it and later contribute the answer back to SO. The plunkr will animate the 2nd element in the list just after 3 seconds by changing its background color.Creolacreole
We do query selectors, so we use html in VM, and it will break if I change the view. In my case I need to select a cell, so even more rigid selector. setTimeout example maps almost 1-on-1 to my requirements, so no need in new question. I copied my question to gitter, no answer so far. And plunkr has no 2nd element, just Hello world, I guess I fail to click the right link or smth.Fortier
Sry I've messed up the link to the Plunkr.Now it's updated. Regarding DOM manipulation I got the impression you wanted to really change the DOM manually instead of only selecting. Yes you're right you should try to avoid direct selectors where possible, although sometimes its needed or simply said faster. In those cases a ref might help to get hold of the element. Your approach below is definitely ok and provides better abstraction. One thing you could additionally do is register the CustomAttribute globally to avoid the require statement, as well as use the animate method (see updated plunkr)Creolacreole
J
0

Unfortunately the accepted answer didnt work for me, the value in display changes before any animation is done, it looks bad.

I solved it by using a binding behavior, the binding update is intercepted and an animation is applied before, then the value is updated and finally another animation is done. Everything looks smooth now.

    import {inject} from 'aurelia-dependency-injection';  
import {CssAnimator} from 'aurelia-animator-css';



@inject(CssAnimator)
export class AnimateBindingBehavior {

  constructor(_animator){
    this.animator = _animator;
  }

  bind(binding, scope, interceptor) {

    let self = this;

    let originalUpdateTarget =  binding.updateTarget;

    binding.updateTarget = (val) => {
      self.animator.addClass(binding.target, 'binding-animation').then(() => {
        originalUpdateTarget.call(binding, val);
        self.animator.removeClass(binding.target, 'binding-animation')
      });
    }
  }

  unbind(binding, scope) {
    binding.updateTarget = binding.originalUpdateTarget;
    binding.originalUpdateTarget = null;
  }
}

Declare your animations in your stylesheet:

@keyframes fadeInRight {
  0% {
    opacity: 0;
    transform: translate3d(100%, 0, 0);
  }
  100% {
    opacity: 1;
    transform: none
  }
}


@keyframes fadeOutRight {
  0% {
    opacity: 1;
    transform: none;
  }
  100% {
    opacity: 0;
    transform: translate3d(-100%, 0, 0)
  }
}


    .binding-animation-add{
      animation: fadeOutRight 0.6s;
    }

    .binding-animation-remove{
      animation: fadeInRight 0.6s;
    }

You use it in your view like

<img src.bind="picture & animate">
Jaddan answered 16/5, 2018 at 10:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.