Chào mừng đến với Cafedev! Trong bài viết này, chúng ta sẽ khám phá về Vuejs và các kỹ thuật chuyển động (Transition) đi kèm. Vuejs không chỉ cung cấp các thành phần tích hợp sẵn để xử lý chuyển động mà còn cho phép chúng ta tạo ra các hiệu ứng đẹp mắt dễ dàng. Hãy cùng khám phá cách sử dụng “ và các kỹ thuật chuyển động khác trong Vuejs để tạo ra trải nghiệm người dùng tuyệt vời hơn!

Vue cung cấp hai thành phần tích hợp sẵn có thể giúp làm việc với các hiệu ứng chuyển động và hoạt hình phản ứng với trạng thái thay đổi:
– <Transition> để áp dụng hiệu ứng khi một phần tử hoặc thành phần được thêm vào và rời khỏi DOM. Điều này được bao gồm trong trang này.
– <Transition> để áp dụng hiệu ứng khi một phần tử hoặc thành phần được chèn vào, loại bỏ, hoặc di chuyển trong một danh sách v-for. Điều này được bao gồm trong chương tiếp theo.

Ngoài hai thành phần này, chúng ta cũng có thể áp dụng các hiệu ứng chuyển động trong Vue bằng các kỹ thuật khác như chuyển đổi các lớp CSS hoặc hiệu ứng dựa trên trạng thái thông qua ràng buộc kiểu. Các kỹ thuật bổ sung này được bao gồm trong chương Kỹ Thuật Hoạt Hình.

1. Thành Phần <Transition>

<Transition> là một thành phần tích hợp sẵn: điều này có nghĩa là nó có sẵn trong bất kỳ mẫu của thành phần nào mà không cần phải đăng ký. Nó có thể được sử dụng để áp dụng hiệu ứng khi phần tử hoặc thành phần được thêm vào và rời khỏi thông qua khe mặc định của nó. Việc thêm hoặc rời khỏi có thể được kích hoạt bằng một trong các cách sau:
– Hiển thị điều kiện thông qua v-if
– Hiển thị điều kiện thông qua v-show
– Chuyển đổi thành phần động qua phần tử <component>  đặc biệt
– Thay đổi thuộc tính đặc biệt key
Đây là một ví dụ về cách sử dụng cơ bản nhất:

<button @click="show = !show">Toggle</button>
<Transition>
  <p v-if="show">hello</p>
</Transition>
/* we will explain what these classes do next! */
.v-enter-active,
.v-leave-active {
  transition: opacity 0.5s ease;
}

.v-enter-from,
.v-leave-to {
  opacity: 0;
}

Try it in the Playground

  1. Vue sẽ tự động kiểm tra xem phần tử mục tiêu có áp dụng các chuyển động hoặc hoạt ảnh CSS không. Nếu có, một số các lớp chuyển động CSS sẽ được thêm / loại bỏ vào thời điểm thích hợp.
  2. Nếu có trình nghe cho các hooks JavaScript, những hooks này sẽ được gọi vào thời điểm thích hợp.
  3. Nếu không có chuyển động / hoạt ảnh CSS được phát hiện và không có hooks JavaScript được cung cấp, các thao tác DOM cho việc chèn và / hoặc loại bỏ sẽ được thực hiện trên frame hoạt ảnh tiếp theo của trình duyệt.

2. Chuyển Động Dựa Trên CSS

2.1 Các Lớp Chuyển Động

Có sáu lớp được áp dụng cho các chuyển động vào/ra.

  1. v-enter-from: Trạng thái bắt đầu cho chuyển vào. Được thêm vào trước khi phần tử được chèn, loại bỏ một frame sau khi phần tử được chèn.
  2. v-enter-active: Trạng thái hoạt động cho chuyển vào. Áp dụng trong suốt giai đoạn nhập. Được thêm vào trước khi phần tử được chèn, loại bỏ khi chuyển động/hoạt ảnh kết thúc. Lớp này có thể được sử dụng để định nghĩa thời gian, độ trễ và đường cong làm mịn cho chuyển động vào.
  3. v-enter-to: Trạng thái kết thúc cho chuyển vào. Được thêm vào một frame sau khi phần tử được chèn (cùng lúc với việc loại bỏ v-enter-from), loại bỏ khi chuyển động/hoạt ảnh kết thúc.
  4. v-leave-from: Trạng thái bắt đầu cho chuyển ra. Được thêm ngay lập tức khi một chuyển động ra được kích hoạt, loại bỏ sau một frame.
  5. v-leave-active: Trạng thái hoạt động cho chuyển ra. Áp dụng trong suốt giai đoạn rời đi. Được thêm ngay lập tức khi một chuyển động ra được kích hoạt, loại bỏ khi chuyển động/hoạt ảnh kết thúc. Lớp này có thể được sử dụng để định nghĩa thời gian, độ trễ và đường cong làm mịn cho chuyển động ra.
  6. v-leave-to: Trạng thái kết thúc cho chuyển ra. Được thêm vào một frame sau khi một chuyển động ra được kích hoạt (cùng lúc với việc loại bỏ v-leave-from), loại bỏ khi chuyển động/hoạt ảnh kết thúc.

