Tổng quan về Transition trong Vue.js

Trong bài hôm nay chúng ta sẽ tìm hiểu tổng quan về Transition trong Vue.js các bạn nhé. Để tiện đối chiếu giữa các class CSS mà Vue tạo ra trong một transition (chuyển tiếp) và trạng thái của các phần tử hoặc component, trong bài học này chúng mình giữ nguyên bản tiếng Anh các từ enter (đi vào, nhập vào) và leave (rời khỏi) khi cần.

Giới thiệu về transition trong Vuejs

Vue.js cung cấp nhiều cách khác nhau để áp dụng các hiệu ứng transition khi các phần tử được thêm vào, thay đổi, hoặc gỡ bỏ khỏi DOM. Điều này bao gồm các công cụ để:

  • tự động áp dụng các class CSS cho các transition và animation
  • tích hợp các thư viện chuyển động CSS bên thứ ba, ví dụ như Animate.css
  • sử dụng JavaScript để trực tiếp thay đổi DOM trong các hook transition
  • tích hợp các thư viện chuyển động JavaScript bên thứ ba, ví dụ như Velocity.js

Trong loạt bài này chúng ta sẽ chỉ bàn về transition cho enter/leave và list (danh sách), nhưng bạn có thể xem phần tiếp theo về quản lí transition cho trạng thái (state transition).

Transition cho phần tử hoặc component đơn lẻ

Vue cung cấp một component transition, cho phép chungs ta áp dụng các hiệu ứng transition enter/leave lên các phần tử hoặc component trong các ngữ cảnh sau:

  • Render theo điều kiện (sử dụng v-if)
  • Hiển thị theo điều kiện (sử dụng v-show)
  • Component động
  • Root node của component

Sau đây là một ví dụ:

<div id="demo">
  <button v-on:click="show = !show">
    Kích hoạt
  </button>
  <transition name="fade">
    <p v-if="show">Xin chào</p>
  </transition>
</div>
new Vue({
  el: '#demo',
  data: {
    show: true
  }
})
.fade-enter-active, .fade-leave-active {
  transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
  opacity: 0;
}


Kết quả hiển thị:

Khi một phần tử chứa trong component transition được thêm vào hoặc gỡ bỏ khỏi DOM, các bước sau đây sẽ diễn ra:

  • Vue sẽ tự phát hiện ra nếu phần tử đang nhắc đến có CSS transition hoặc animation và thêm/bớt các class CSS transition vào đúng thời điểm.
  • Nếu component cung cấp hook JavaScript, các hook này sẽ được gọi vào đúng thời điểm.
  • Trong trường hợp không tìm thấy transition hoặc animation nào trong CSS và cũng không có hook JavaScript nào, việc thêm vào hoặc gỡ bỏ khỏi DOM sẽ được thực thi ngay trong frame tiếp theo (Lưu ý: đây là animation frame của trình duyệt, khác với khái niệm nextTick của Vue).

Các class transition

Trong Vuejs có tổng cộng 6 class được áp dụng cho enter/leave transition.

  • v-enter: Trạng thái bắt đầu của enter. Được áp dụng trước khi phần tử được thêm vào DOM và gỡ bỏ đi một frame sau đó.
  • v-enter-active: Trạng thái active của enter. Được áp dụng trong suốt quá trình enter, từ ngay sau khi phần tử được thêm vào DOM cho đến khi transition/animation kết thúc. Class này có thể được dùng để định nghĩa duration, delay, và hàm easing cho transition enter.
  • v-enter-to: 2.1.8+. Trạng thái kết thúc của enter. Áp dụng một frame sau khi element được thêm vào DOM (cùng lúc với việc v-enter được gỡ bỏ), gỡ bỏ đi khi transition/animation kết thúc.
  • v-leave: Trạng thái bắt đầu của leave. Được áp dụng ngay khi một leave transition được kích hoạt và gỡ bỏ đi một frame sau đó.
  • v-leave-active: Trạng thái active của leave. Được áp dụng trong suốt quá trình leave, từ khi transition được kích hoạt cho đến khi transition/animation kết thúc. Class này có thể được dùng để định nghĩa duration, delay, và hàm easing cho leave transition.
  • v-leave-to: 2.1.8+. Trạng thái kết thúc của leave. Áp dụng một frame sau khi leave transition được kích hoạt (cùng lúc với việc v-leave được gỡ bỏ), gỡ bỏ đi khi transition/animation kết thúc.

