DEV Community

Thomas Sjögren
Thomas Sjögren

Posted on

Reusable transitions with Vue.js

This is a re-post of a story I previously posted and deleted on Medium

Adding transitions or animations to a web page or an application can be a good way to engage users and create a better user experience. But it can take a lot of work implementing and maintaining them. By taking advantage of some core features of the Vue.js framework, these parts can be made more reusable.

Secret code

Photo by Markus Spiske on Unsplash

Creating transitions in Vue.js can be as simple as using the built-in <transition> component. Provide a name and use that in a stylesheet to create the transition or animation. The official Vue.js documentation explains this very well.

To get more control over the transition component, Vue.js provide a set of JavaScript hooks. This makes it easy to use other libraries or custom code and an excellent case for extracting these methods to a mixin that can be reused in components.

// The transition mixin
export default {
  methods: {
    beforeEnter(el) {
      el.style.height = '0';
    },
    enter(el) {
      el.style.height = `${el.scrollHeight}px`;
    },
    leave(el) {
      el.style.height = '0';
    },
  },
};

Import and register the mixin in the component. Then set up the methods with the transition hooks and it is ready for use.

// SomeComponent.vue
<template>
  <div id="app">
    <button @click="toggle">Toggle 1</button>
    <transition
      name="slide-down"
      @before-enter="beforeEnter"
      @enter="enter"
      @leave="leave"
    >
      <div v-if="show" class="box">
        <p v-for="c in count" :key="c">{{ text }}</p>
      </div>
    </transition>
  </div>
</template>
<script>
import SlideDownMixin from './SlideDownMixin.js';
export default {
  el: '#app',
  mixins: [slideDownMixin],
  data() {
    return {
      text: 'Some text',
      count: 5,
      show: false,
    };
  },
  methods: {
    toggle() {
      this.show = !this.show;
      if (!this.show) {
        // Just to make it "dynamic"
        this.count = Math.floor(Math.random() * 5) + 1;
      }
    },
  },
});
</script>
<style>
.box {
  height: 0;
  background-color: #eee;
  overflow: hidden;
  position: relative;
  width: 200px;

  will-change: height;
  transform: translateZ(0);
  backface-visibility: hidden;
  perspective: 1000px;
}
.slide-down-enter-active,
.slide-down-leave-active {
  transition: height .5s;
}
</style>

It is a good start to avoid duplicate code in components, but there is still the issue with having to repeat the initial binding of the methods for the transition.

It is possible to improve on this and take it a step further.

Dedicated transition component

In order to avoid repeating the binding of methods to the transition hooks, it is possible to extract the whole <transition> into a dedicated component. Now the methods and bindings are defined in one place. Any required styles can also go here to keep everything tidy.

// SlideDownTransition.vue
<template>
  <transition
    name="slide-down"
    @before-enter="beforeEnter"
    @enter="enter"
    @leave="leave"
  >
    <slot/>
  </transition>
</template>

<script>
export default {
  methods: {
    beforeEnter(el) {
      el.style.height = '0';
    },
    enter(el) {
      el.style.height = `${el.scrollHeight}px`;
    },
    leave(el) {
      el.style.height = '0';
    },
  },
};
</script>

<style>
.slide-down-enter-active,
.slide-down-leave-active {
  transition: all .2s;
}
</style>

The component can now be used instead of the transition anywhere in the app without having to repeat any hook bindings or methods. Simply import, register and use it.

// MainComponent.vue
<template>
  <div>
    <button @click="toggle">Toggle</button>
    <SlideDownTransition>
      <div v-if="show" class="box">
        <p v-for="c in count :key="c">{{ text }}</p>
      </div>
    </SlideDownTransition>
  </div>
</template>

<script>
import SlideDownTransition from './SlideDownTransition';

export default {
  components: { SlideDownTransition },

  data() {
    return {
      show: false,
      count: 5,
      text: 'Some text',
    };
  },

  methods: {
    toggle() {
      this.show = !this.show;
      // Just to make content "dynamic"
      if (!this.show) {
        this.count = Math.floor(Math.random() * 5) + 1;      
      }
    },
  },
};
</script>

<style>
.box {
  background-color: #eee;
  overflow: hidden;
  position: relative;
  width: 200px;
  will-change: height;
  transform: translateZ(0);
  backface-visibility: hidden;
  perspective: 1000px;
}
</style>

The transition component is a powerful tool in it self and combined with some styling it can cover many use-cases. When it comes to more advanced animations, moving over to use its JavaScript hooks makes this easy. The present example uses this to calculate the height of some "dynamic" content so it can be animated correctly.

The example code is available on GitHub.

It has been quite some time (years) since I've written any kind of article, technical or academic, but I want to share some of my knowledge and experience in the field. I hope someone finds it useful anyway.

Latest comments (1)

Collapse
 
liyasthomas profile image
Liyas Thomas

Wow.. looks interesting. I never knew Vue could do all this!

If you're into open source contributions, we're developing postwoman.io, a web open source API request builder (free, open source alternative for postman), and would love to count you as a contributor to improve our components & applications 🙏

github.com/liyasthomas/postwoman