v-enter-activev-leave-active cung cấp cho chúng ta khả năng chỉ định các đường cong làm mịn khác nhau cho các chuyển động vào/ra, mà chúng ta sẽ thấy một ví dụ trong các phần tiếp theo.

2.2 Chuyển Động Đặt Tên

Một chuyển động có thể được đặt tên thông qua thuộc tính name:

<Transition name="fade">
  ...
</Transition>

Đối với một chuyển động được đặt tên, các lớp chuyển động của nó sẽ được tiền tố bằng tên của nó thay vì v. Ví dụ, lớp được áp dụng cho chuyển động trên sẽ là fade-enter-active thay vì v-enter-active. CSS cho chuyển động mờ nên trông giống như sau:

.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

2.3 Chuyển Động CSS

<Transition> thường được sử dụng kết hợp với chuyển động CSS tự nhiên, như đã thấy trong ví dụ cơ bản ở trên. Thuộc tính CSS transition là một phím tắt cho phép chúng ta chỉ định nhiều khía cạnh của một chuyển động, bao gồm các thuộc tính cần được hoạt hình, thời gian của chuyển động và đường cong làm mịn.

Dưới đây là một ví dụ nâng cao hơn về việc chuyển đổi nhiều thuộc tính, với các thời gian và đường cong làm mịn khác nhau cho các chuyển động vào và ra:

<Transition name="slide-fade">
  <p v-if="show">hello</p>
</Transition>
/*
  Enter and leave animations can use different
  durations and timing functions.
*/
.slide-fade-enter-active {
  transition: all 0.3s ease-out;
}

.slide-fade-leave-active {
  transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
}

.slide-fade-enter-from,
.slide-fade-leave-to {
  transform: translateX(20px);
  opacity: 0;
}



Thử nghiệm ở Playground

2.4 Hoạt Động CSS

Các hoạt động CSS tự nhiên được áp dụng cách như chuyển động CSS, với sự khác biệt là *-enter-from không được loại bỏ ngay sau khi phần tử được chèn, mà sau sự kiện animationend.
Đối với hầu hết các hoạt động CSS, chúng ta có thể đơn giản chỉ định chúng dưới các lớp *-enter-active*-leave-active. Dưới đây là một ví dụ:

<Transition name="bounce">
  <p v-if="show" style="text-align: center;">
    Hello here is some bouncy text!
  </p>
</Transition>
.bounce-enter-active {
  animation: bounce-in 0.5s;
}
.bounce-leave-active {
  animation: bounce-in 0.5s reverse;
}
@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.25);
  }
  100% {
    transform: scale(1);
  }
}


Thử nghiệm ở Playground

2.5 Các Lớp Chuyển Động Tùy Chỉnh

Bạn cũng có thể chỉ định các lớp chuyển động tùy chỉnh bằng cách truyền các props sau vào <Transition>:
enter-from-class
enter-active-class
enter-to-class
leave-from-class
leave-active-class
leave-to-class
Những lớp này sẽ ghi đè lên các tên lớp thông thường. Điều này đặc biệt hữu ích khi bạn muốn kết hợp hệ thống chuyển động của Vue với một thư viện hoạt động CSS hiện có, chẳng hạn như Animate.css:

<!-- assuming Animate.css is included on the page -->
<Transition
  name="custom-classes"
  enter-active-class="animate__animated animate__tada"
  leave-active-class="animate__animated animate__bounceOutRight"
  <p v-if="show">hello</p>
</Transition>


Thử nghiệm ở Playground

2.6 Sử Dụng Chuyển Động và Hoạt Động Cùng Nhau

Vue cần gắn các trình nghe sự kiện để biết khi nào một chuyển động kết thúc. Điều này có thể là transitionend hoặc animationend, phụ thuộc vào loại quy tắc CSS được áp dụng. Nếu bạn chỉ sử dụng một trong hai, Vue có thể tự động phát hiện loại chính xác.