Mỗi class trên đây sẽ có prefix (tiếp đầu ngữ) là tên của transition. Ở đây prefix v- là mặc định khi bạn dùng một thẻ <transition> không có tên. Nếu chẳng hạn bạn dùng <transition name="my-transition"> thì class v-enter sẽ trở thành my-transition-enter.

v-enter-activev-leave-active cho phép bạn dùng các hàm easing khác nhau cho các enter/leave transition, như bạn sẽ thấy trong phần tiếp theo đây.

CSS transition

CSS transition là một trong những transition thông dụng nhất. Ví dụ:

<div id="example-1">
  <button @click="show = !show">
    Kích hoạt
  </button>
  <transition name="slide-fade">
    <p v-if="show">Xin chào</p>
  </transition>
</div>
new Vue({
  el: '#example-1',
  data: {
    show: true
  }
})
/*
  Animation cho enter và leave có thể có giá trị
  duration và timing function khác nhau.
*/
.slide-fade-enter-active {
  transition: all .3s ease;
}
.slide-fade-leave-active {
  transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to
/* Trước 2.1.8 thì dùng .slide-fade-leave-active */ {
  transform: translateX(10px);
  opacity: 0;
}

Kết quả như sau:

CSS animation

CSS animation được áp dụng cùng một cách tương tự như CSS transition. Điểm khác nhau là v-enter không được gỡ bỏ ngay lập tức sau khi phần tử được thêm vào DOM mà là khi sự kiện animationend được phát ra.

Dưới đây là một ví dụ (chúng ta sẽ bỏ đi các prefix CSS cho gọn):

<div id="example-2">
  <button @click="show = !show">Kích hoạt</button>
  <transition name="bounce">
    <p v-if="show">Cân đẩu vân</p>
  </transition>
</div>
new Vue({
  el: '#example-2',
  data: {
    show: true
  }
})
.bounce-enter-active {
  animation: bounce-in .5s;
}
.bounce-leave-active {
  animation: bounce-in .5s reverse;
}
@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.5);
  }
  100% {
    transform: scale(1);
  }
}

Hiển thị kết quả:

Class tùy biến cho transition

Chúng ta cũng có thể chỉ định các class tùy biến cho transition bằng cách cung cấp các thuộc tính sau đây:

  • enter-class
  • enter-active-class
  • enter-to-class (2.1.8+)
  • leave-class
  • leave-active-class
  • leave-to-class (2.1.8+)

Các thuộc tính này sẽ override những tên class theo thông lệ của Vuejs. Điều này đặc biệt có ích khi bạn muốn kết hợp giữa hệ thống transition của Vue và một thư viện CSS animation có sẵn như Animate.css.

Đây là một ví dụ:

<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">


<div id="example-3">
  <button @click="show = !show">
    Kích hoạt
  </button>
  <transition
    name="custom-classes-transition"
    enter-active-class="animated tada"
    leave-active-class="animated bounceOutRight"
  >
    <p v-if="show">Thú nhún</p>
  </transition>
</div>
new Vue({
  el: '#example-3',
  data: {
    show: true
  }
})

Sử dụng animation và transition cùng nhau

Vue cần phải đính kèm một sự kiện để có thể biết khi nào thì một chuyển động kết thúc. Sự kiện này có thể là transitionend hoặc animationend, tùy thuộc vào các rule CSS được áp dụng. Nếu bạn chỉ sử dụng animation hoặc transition, Vue có thể tự phát hiện kiểu chuyển động (animation hay transition).

Tuy nhiên, trong một số trường hợp có thể bạn muốn dùng cả transition và animation trên cùng một phần tử thì sao, ví dụ sử dụng Vue để kích hoạt một CSS animation, đồng thời áp dụng một hiệu ứng CSS transition khi hover chuột. Trong những trường hợp này, bạn sẽ phải khai báo rõ kiểu chuyển động bạn muốn Vue xử lí bằng cách dùng một thuộc tính type với giá trị là animation hoặc transition.

Chỉ định rõ thời lượng cho transition

