تکمیل انیمیشن جاوا اسکریپتی + کامپوننت‌های پویا

Complete JavaScript Animation + Dynamic Components

14 آبان 1399
Vue.JS 2: تکمیل انیمیشن جاوا اسکریپتی + کامپوننت های پویا - قسمت 65

تکمیل انیمیشن جاوا اسکریپتی

همانطور که در جلسه قبل توضیح دادم برای استفاده از انیمیشن های جاوا اسکریپتی hook های مختلفی داریم اما انیمیشن اصلی درون enter و leave نوشته می شود بنابراین شروع به کدنویسی در enter کردیم اما با مشکلی روبرو شدیم. مشکل ما این است که عرض مربع ما به صورت تصاعدی رشد می کند:

  methods: {
    beforeEnter(el) {
      console.log("beforeEnter");
    },
    enter(el, done) {
      console.log("enter");
      let round = 1;
      const interval = setInterval(() => {
        el.style.width = el.style.width + round * 10 + "px";
      }, 20);
    },
// بقیه کدها //

همانطور که می بینید در هر دوره، عرض اضافه شده و برای دوره بعدی از همان عرض اضافه شده استفاده می شود و 10 پیکسل به آن اضافه خواهد شد. برای حل این مشکل می توانیم یک خصوصیت در data تعریف کنیم تا مستقل از عرض مربع باشد و تغییر نکند:

  data() {
    return {
      show: false,
      load: true,
      alertAnimation: "fade",
      elementWidth: 100
    };
  },

elementWidth همان عرض عنصر ما است که آن را روی 100 گذاشته ام. حالا می توانم قبلی را به شکل زیر ویرایش و تکمیل کنم:

    enter(el, done) {
      console.log("enter");
      let round = 1;
      const interval = setInterval(() => {
        el.style.width = this.elementWidth + round * 10 + "px";
        round++;
        if (round > 20) {
          clearInterval(interval);
          done();
        }
      }, 20);
    },

حالا رشد سایز مربع ما خطی است نه تصاعدی، چرا که مقدار elementWidth همیشه ثابت می ماند. یادتان نرود که px را به صورت رشته در آخر کد اضافه کنید. ما در حال نوشتن استایل هستیم و واحد ما پیکسل است بنابراین عدد خالی بدون پیکسل کار نخواهد کرد. پس از اضافه کردن 10 پیکسل در هر دور، باید 1 واحد به round اضافه کنیم تا به دور بعدی برویم. سپس می گوییم. اگر round به 20 رسید (یعنی 20 بار 10 پیکسل را اضافه کرده ایم) interval را می بندیم (متد clearInterval). در نهایت هم done را صدا می زنیم. توجه کنید که اینجا یک عملیات ناهمگام (asynchronous) داشتیم و اگر قرار بود با اتمام کدهای enter این قسمت تمام شود و صدا زدن done دست ما نبود، نمی توانستیم چنین کاری انجام بدهیم.  البته هنوز باید متد beforeEnter را ویرایش کنیم:

  methods: {
    beforeEnter(el) {
      console.log("beforeEnter");
      this.elementWidth = 100;
      el.style.width = this.elementWidth + "px";
    },

با اینکه elementWidth را تغییر نمی دهیم، اما برای اطمینان همیشه در beforeEnter آن را روی 100 تنظیم می کنیم تا کدها بهم نریزد. سپس باید مطمئن شویم که مربع ما قبل از اضافه شدن به DOM حتما دارای عرضی برابر با elementWidth است. چرا؟ اگر قبلا آن را اضافه کرده و حذف کرده باشیم و سپس دوباره بخواهیم آن را اضافه کنیم، ممکن است عرض آن تغییر کرده باشد و دیگر روی 100 (همان elementWidth) نباشد.

همین کار را برای beforeLeave نیز انجام می دهیم:

    beforeLeave(el) {
      console.log("beforeLeave");
      this.elementWidth = 300;
      el.style.width = this.elementWidth + "px";
    },

چرا 300؟ به دلیل اینکه ما interval را 20 بار اجرا می کنیم و هر دفعه 10 پیکسل به عرض اولیه اضافه می کنیم که می شود 200 پیکسل. خود عرض اولیه مربع ما نیز 100 پیکسل بود بنابراین جمعا می شود 300 پیکسل و برای اینکه انیمیشن ظاهر درستی داشته باشد باید حالت آن را در تمام قسمت ها حفظ کنیم. سپس برای انیمیشن خروجی (هنگام حذف عنصر) می توانیم از همان کدهای enter استفاده کرده و آن را کمی تغییر بدهیم:

    leave(el, done) {
      console.log("leave");
      let round = 1;
      const interval = setInterval(() => {
        el.style.width = this.elementWidth - round * 10 + "px";
        round++;
        if (round > 20) {
          clearInterval(interval);
          done();
        }
      }, 20);
    },

تنها تفاوتی که در این کد و enter است، تفریق کردن (به جای جمع کردن) برای el.style.width یا همان عرض مربع است چرا که می خواهیم در هنگام حذف کوچک تر شود. اگر الان به مرورگر بروید، کدها بدون مشکل کار می کنند اما در اولین بارگذاری صفحه شاهد یک پرش زشت هستیم. چرا؟ به دلیل اینکه عرض اولیه عنصر ما 100 است اما در انیمیشن خروج، عرض اولیه را 300 در نظر گرفته ایم بنابراین ابتدا از 100 به 300 می رسد و سپس تا 100 کوچک شده و در نهایت از بین می رود. راه حل این است اکه عرض اولیه را روی 300 بگذاریم:

<transition
  @before-enter="beforeEnter"
  @enter="enter"
  @after-enter="afterEnter"
  @enter-cancelled="enterCancelled"
  @before-leave="beforeLeave"
  @leave="leave"
  @after-leave="afterLeave"
  @leave-cancelled="leaveCancelled"
  :css="false"
>
  <div style="width: 300px; height: 100px; background-color: lightgreen" v-if="load"></div>
</transition>

با قرار دادن width روی 300 پیکسل دیگر شاهد این پرش نخواهیم بود. این هم از انیمیشن ما!

کامپوننت های پویا (ِDynamic Components)

اگر یادتان باشد قبلا در مورد کامپوننت های پویا صحبت کردیم. من می خواهم از قدرت این نوع کامپوننت های برای انیمیشن سازی استفاده کنم تا با هم به قدرت اصلی آن پی ببریم. برای این کار ابتدا دو کامپوننت جدید (دو فایل جدید) به نام های DangerAlert.vue و SuccessAlert.vue تعریف می کنیم (مستقیما در پوشه src باشند). محتویات فایل DangerAlert.vue به شکل زیر خواهد بود:

<template>
    <div class="alert alert-danger">This is dangerous!</div>
</template>

و محتویات SuccessAlert.vue نیز به شکل زیر خواهد بود:

<template>
    <div class="alert alert-success">This is successful!</div>
</template>

برای اینکه بتوانیم از این دو کامپوننت درون فایل App.vue استفاده کنیم باید آن ها import کنیم بنابراین به فایل App.vue رفته و می گوییم:

<script>
import DangerAlert from "./DangerAlert.vue";
import SuccessAlert from "./SuccessAlert.vue";

export default {
  data() {
    return {
      show: false,
      load: true,
      alertAnimation: "fade",
// بقیه کدها //

سپس آن ها را به عنوان کامپوننت محلی ثبت می کنیم:

// بقیه کدها //
    leaveCancelled(el) {
      console.log("leaveCancelled");
    }
  },
  components: {
    appDangerAlert: DangerAlert,
    appSuccessAlert: SuccessAlert
  }
};
</script>

در مرحله بعد باید خصوصیتی به نام selectedComponent را تعریف کنیم که مشخص کند کدام یکی از این عناصر به صورت پیش فرض نمایش داده شوند:

export default {
  data() {
    return {
      show: false,
      load: true,
      alertAnimation: "fade",
      elementWidth: 100,
      selectedComponent: "app-success-alert"
    };
  },

من success-alert را به عنوان کامپوننت پیش فرض برای نمایش انتخاب می کنم. قبلا هم توضیح داده ام که اگر برای تعریف نام کامپوننت ها از حروف بزرگ استفاده کنید، می توانید در قسمت های دیگر به جای آن ها از خط فاصله استفاده کنید. مثلا در کد بالا app-success-alert و appSuccessAlert هر دو صحیح هستند.

حالا در قسمت Template می گوییم:

// بقیه کدها //
          <div style="width: 300px; height: 100px; background-color: lightgreen" v-if="load"></div>
        </transition>
        <hr />
        <component :is="selectedComponent"></component>

همانطور که می دانید برای استفاده از کامپوننت های پویا باید از <component> استفاده کنیم و با is آن را به selectedComponent متصل می کنیم. سپس باید یک دکمه داشته باشیم که به ما اجازه دهد کامپوننت ها را تغییر دهیم بنابراین می گوییم:

    <div style="width: 300px; height: 100px; background-color: lightgreen" v-if="load"></div>
  </transition>
  <hr />
  <button
    class="btn btn-primary"
    @click="selectedComponent == 'app-success-alert' ? selectedComponent = 'app-danger-alert' : selectedComponent = 'app-success-alert'"
  >Toogle Components</button>
  <br />
  <br />
  <component :is="selectedComponent"></component>

دکمه من کلاس btn و btn-primary را گرفته است تا ظاهر بهتری داشته باشد. سپس برای آن یک click listener تعریف کرده ام که می گوید اگر selectedComponent برابر 'app-success-alert' بود، آن را برابر 'app-danger-alert' قرار بده و در غیر این صورت آن را برابر 'app-success-alert' قرار بده. به زبان ساده تر با کلیک روی این دکمه، از یک کامپوننت به کامپوننت دیگر منتقل می شویم (یک عملیات toggle ساده). حالا می توانید این کدها را ذخیره کرده و مرورگر را باز کنید. کدهای ما به درستی کار می کنند و فقط باید انیمیشن ساده ای به آن اضافه کنیم.

برای این کار باید یک transition داشته باشیم:

<transition name="fade" mode="out-in">
  <component :is="selectedComponent"></component>
</transition>

من از همان transition قدیمی خودمان (Fade) استفاده کرده ام و mode را نیز گذاشته ام تا شاهد جهش بین عنصر ها نباشیم. حالا همه چیز به خوبی در مرورگر کار می کند و می توانید آن را تست کنید.

توجه داشته باشید که از اول فصل تا این قسمت هر چیزی که یاد گرفته ایم برای نمایش یک عنصر بوده است. به زبان ساده تر <transition> به ما اجازه می دهد بین هر تعداد عنصری که خواستیم منتقل شویم (چه دو عنصر مانند مثال بالا و چه ده عنصر یا 100 عنصر) اما در نهایت فقط یکی از آن ها باید نمایش داده شود و نمی توانیم چندین عنصر را با هم نمایش دهیم. در قسمت بعدی در مورد دور زدن این مشکل و اضافه کردن انیمیشن به مجموعه ای از عناصر صحبت خواهیم کرد.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری آموزش رایگان Vue js از صفر تا صد توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.