Tuy nhiên, trong một số trường hợp, bạn có thể muốn có cả hai trên cùng một phần tử, ví dụ như có một hoạt động CSS được kích hoạt bởi Vue, cùng với một hiệu ứng chuyển động CSS khi di chuột qua. Trong những trường hợp này, bạn sẽ phải rõ ràng khai báo loại mà bạn muốn Vue quan tâm bằng cách truyền prop type, với giá trị là animation hoặc transition:

<Transition type="animation">...</Transition>

2.7 Chuyển Động Lồng Ghép và Thời Gian Chuyển Động Rõ Ràng

Mặc dù các lớp chuyển động chỉ được áp dụng cho phần tử con trực tiếp trong , chúng ta có thể chuyển đổi các phần tử lồng nhau bằng cách sử dụng các bộ chọn CSS lồng nhau:

<Transition name="nested">
  <div v-if="show" class="outer">
    <div class="inner">
      Hello
    </div>
  </div>
</Transition>
/* rules that target nested elements */
.nested-enter-active .inner,
.nested-leave-active .inner {
  transition: all 0.3s ease-in-out;
}

.nested-enter-from .inner,
.nested-leave-to .inner {
  transform: translateX(30px);
  opacity: 0;
}

/* ... other necessary CSS omitted */


Chúng ta thậm chí có thể thêm một độ trễ chuyển động cho phần tử lồng nhau khi vào, tạo ra một chuỗi chuyển động vào theo thứ tự lịch sự:

/* delay enter of nested element for staggered effect */
.nested-enter-active .inner {
  transition-delay: 0.25s;
}


Tuy nhiên, điều này tạo ra một vấn đề nhỏ. Mặc định, thành phần <Transition> cố gắng tự động xác định khi chuyển động đã kết thúc bằng cách lắng nghe sự kiện đầu tiên transitionend hoặc animationend trên phần tử chuyển động gốc. Với một chuyển động lồng nhau, hành vi mong muốn phải chờ đến khi các chuyển động của tất cả các phần tử lồng nhau đã kết thúc.

Trong những trường hợp như vậy, bạn có thể chỉ định một thời gian chuyển động rõ ràng (tính bằng mili giây) bằng cách sử dụng prop duration trên thành phần <Transition>. Tổng thời gian phải khớp với độ trễ cộng với thời gian chuyển động của phần tử lồng nhau:

<Transition :duration="550">...</Transition>


Thử nghiệm ở Playground
Nếu cần thiết, bạn cũng có thể chỉ định các giá trị riêng biệt cho thời gian chuyển động vào và ra bằng cách sử dụng một đối tượng:

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

2.8 Xem Xét Hiệu Suất

Bạn có thể nhận thấy rằng các hoạt động được hiển thị ở trên chủ yếu sử dụng các thuộc tính như transformopacity. Các thuộc tính này hiệu quả khi chuyển động vì:
1. Chúng không ảnh hưởng đến bố cục tài liệu trong suốt quá trình chuyển động, vì vậy chúng không kích hoạt tính toán bố cục CSS đắt đỏ trên mỗi khung hình chuyển động.
2. Hầu hết các trình duyệt hiện đại có thể tận dụng phần cứng GPU khi chuyển động transform.

So với đó, các thuộc tính như height hoặc margin sẽ kích hoạt bố cục CSS, vì vậy chúng đắt đỏ hơn để chuyển động và nên sử dụng cẩn thận. Chúng ta có thể kiểm tra tài nguyên như CSS-Triggers để xem các thuộc tính nào sẽ kích hoạt bố cục nếu chúng ta chuyển động chúng.

3. Các Khoá JavaScript

Bạn có thể kết nối vào quá trình chuyển động bằng JavaScript bằng cách lắng nghe các sự kiện trên thành phần :

Ví dụ 2 kiểu Option và Composition

<Transition
  @before-enter="onBeforeEnter"
  @enter="onEnter"
  @after-enter="onAfterEnter"
  @enter-cancelled="onEnterCancelled"
  @before-leave="onBeforeLeave"
  @leave="onLeave"
  @after-leave="onAfterLeave"
  @leave-cancelled="onLeaveCancelled"
  <!-- ... -->
</Transition>
// called before the element is inserted into the DOM.
// use this to set the "enter-from" state of the element
function onBeforeEnter(el) {}

// called one frame after the element is inserted.
// use this to start the entering animation.
function onEnter(el, done) {
  // call the done callback to indicate transition end
  // optional if used in combination with CSS
  done()
}

