8

I have a list like this:

var v = new Vue({
  'el' : '#app',
  'data' : {
    'list' : [1,2,3,4,5,6,7,8,9,10]
  },
  
  methods: {
    activateClass($event){
      $event.target.classList.remove('animate');
      void $event.target.offsetWidth;
      $event.target.classList.add('animate');
    },
    changeRandomValue(){
      var randomAmount = Math.round(Math.random() * 12);
      var randomIndex = Math.floor(Math.random() * this.list.length);
      Vue.set(this.list, randomIndex, randomAmount)
    }
  },
  
  mounted(){
    var vm = this;
    setInterval(function(){
      vm.changeRandomValue();
    }, 500);
  }
})
.animate{
  animation: fade 0.5s;
}

@keyframes fade{
  0% { background:blue; }
  100% { background:white; }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
<div id="app">
  <ul>
    <li v-for="item in list" v-html="item" @click="activateClass($event)"></li>
  </ul>
</div>

If you run the above snippet, you'll see that if you click an li it will use this code:

activateClass($event){
  $event.target.classList.remove('animate');
  void $event.target.offsetWidth;
  $event.target.classList.add('animate');
}

To add a class to it once and play an animation (https://css-tricks.com/restart-css-animation/). Awesome!

Now, I also have a changeRandomValue() function that selects one element from the list array and change its value.

How would I use the activateClass method from inside of the changeRandomValue method?

I've had a few thoughts about using events on the element, so it would be something like:

<li v-for="item in list" v-html="item"
    @click="activateClass($event)"
    @myValueChanged="activateClass($event)"
></li>

But I don't think anything like that exists. I have also been looking into watchers but I don't think this is really what they are for.

I have no problem taking the element that has been clicked and finding the data that it is referencing, but I can't work out how to take the data that has been changed and then find its dom reference.

The reason I'm not using class bindings is that I need to trigger a reflow. Maybe there is a really easy way to do that with vue, but I'm not aware of it.

Community
  • 1
  • 1
Djave
  • 8,595
  • 8
  • 70
  • 124
  • 1
    What you're doing is an anti-pattern, use [class bindings](https://vuejs.org/v2/guide/class-and-style.html) instead. – Etheryte Dec 04 '17 at 13:08
  • If you need that feature, I guess you have to use either watch or better yet, don't pass just integers to Vue.set() but pass an object. One for the random id, and next for classname to animated.. maybe pass active as style name.. but it smells like anti-pattern like Nit said – samayo Dec 04 '17 at 13:17
  • @Nit yep, that's what I would usually do. The problem in my case is, in order to have a repeatable animation class I need to trigger that `$event.target.offsetWidth` to trigger reflow. (more info in that CSS tricks article on triggering repeating animations https://css-tricks.com/restart-css-animation/) – Djave Dec 04 '17 at 13:18

2 Answers2

5

One easy solution would be to use class bindings and the animationend event.
You can also trigger the same solution programmatically, as shown in the mounted event.

new Vue({
  el: '#app',
  data: {
    items: [{
      id: 1,
      highlight: false
    }, {
      id: 2,
      highlight: false
    }]
  },
  mounted() {
    // Demonstrate programmatic highlighting
    setTimeout(() => {
      this.items[1].highlight = true
      setTimeout(() => {
        this.items[1].highlight = true
      }, 1000)
    }, 1000)
  },
  methods: {
    addClass(item) {
      item.highlight = true
    },
    removeClass(item) {
      item.highlight = false
    }
  }
})
p {
  cursor: pointer;
}

.animate {
  animation: fade 0.5s;
}

@keyframes fade {
  0% {
    background: blue;
  }
  100% {
    background: white;
  }
}
<script src="https://unpkg.com/vue@2"></script>

<div id="app">
  <p v-for="item in items" v-bind:class="{ animate: item.highlight }" v-on:click="addClass(item)" v-on:animationend="removeClass(item)">{{ item.id }}</p>
</div>
Etheryte
  • 24,589
  • 11
  • 71
  • 116
4

Similar to @Etheryte's solution, but shorter:

v-on:click="($event) => $event.target.classList.add('my-class')"
v-on:animationend="($event) => $event.target.classList.remove('my-class')"
Comfort Eagle
  • 2,112
  • 2
  • 22
  • 44