Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delay the rendering of a component in vue

Tags:

vuejs2

I've created a component in vue which wraps a vue-apexchart donut graph. As soon as the page loads and this component is loaded, the vue-apexchart animates and displays a small graph. Now I would like to instantiate multiple of these components from a dataset side by side. Instead of the components to all load an animate at the same time, I would like a small rendering delay to give it an overall nice effect. Something like this would be nice:

  <donut :items="series1"></donut>
  <donut :items="series2" delay=1500></donut>

The vue-apexchart doesent support initialization delays, and as far as I can see there isn't any vue-specific official solution to delay the rendering of components. I've tried to put a setTimeout in any of the component hooks to stall the initialization, I´ve also tried to inject the all the graph DOM in the template element on a v-html tag in a setTimeout, but apexchart doesent notice this new dom content, and vue doesent notice the html bindings either.

I´ve created this fiddle which loads two instances of a graph: https://jsfiddle.net/4f2zkq5c/7/

Any creative suggestions?

like image 752
Farsen Avatar asked Dec 14 '22 12:12

Farsen


2 Answers

There are several ways you can do this, and it depends on whether you can actually modify the <animated-component> logic yourself:

1. Use VueJS's built-in <transition-group> to handle list rendering

VueJS comes with a very handy support for transitions that you can use to sequentially show your <animated-component>. You will need to use a custom animation library (like VelocityJS) and simply store the delay in the element's dataset, e.g. v-bind:data-delay="500". VueJS docs has a very good example on how to introduce staggered transitions for <transition-group>, and the example below is largely adapted from it.

You then use the beforeAppear and appear hooks to set the opacity of the individual children of the <transition-group>.

Vue.component('animated-component', {
  template: '#animatedComponentTemplate',
  props: {
    data: {
      required: true
    }
  }
});

new Vue({
  el: '#app',
  data: {
    dataset: {
      first: 'Hello world',
      second: 'Foo bar',
      third: 'Lorem ipsum'
    }
  },
  methods: {
    beforeAppear: function(el) {
      el.style.opacity = 0;
    },
    appear: function(el, done) {
      var delay = +el.dataset.delay;
      setTimeout(function() {
        Velocity(
          el, {
            opacity: 1
          }, {
            complete: done
          }
        )
      }, delay)
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <transition-group name="fade" v-on:before-appear="beforeAppear" v-on:appear="appear">
    <animated-component v-bind:data="dataset.first" v-bind:key="0"> </animated-component>
    <animated-component v-bind:data="dataset.second" v-bind:key="1" v-bind:data-delay="500"> </animated-component>
    <animated-component v-bind:data="dataset.third" v-bind:key="2" v-bind:data-delay="1000"> </animated-component>
  </transition-group>
</div>

<script type="text/x-template" id="animatedComponentTemplate">
  <div>
    <h1>Animated Component</h1>
    {{ data }}
  </div>
</script>

2. Let <animated-component> handle its own rendering

In this example, you simply pass the a number to the delay property (remember to use v-bind:delay="<number>" so that you pass a number and not a string). Then, in the <animated-component>'s mounted lifecycle hook, you use a timer to toggle the visibility of the component itself.

The technique on how you want to show the initially hidden component is up to you, but here I simply apply an initial opacity of 0 and then transition it after a setTimeout.

Vue.component('animated-component', {
  template: '#animatedComponentTemplate',
  props: {
    data: {
      required: true
    },
    delay: {
      type: Number,
      default: 0
    }
  },
  data: function() {
    return {
      isVisible: false
    };
  },
  computed: {
    styleObject: function() {
      return {
        opacity: this.isVisible ? 1 : 0
      };
    }
  },
  mounted: function() {
    var that = this;
    window.setTimeout(function() {
      that.isVisible = true;
    }, that.delay);
  }
});

new Vue({
  el: '#app',
  data: {
    dataset: {
      first: 'Hello world',
      second: 'Foo bar',
      third: 'Lorem ipsum'
    }
  }
});
.animated-component {
  transition: opacity 0.25s ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <animated-component v-bind:data="dataset.first"> </animated-component>
  <animated-component v-bind:data="dataset.second" v-bind:delay="500"> </animated-component>
  <animated-component v-bind:data="dataset.third" v-bind:delay="1000"> </animated-component>
</div>

<script type="text/x-template" id="animatedComponentTemplate">
  <div class="animated-component" v-bind:style="styleObject">
    <h1>Animated Component, delay: {{ delay }}</h1>
    {{ data }}
  </div>
</script>
like image 105
Terry Avatar answered Feb 17 '23 22:02

Terry


If you have the possibility to reformat your data, you can build an array of series objects, add a show: true/false property and iterate it:

//template
<div v-for="serie in series">
  <donut :items="serie.data" v-if="serie.show"></donut>
</div>

//script
data: function() {
  return {
    series: [
      { data: [44, 55, 41, 17, 15], show: false },
      { data: [10, 20, 30], show: false },
    ]
  }
}

Now you can create a setTimeout function which will change the serie.show to true by incrementing the delay based on the serie index.
Then add the function on the mounted hook:

methods: {
  delayedShow (serie, idx) {
    let delay = 1500 * idx
    setTimeout(() => {
      serie.show = true
    }, delay)
  }
},
mounted () {
  this.series.forEach((serie, idx) => {
    this.delayedShow(serie, idx)
  })
}

Live example

like image 40
Sovalina Avatar answered Feb 17 '23 21:02

Sovalina