// called when the enter transition has finished.
function onAfterEnter(el) {}

// called when the enter transition is cancelled before completion.
function onEnterCancelled(el) {}

// called before the leave hook.
// Most of the time, you should just use the leave hook
function onBeforeLeave(el) {}

// called when the leave transition starts.
// use this to start the leaving animation.
function onLeave(el, done) {
  // call the done callback to indicate transition end
  // optional if used in combination with CSS
  done()
}

// called when the leave transition has finished and the
// element has been removed from the DOM.
function onAfterLeave(el) {}

// only available with v-show transitions
function onLeaveCancelled(el) {}
export default {
  // ...
  methods: {
    // called before the element is inserted into the DOM.
    // use this to set the "enter-from" state of the element
    onBeforeEnter(el) {},

    // called one frame after the element is inserted.
    // use this to start the animation.
    onEnter(el, done) {
      // call the done callback to indicate transition end
      // optional if used in combination with CSS
      done()
    },

    // called when the enter transition has finished.
    onAfterEnter(el) {},

    // called when the enter transition is cancelled before completion.
    onEnterCancelled(el) {},

    // called before the leave hook.
    // Most of the time, you should just use the leave hook.
    onBeforeLeave(el) {},

    // called when the leave transition starts.
    // use this to start the leaving animation.
    onLeave(el, done) {
      // call the done callback to indicate transition end
      // optional if used in combination with CSS
      done()
    },

    // called when the leave transition has finished and the
    // element has been removed from the DOM.
    onAfterLeave(el) {},

    // only available with v-show transitions
    onLeaveCancelled(el) {}
  }
}

Các khoá này có thể được sử dụng kết hợp với chuyển động / hoạt động CSS hoặc riêng lẻ.
Khi sử dụng chuyển động chỉ bằng JavaScript, thường là một ý tưởng tốt là thêm prop :css="false". Điều này một cách rõ ràng cho Vue biết bỏ qua phát hiện chuyển động CSS tự động. Ngoài việc hiệu suất nhất định hơn một chút, điều này cũng ngăn cản các quy tắc CSS từ việc gây ra sự can thiệp không cần thiết vào quá trình chuyển động:


<Transition
  ...
  :css="false"
  ...
</Transition>

Với :css="false", chúng ta cũng hoàn toàn chịu trách nhiệm điều khiển khi nào chuyển động kết thúc. Trong trường hợp này, các callback done là bắt buộc cho các khoá @enter@leave. Nếu không, các khoá sẽ được gọi đồng bộ và chuyển động sẽ kết thúc ngay lập tức.
Dưới đây là một demo sử dụng thư viện GSAP để thực hiện các hoạt động chuyển động. Bạn có thể, tất nhiên, sử dụng bất kỳ thư viện hoạt động nào khác bạn muốn, ví dụ như Anime.js hoặc Motion One:
Thử nghiệm ở Playground

4. Chuyển Động Tái Sử Dụng

Chuyển động có thể được tái sử dụng thông qua hệ thống thành phần của Vue. Để tạo một chuyển động có thể tái sử dụng, chúng ta có thể tạo một thành phần bao bọc thành phần và chuyền nội dung khe xuống:


<!-- MyTransition.vue -->
<script>
// JavaScript hooks logic...
</script>

  <!-- wrap the built-in Transition component -->
  <Transition
    name="my-transition"
    @enter="onEnter"
    @leave="onLeave">
    <slot></slot> <!-- pass down slot content -->
  </Transition>

<style>
/*
  Necessary CSS...
  Note: avoid using <style scoped> here since it
  does not apply to slot content.
*/
</style>

Bây giờ MyTransition có thể được nhập và sử dụng giống như phiên bản tích hợp sẵn:

<MyTransition>
  <div v-if="show">Hello</div>
</MyTransition>

5. Chuyển Động Khi Xuất Hiện

Nếu bạn cũng muốn áp dụng một chuyển động vào lúc kết xuất ban đầu của một nút, bạn có thể thêm prop appear:

<Transition appear>
  ...
</Transition>

6. Chuyển Động Giữa Các Phần Tử

Ngoài việc chuyển đổi một phần tử bằng v-if / v-show, chúng ta cũng có thể chuyển động giữa hai phần tử bằng cách sử dụng v-if / v-else / v-else-if, miễn là chúng ta đảm bảo chỉ có một phần tử được hiển thị vào bất kỳ thời điểm nào:

<Transition>
  <button v-if="docState === 'saved'">Edit</button>
  <button v-else-if="docState === 'edited'">Save</button>
  <button v-else-if="docState === 'editing'">Cancel</button>