Trong đa số các trường hợp, Vue có thể tự biết được khi nào một transition kết thúc. Mặc định, Vue đợi cho sự kiện transitionend hoặc animationend của phần tử transition gốc được phát ra. Tuy nhiên, không phải lúc nào đây cũng là điều bạn muốn – ví dụ, bạn có thể có một chuỗi các transition nối tiếp nhau, trong đó một số phần tử bên trong có transition được trì hoãn (delay) hoặc kéo dài lâu hơn transition của phần tử transition gốc.

Trong những trường hợp như vậy, chúng ta có thể chỉ định một cách tường minh thời lượng (duration) của transition (với đơn vị là mili giây) dùng thuộc tính duration trên component <transition>:

<transition :duration="1000">...</transition>

Chúng ta cũng có thể chỉ định hai giá trị tách biệt nhau cho thời lượng enter leave:

<transition :duration="{ enter: 500, leave: 800 }">...</transition>

Hook JavaScript

Chúng ta cũng có thể định nghĩa các hook JavaScript trong các thuộc tính của component <transition>:

<transition
  v-on:before-enter="beforeEnter"
  v-on:enter="enter"
  v-on:after-enter="afterEnter"
  v-on:enter-cancelled="enterCancelled"


  v-on:before-leave="beforeLeave"
  v-on:leave="leave"
  v-on:after-leave="afterLeave"
  v-on:leave-cancelled="leaveCancelled"
>
  <!-- ... -->
</transition>
// ...
methods: {
  // -----
  // ENTER
  // -----


  beforeEnter: function (el) {
    // ...
  },
  // callback `done()` là không bắt buộc
  // khi sử dụng cùng với CSS
  enter: function (el, done) {
    // ...
    done()
  },
  afterEnter: function (el) {
    // ...
  },
  enterCancelled: function (el) {
    // ...
  },


  // -----
  // LEAVE
  // -----


  beforeLeave: function (el) {
    // ...
  },
  // callback `done()` là không bắt buộc
  // khi sử dụng cùng với CSS
  leave: function (el, done) {
    // ...
    done()
  },
  afterLeave: function (el) {
    // ...
  },
  // leaveCancelled chỉ hoạt động với v-show
  leaveCancelled: function (el) {
    // ...
  }
}

Những hook này có thể được sử dụng độc lập hoặc có thể dùng chung với CSS transition/animation.

  • Khi sử dụng các transition JavaScript, hàm callback done là bắt buộc đối với các hook enter và leave. Trong các trường hợp khác, các hook này sẽ được gọi một cách đồng bộ và transition sẽ kết thúc ngay lập tức.
  • Bạn cũng nên chỉ định rõ v-bind:css="false" cho các transition JavaScript để Vue có thể bỏ qua phần dò tìm CSS. Việc này cũng ngăn không cho các rule trong CSS can thiệp vào transition.

Ví dụ dưới đây là một transition JavaScript sử dụng Velocity.js:

<!--
Velocity hoạt động rất giống như jQuery.animate
và là một lựa chọn tuyệt vời cho animation bằng JavaScript
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>


<div id="example-4">
  <button @click="show = !show">
    Kích hoạt
  </button>
  <transition
    v-on:before-enter="beforeEnter"
    v-on:enter="enter"
    v-on:leave="leave"
    v-bind:css="false"
  >
    <p v-if="show">
      Demo
    </p>
  </transition>
</div>

VUEJS

new Vue({
  el: '#example-4',
  data: {
    show: false
  },
  methods: {
    beforeEnter: function (el) {
      el.style.opacity = 0
    },
    enter: function (el, done) {
      Velocity(el, { opacity: 1, fontSize: '1.4em' }, { duration: 300 })
      Velocity(el, { fontSize: '1em' }, { complete: done })
    },
    leave: function (el, done) {
      Velocity(el, { translateX: '15px', rotateZ: '50deg' }, { duration: 600 })
      Velocity(el, { rotateZ: '100deg' }, { loop: 2 })
      Velocity(el, {
        rotateZ: '45deg',
        translateY: '30px',
        translateX: '30px',
        opacity: 0
      }, { complete: done })
    }
  }
})

Kết quả như sau:

Lời kết:

Trên đây là toàn bộ lý thuyết cơ bản về transition trong Vuejs, có gì chưa hiểu rõ các bạn cứ mạnh dạn comment bên đưới nhé. Chúc các bạn học tốt

Bình luận