</Transition>


Thử nghiệm ở Playground

7. Chế Độ Chuyển Động

Trong ví dụ trước, các phần tử nhập và rời khỏi được hoạt động cùng một lúc, và chúng ta đã phải đặt chúng thành position: absolute để tránh vấn đề bố cục khi cả hai phần tử hiện diện trong DOM.

Tuy nhiên, trong một số trường hợp điều này không phải là một lựa chọn, hoặc đơn giản là không phải là hành vi mong muốn. Chúng ta có thể muốn phần tử rời khỏi được hoạt động trước, và chỉ khi hoàn thành hoạt động rời khỏi thì phần tử nhập mới được chèn vào sau đó. Tổ chức các hoạt động này một cách thủ công sẽ rất phức tạp – may mắn thay, chúng ta có thể kích hoạt hành vi này bằng cách truyền prop mode vào <Transition>:

<Transition mode="out-in">
  ...
</Transition>

Dưới đây là demo trước với mode="out-in":

cũng hỗ trợ mode="in-out", tuy nó được sử dụng ít hơn nhiều.

8. Chuyển Động Giữa Các Thành Phần

<Transition> cũng có thể được sử dụng xung quanh các thành phần động:

<Transition name="fade" mode="out-in">
  <component :is="activeComponent"></component>
</Transition>


Thử nghiệm ở Playground

9. Chuyển Động Động

Các prop của <Transition> như name cũng có thể là động! Nó cho phép chúng ta áp dụng các chuyển động khác nhau dựa trên thay đổi trạng thái:

<Transition :name="transitionName">
  <!-- ... -->
</Transition>

Điều này có ích khi bạn đã xác định các chuyển động CSS / animations bằng cách sử dụng các quy ước lớp chuyển động của Vue và muốn chuyển đổi giữa chúng.
Bạn cũng có thể áp dụng hành vi khác nhau trong các hook chuyển động JavaScript dựa trên trạng thái hiện tại của thành phần của bạn. Cuối cùng, cách tốt nhất để tạo ra các chuyển động động là thông qua các thành phần chuyển động tái sử dụng nhận các props để thay đổi tính chất của các chuyển động sẽ được sử dụng. Có thể nghe có vẻ nhạt nhẽo, nhưng giới hạn thực sự chỉ là trí tưởng tượng của bạn.

10. Chuyển Động Với Thuộc Tính Key

Đôi khi bạn cần buộc một phần tử DOM phải vẽ lại để một chuyển động xảy ra.
Ví dụ 2 kiểu options vs composition, hãy xem xét thành phần bộ đếm này:

<script setup>
import { ref } from 'vue';
const count = ref(0);

setInterval(() => count.value++, 1000);
</script>

  <Transition>
    <span :key="count">{{ count }}</span>
  </Transition>
<script>
export default {
  data() {
    return {
      count: 1,
      interval: null 
    }
  },
  mounted() {
    this.interval = setInterval(() => {
      this.count++;
    }, 1000)
  },
  beforeDestroy() {
    clearInterval(this.interval)
  }
}
</script>

  <Transition>
    <span :key="count">{{ count }}</span>
  </Transition>

Nếu chúng ta loại bỏ thuộc tính key, chỉ có nút văn bản sẽ được cập nhật và do đó không có chuyển động nào xảy ra. Tuy nhiên, với thuộc tính key tồn tại, Vue biết phải tạo một phần tử span mới mỗi khi count thay đổi và do đó thành phần Transition có 2 phần tử khác nhau để chuyển đổi giữa chúng.
Thử nghiệm ở Playground

Trong bài viết này, chúng ta đã tìm hiểu về cách sử dụng Vuejs để tạo các hiệu ứng chuyển động tuyệt vời. Từ “ đến các kỹ thuật chuyển động phức tạp, chúng ta đã có cái nhìn tổng quan về cách Vuejs cung cấp các công cụ mạnh mẽ để tạo ra trải nghiệm người dùng động và hấp dẫn. Hy vọng bạn đã tận hưởng việc khám phá và học hỏi từ bài viết này trên Cafedev. Hãy tiếp tục áp dụng những kiến thức mới vào dự án của bạn và chia sẻ những trải nghiệm của mình với cộng đồng Vuejs!

Tham khảo thêm: MIỄN PHÍ 100% | Series tự học Vuejs từ cơ bản tới nâng cao

Các nguồn kiến thức MIỄN PHÍ VÔ GIÁ từ cafedev tại đây

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của CafeDev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!