20 Mart 2022 Pazar

Vue3 (Vue.js versiyon-3) Öğreniyorum - Bölüm 3

 Merhaba bu yazıyı okumaya başlamadan önce okumanız gereken 2 bölüm var.

Vue3 (Vue.js versiyon-3) Öğreniyorum - Bölüm 1

Vue3 (Vue.js versiyon-3) Öğreniyorum - Bölüm 2

Şimdi kaldığımız yerden devam edelim. Alt komponentlerden üst komponentde veri değiştirmenin ikinci yolu olarak Callback Fonksiyonları da kullanabiliriz demiştik. 


Vue Call Back Fonksiyonlar

Alt komponentten üst komponent verilerine müdahale için sağlıklı yol Olay Bildirimi (emit) geçen bölüm anlatmıştık. Call Back fonksiyonları da üst komponent verilerini değiştirmek için alternatif bir yoldur. Önce App komponentinde yaşDeğiştirCB adı ile kullanacağımız Call Back fonksiyonu tanımlayalım.


src/App.vue

    methods: {
      yaşDeğiştir(num) {
        this.yaş += num
      },
      yaşDeğiştirCB(num) {
        this.yaş += num
      }
    }

Daha önce olay bildirimi yönteminde kullandığımız metodla aynı işi yapıyor. Yeniden tanımlamamız iki yöntem birbirine karışmasın diye. Değişkeni alt komponente gönderebildiğimiz gibi fonksiyonu da gönderebiliriz. 

src/App.vue

<template>
  <selam :yaş="yaş"></selam>
  <user :yaş="yaş" @yaş-değişti="yaşDeğiştir" :yaşDeğiştirFN="yaşDeğiştirCB"></user>
</template>

User komponentinde props bölümünde fonksiyonumuz yaşDeğiştirFN adıyla alınacak. Bu fonksiyonu prop'da kayıt edelim.

src/components/User.vue

    props: {
        yaş: {
            type: Number,
            validator(value) {
                return value < 130
            }
        },
        yaşDeğiştirFN: Function
    },

Fonksiyon alt komponente gitti. Şimdi kodumuzda kullanabiliriz. Görsele bir buton daha ekleyelim o da bu fonksiyonu kullansın.

src/components/User.vue

<template>
  <button @click="onClickYaş">Yaşı Arttır</button>
  <button @click="yaşDeğiştirFN(3)">Yaşı Arttır CB</button>
  <p>Kullanıcı {{ yaş }} yaşında</p>
</template>

İkinci butondan direk fonksiyonu çağırdık. Test edersek aynı şekilde çalıştığını göreceğiz. Üst komponentten fonksiyonu aşağı gönderince fonksiyon kendisi nesne olarak gitti. Bir kopyası gitmedi. Bu durumda metodu aşağıda çağırınca metod sanki üst elemanda çağırılmış gibi oluyor. Çünkü metod üst komponentin metodu sonuçta. 

Peki hangisini kullanalım dersek, performans bakımından pek farkı yok ama VueJs geliştiricileri Olay Bildirimini öneriyorlar. Bunun ana sebebi olayları kullandığımızda Vue DevTools'da oalyı görüp verileri inceleyebiliriz. Böylece Debug işleri kolaylaşır. 




Vue Komponentde Slots Kullanımı

Slot'lar Vue komponentleri arasında veri paylaşımı için kullanılan bir başka yöntem. Bunu görebilmek için yepyeni bir uygulama üretelim. Uygulamalarımızın klasörlerini koyduğumuz üst klasörde bir terminal açalım ve,

 vue create slots              

Komutunu girelim. Seçeneklerde default Vue-3 seçeneği ile kurulumun gerçekleşmesini sağlayalım. Sonra da bildiğimiz gibi ,

 cd slots             

npm run serve     

Yazarak uygulamamızı tarayıcıda görelim. Temizlik yapmaya başlayalım, HelloWorld komponentini silerek başlayalım (src/components/HelloWorld.vue dosyası silinecek). Sonra da App komponenti içini boşaltıp, minimum uygulama yapalım.

src/App.vue

<template>

</template>

<script>
export default {
  name: 'App'
}
</script>

Hazırız. Görsel şablonda basit bir form oluşturacağız. Formda 3 bölge var bunları ifade için div elemanları kullanacağız.

src/App.vue

<template>
  <div class="help"></div>
  <div class="fields"></div>
  <div class="buttons"></div>
</template>

Bir help bölümü, burada kullanıcıya yol gösteren bir yazı var. fields bölümünde kullanıcının giriş yapacağı elemanlar var. buttons bölümünde de formu gönderme butonu olacak. 

src/App.vue

<template>
  <form>
    <div class="help">
      <p>Bu bir yardım yazısı</p>
    </div>
    <div class="fields">
      <input type="text" placeholder="email" />
      <input type="text" placeholder="username" />
      <input type="password" placeholder="password" />
    </div>
    <div class="buttons">
      <button type="submit">Gönder</button>
    </div>
  </form>
</template>

Stili şimdilik boş verelim. Ama buradan görünen bu formu bir komponent olarak tanımlamak düzenli kod yazmak için iyi bir fikir. Bu amaçla components klasöründe Form.vue ismiyle bir komponent tanımlayalım.

src/components/Form.vue

<template>
  <form>
    <div class="help">
      <p>Bu bir yardım yazısı</p>
    </div>
    <div class="fields">
      <input type="text" placeholder="email" />
      <input type="text" placeholder="username" />
      <input type="password" placeholder="password" />
    </div>
    <div class="buttons">
      <button type="submit">Gönder</button>
    </div>
  </form>
</template>

<script>
export default {
    name: "FormKomp"
}
</script>

Şimdilik script bölümü yok sadece şablona form elemanını taşıdık. Şimdi de App komponentinde Form komponentini ekleyelim. 

src/App.vue

<template>
  <app-form />
</template>

<script>
import AppForm from "./components/Form.vue"

export default {
  name: 'App',
  components: {
    AppForm
  }
}
</script>

Öncekinden farklı 2 davranış sergiledik. İlki Form.vue dosyasından aldığımız komponente AppForm adını verdik. Bu aslında kod berraklığı açısından kullandığımız bir teknik, App komponenti alt eleman Form komponenti şeklinde bir mantık kod büyüdükçe hakimiyeti kolaylaştırır. İkincisi görsel şablon içinde AppForm komponentini <app-form> şeklinde kebap-case ifade edebiliriz, böylece büyük harf kullanmayız. . 

Tarayıcıda sayfayı açıp bakalım, biraz şekilsiz ama formumuz görünmekte.

Formlar web uygulamalarında çok kullanılır, login form , register form gibi. Bir çok form elemanı birbirine benzer stiller ile uygulama içinde kullanılır. Mesela bir de iletişim formumuz olduğunu düşünelim. O form da da buradakine benzer elemanlar olacak ve büyük ihtimal stilleri aynı olacaktır. Aynı stili paylaşmasını props ile App komponentinden stilleri göndererek sağlayabiliriz. Ancak bu yöntem stilller karmaşıklaştıkça bizi zorlamaya başlayacaktır. Burası slotların kullanımı için çok uygun bir ortam oluşturuyor. 

Slot kullanmaya başlamadan önce komponentimizin hangi parçalarının diğer formlarda da kullanılacağını belirlemek lazım. Hazırlık olarak form elemanının içeriğini tekrar App komponentine taşıyacağız ama bir farkla formu app-form elemanı içine koyacağız.

src/App.vue

<template>
  <app-form>
    <div class="help">
      <p>Bu bir yardım yazısı</p>
    </div>
    <div class="fields">
      <input type="text" placeholder="email" />
      <input type="text" placeholder="username" />
      <input type="password" placeholder="password" />
    </div>
    <div class="buttons">
      <button type="submit">Gönder</button>
    </div>
  </app-form>
</template>

src/components/Form.vue

<template>
  <form>
  </form>
</template>

Şu halde çalışmaz çünkü Form alt komponenti daha App üst komponenti tarafından kendisine gönderilen içeriği nereye koyacağını bilmiyor. İşte bu amaçla <slot> elemanı kullanılır. 

src/components/Form.vue

<template>
  <form>
    <slot></slot>
  </form>
</template>

App üst komponentinden girilen içerik slot elemanı yerine VueJs tarafından yerleştirilecektir. Slot ile üst komponentten alt komponente HTML görsel gönderebiliriz. Üst komponentten slot içeriğinin boş gönderilmesi durumunda default bir şablon kullanabiliriz. 

src/components/Form.vue

<template>
  <form>
    <slot>Yayınlanacak Form Gönderilmedi</slot>
  </form>
</template>

App komponentinin şablonunda Form alt komponentini bir de içerik vermeden kullanırsak

src/App.vue

<template>
  <app-form />
  <app-form>
    <div class="help">
...

İlk app-form elemanı default içerikle gelecektir. 



İsimlendirilmiş Slotlar

İkinci form içinde de bir iletişim formu düzenleyelim. Önce diğer form içeriğini kopyalayarak başlayalım. Sonra mesajı ve input elemanlarını yeni forma göre düzenleyelim.

src/App.vue

<template>
  <app-form>
    <div class="help">
      <p>Bu iletişim formu yardım yazısı</p>
    </div>
    <div class="fields">
      <input type="text" placeholder="isim" />
      <input type="text" placeholder="mesaj" />
    </div>
    <div class="buttons">
      <button type="submit">Gönder</button>
    </div>
  </app-form>
...

Aynı Form alt komponentinde 2 değişik form yayınladık. 

Slot kullanarak Form alt komponentini bir iskelet olarak değişik formlar yayınlamak için kullandık. Ayrı ayrı bir Login ve bir Contact komponenti kullanmadan bunu gerçekleştirdik. Bu yöntemi isimlendirilmiş slotlar kullanarak daha etkili kullanabiliriz. İncelersek her iki formda ortak kullanılan şeyler var. Mesela her ikisinde de aynı class değerlerinde 3 bölüm var, bunları kopyalayıp durduk. Bu 3 bölümü Form alt komponentine taşıyabiliriz. 

src/components/Form.vue

<template>
  <form>
    <div class="help"></div>
    <div class="fields"></div>
    <div class="buttons"></div>  
  </form>
</template>

Her 3 bölümün içine de birer slot elemanı yerleştirelim , ama bu sefer name özelliği ile isimlendirerek.

src/components/Form.vue

    <div class="help">
      <slot name="help" />
    </div>
    <div class="fields">
      <slot name="fields" />
    </div>
    <div class="buttons">
      <slot name="buttons" />
    </div>  

Bu slotları kullanmak için tekrar App üst komponentine dönelim.

src/App.vue

<template>
  <app-form>
    <template v-slot:help>
      <p>Bu iletişim formu yardım yazısı</p>
    </template>
    <template v-slot:fields>
      <input type="text" placeholder="isim" />
      <input type="text" placeholder="mesaj" />
    </template>
    <template v-slot:buttons>
      <button type="submit">Gönder</button>
    </template>
  </app-form>
  <app-form>
    <template v-slot:help>
      <p>Bu bir yardım yazısı</p>
    </template>
    <template v-slot:fields>
      <input type="text" placeholder="email" />
      <input type="text" placeholder="username" />
      <input type="password" placeholder="password" />
    </template>
    <template v-slot:buttons>
      <button type="submit">Gönder</button>
    </template>
  </app-form>
</template>

v-slot:help gibi Form alt komponentinde verdiğimiz isimlerle template elemanları ile bölümlendirmeyi yapıyoruz. v-slot direktifi div elemanında çalışmaz çalışması için template elemanı olması lazım, bu yüzden div elemanlarını template olarak değiştirdik. Test ettiğimizde görselin hala normal çalıştığını görürüz. 

Burada isimlendirilmiş ve isimlendirilmemiş slot karışımı da yapabiliriz. Eğer alt komponentte isim verilmemiş bir slot varsa üst komponentte isim verilmemiş elemanlar oraya gelecektir. Örnek:

src/App.vue

<template>
  <app-form>
    <template v-slot:help>
      <p>Bu iletişim formu yardım yazısı</p>
    </template>
    <template v-slot:fields>
      <input type="text" placeholder="isim" />
      <input type="text" placeholder="mesaj" />
    </template>
    <template v-slot:buttons>
      <button type="submit">Gönder</button>
    </template>
    <p>İlave paragraf</p>
  </app-form>

src/components/Form.vue

...
    <div class="buttons">
      <slot name="buttons" />
    </div>  
    <slot />
  </form>
</template>

Üstteki formda ilave etiğimiz paragraf isimsiz olan slot elemanı yerine geldi. Ancak ikinci formda v-slot ile isimlendirilmemiş bir eleman olmadığı için oraya bir şey gelmedi. 



Vue Slot ile Alt Komponente Veri Paylaşımı

Slot yardımı ile üst komponentten aşağı doğru veri paylaşımı yapabiliriz. App komponentinde data() metodu ekleyerek veri girelim. 

src/App.vue

<script>
import AppForm from "./components/Form.vue"

export default {
  name: 'App',
  components: {
    AppForm
  },
  data() {
    return {
      help: "Bu bir yardım yazısı"
    }
  }
}
</script>

Alttaki formun yardım yazısını help adında değişkene tanımladık. Bunu görselde kullanabiliriz.

src/App.vue

  <app-form>
    <template v-slot:help>
      <p>{{ help }}</p>
    </template>
    <template v-slot:fields>
      <input type="text" placeholder="email" />
      <input type="text" placeholder="username" />
      <input type="password" placeholder="password" />
    </template>
    <template v-slot:buttons>
      <button type="submit">Gönder</button>
    </template>
  </app-form>

Çalışıyor çünkü hala App komponenti içindeyiz. Bu yöntemde prop kullanmadan alt komponente bilgi gönderilmiş oldu. Bu da bir alternatif olarak kullanılabilir. Yani değişken değerini değil de komple değerin gösterimini slot kullanarak alt komponentte kullanmış olduk. Bu da yerine göre işe yarayabilecek ilginç bir yöntem olarak aklımızda kalsın.





Dinamik Komponentler

Ne komponentlermiş ama? Ne yapalım VueJs ile uygulama yaparken en çok kullanılacak özellik komponentler. Dinamik komponentler , komponentler arasında atlama yapmak için kullanılır. Daha açık ifade etmek gerekirse bir komponentten diğerine geçiş (switching). Mesala tab sekmelerinde görselde komponentlerin gösteriminde bu dinamik  geçişler kullanılabilir. 

Dinamik komponent kullanımı karmaşık değil, tabi önceden bilmemiz gereken şeyler var. Bunları göstermek için yeni bir uygulama ile başlıyoruz. Ana klasöre çıkıp terminalde şunu yazalım:

 vue create dinamik-komponentler       

Uzun biir uygulama adı oldu ama ne yapalım. Kurulumda default Vue-3 seçenekleri ile kuralım. Standart hareket uygulama klasörüne geçip server çalıştırarak tarayıcıda uygulamanın çalıştığını test edelim.

 cd dinamik-komponentler     

 npm run serve                  

Temizliğe başlayalım, components klasörü içinde Vue-CLI tarafından üretilmiş komponentleri silelim. App komponentinde style bloğunu silelim, script ve template içeriğini minimum yaparak başlayalım. 

src/App.vue

<template>
  Test
</template>

<script>
export default {
  name: 'App',
}
</script>

<style>
</style>

components klasöründe  Home ve About adında 2 komponent ekleyelim, sanki bir web sitesi sayfaları gibi.

src/components/About.vue

<template>
    <h1>Hakkımızda Sayfası</h1>
</template>

src/components/Home.vue

<template>
    <h1>Ana Sayfa</h1>
</template>

Her iki komponentte de hangi komponent olduğunu belirtir bir mesaj yayınlanıyor. Bu sayfaları kullanıcı seçimine göre yayınlamak istiyoruz. Sayfada bir dropdown elemandan kullanıcı hangi komponenti görmek istediğini seçecek. 

src/App.vue

<template>
  <select>
    <option value="Home">Ana Sayfa</option>
    <option value="About">Hakkımızda</option>
  </select>
</template>

Şimdi de script içinde komponentleri import edelim ki kullanabilelim. 

src/App.vue

<script>
import Home from "./components/Home.vue"
import About from "./components/About.vue"

export default {
  name: 'App',
  components: {
    AppHome, AppAbout
  }
}
</script>

Komponentleri de kendi dosyalarında script bölümünde export edelim. 

src/components/About.vue

<script>
export default {
    name: "AboutKomp"
}
</script>

src/components/Home.vue

<script>
export default {
    name: "HomeKomp"
}
</script>

Komponentlerimiz hazır ama biz hemen şablona koymak istemiyoruz. Kullanıcı seçim yapınca seçtiği komponent gelsin görsele. Yapılan seçimin komponent adı için bir değişken tanımlayalım, default değeri "Home" olsun.

src/App.vue

export default {
  name: 'App',
  components: {
    Home, About
  },
  data() {
    return {
      seçiliSayfa: "Home"
    }
  }
}

Görselde seçime bu değişkeni v-model ile bağlayalım.

src/App.vue

  <select v-model="seçiliSayfa">
    <option value="Home">Ana Sayfa</option>
    <option value="About">Hakkımızda</option>
  </select>

Seçimimize göre bir komponent yayınlamak için <component> elemanı kullanılır. Hangi komponentin yayınlanacağı ise is özelliğine dinamik bağlama yaparak belirtilir.

src/App.vue

<template>
  <select v-model="seçiliSayfa">
    <option value="Home">Ana Sayfa</option>
    <option value="About">Hakkımızda</option>
  </select>

  <component :is="seçiliSayfa"></component>
</template>

Test edelim

İşlem basit , görsel şablonumuzda component komponentleri kullanacağız. Sarımsaklasak da mı saklasak gibi oldu. 




Komponentlerde Veri Kalıcılığı

Az evvelki dinamik komponentleri kullanırken çok karşılaşılan bir sorun, bir o komponent bir bu komponente geçerken her seferinde komponent tekrar monte edilir ve içinde kullanılan data değerleri default değeriyle başlar. Olayı görmek için komponentlerde Life Cycle fonksiyonlarından log satırı atalım. 

src/components/Home.vue

<script>
export default {
    name: "HomeKomp",
    unmounted() {
        console.log("Home komponenti unmount oldu")
    }
}
</script>

Test edersek komponent her Home'dan About'a geçişte unmount olduğuna dair mesajı yazacaktır. Demek ki hakikatten her seferinde tekrar monte ediliyor. Umarım bunun bir çöp maliyeti yoktur. Neyse bizi ilgilendiren mesela tablarla parça parça görsellerde kullanıcı veriler giriyor, diğer tab'a geçip geri geldiğinde girdiği değerler uçmasın, ya da bizim kullandığımız veriler de olabilir ve onların en son değerlerini muhafaza etmesini isteyebiliriz. Bu amaçla component elemanını keep-alive (canlı kalsın) elemanı içine alırız.

src/App.vue

...
  <keep-alive>
    <component :is="seçiliSayfa"></component>
  </keep-alive>
</template>
...

Test edersek artık konsola mesaj gelmeyecek çünkü komponent VueJs tarafından yok edilmeyip hafızada canlı tutulacaktır. Vue DevTools'da görebiliriz.

Yok ben şimdi de komponentin aktif olduğu zamanı bilmek istiyorum dersek, o amaçla 2 life cycle fonksiyonu var komponentler için.

src/components/Home.vue

    activated() {
        console.log("Home komponenti aktif oldu")
    },
    deactivated() {
        console.log("Home komponenti pasif oldu")
    },

Komponent aktif olduğunda ya da pasif olduğunda yapacaklarınızı bu metodlar içinde yapabilirsiniz. 






Vue'de Transition ile CSS Animasyon Yapmak

Sırayı stillere getirmeyi başardık. Kod mantıklarını öğrendikten sonra biraz da şekil verelim görsellerimize. Animasyonlar kullanıcının sayfada ilgisi ve bilgisini gereken elemanlara çekmek için kullanılır. İki çeşit animasyon vardır CSS ile yapılan animasyonlar ve JavaScript ile yapılan animasyonlar. 

CSS animasyonları yapmakta kullanılan iki Vue öğesi var , transition ve animation. Önce transition inceleyeceğiz. animasyonlar adında yeni bir Vue uygulaması üretelim ve minimum uygulama için yaptığımız silmeleri daha önce gördüğümüz gibi yapalım. 

src/App.vue

<template>
  <button type="button">Toggle</button>
  <h2>Merhaba Dünya!</h2>
</template>

<script>
export default {
  name: 'App',
}
</script>

<style>
</style>

Sayfaya bir buton bir de mesaj koyduk. Buton animasyonu ateşlemek için kullanılacak. Animasyonda kullanmak üzere mesajın sayfada olup olmadığını bir değişken ile takip edelim. 

src/App.vue

<script>
export default {
  name: 'App',
  data() {
    return {
      flag: false
    }
  }
}
</script>

Mesajın sayfada olmasını v-if direktifi ile kontrol edelim. 

<template>
  <button type="button">Toggle</button>
  <h2 v-if="flag">Merhaba Dünya!</h2>
</template>

Buton her tıkta flag değerini ters çevirecek.

<template>
  <button type="button" @click="flag = !flag">Toggle</button>
  <h2 v-if="flag">Merhaba Dünya!</h2>
</template>

Uygulamayı test ettiğimizde buton tıklandıkça mesajın görünüp kaybolduğunu göreceğiz. Güzel ama yumuşak bir geçiş sağlayarak daha güzel görünüm elde edebiliriz. Animasyon yapabilmek için mesajı <transition> komponenti içine alacağız. 

src/App.vue

<template>
  <button type="button" @click="flag = !flag">Toggle</button>

  <transition name="fade">
    <h2 v-if="flag">Merhaba Dünya!</h2>
  </transition>
</template>

Temel yapı bu. transition elemanına name değeri ile verdiğimiz isim yardımıyla animasyon tanımı yapacağız. 3 stil tanımı olacak, bunlar isim fade olduğuna göre:

  • fade-enter-from : animasyonun başlangıcında stil. Buradan harekete geçecek
  • fade-enter-to : animasyonun bitişinde stil. Animasyon burada bitecek
  • fade-enter-active : animasyon esnası stil. Animasyon süresi vs..

Şimdi stil için CSS yazalım

<style>
.fade-enter-from {
  opacity: 0;
}

.fade-enter-active {
  transition: all 2.5s linear;
}
</style>

Şeffaftan başlayacak 2.5 saniye içinde son hali alacak lineer animasyon. Butona yıklayınca mesaj gelirken 2.5 saniyede belirecektir. Ama yok olurken animasyon yok. Şu anda animasyon eleman ortaya çıkarken. 

Eleman yok olurken de baştakine benzer class isimleri vardır:

  • fade-leave-from : Animasyonun başındaki class değeri. 
  • fade- leave-active :Tüm animasyon boyunca eklenen class değeri.
  • fade-leave-to : Animasyonun son karesinde class değeri

<style>
.fade-enter-from {
  opacity: 0;
}

.fade-enter-active {
  transition: all 2.5s linear;
}

.fade-leave-to {
  transition: all 2.5s linear;
  opacity: 0;
}
</style>

Girişte enter-from ve enter-active adımlarında ayrı ayrı yaptığımız şeyleri niye birleştirdik? enter-from eğer animasyonu film şeridi gibi düşünürsek ki öyle zaten, sadece animasyonun ilk karesinde aktif oluyor. Ama geçişi kontrol eden transition özelliğinin tüm animasyon boyunca aktif olması lazım, o yüzden girerken enter-active class değerini kullandık. enter-to kullanmadık çünkü son hedef opacity değerinin 1 olması ki bir elemanı zaten stilsiz bıraksak opacity değeri 1 olur. 

Eleman yok olurken de leave-from kullanmadık, çünkü zaten opacity değeri 1 olarak başlayacak. Hedefi belirttik leave-to ile ve opacity değeri 0 olacak dedik zaten sonunda da eleman sayfadan yok olacak. Neden transition değerini burada verdik? Çünkü leave-from sadece animasyonun ilk karesinde geçerlidir, geri kalan tamamı boyunca leave-to class değeri aktif olacaktır. Bunların hepsini Geliştirici araçlarında Elements bölümünde h1 elemanına eklenen ve silinen class değerleri olarak görebiliriz. Tabii ki ilk baştaki enter-from ve leave-from class değerleri sadece animasyonların ilk karelerinde verildiği için o kadar kısa süreyi görmemiz imkansız. 

Verilen bütün class değerlerini kullanmak zorunda değiliz, örnekteki gibi sadece lazım olanları kullanmamız yeterli. 

v-if ile de kısıtlı değiliz v-show direktifi ile de kullanabiliriz. Ayrıca bir tek elemanla da kısıtlı değiliz, mesela bir de v-else durumunda mesaj ekleyelim görsele.

src/App.vue

  <transition name="fade">
    <h2 v-if="flag">Merhaba Dünya!</h2>
    <h2 v-else>Öteki mesaj</h2>
  </transition>

Biri gelirken diğeri giderken animasyonu yapacaktır. Sürelerle oynayıp görebilirsiniz. Ama burada iki elemanda animasyon esnasında aynı anda kısa bir süre görünüyor. Sonra yok olan eleman kaybolunca yeni gelen onun yerine kayıyor. Önce giden gitsin sonra yenisi gelsin dersek mode diye bir özellik yardımıyla bunu yaparız. 

  <transition name="fade" mode="out-in">
    <h2 v-if="flag">Merhaba Dünya!</h2>
    <h2 v-else>Öteki mesaj</h2>
  </transition>

Yani önce out sonra in işlemi yapılsın seçtik. in-out olsaydı önce yeni gelen eleman animasyonunu bitirecek giden eleman onun animasyonu bitince gidiş animasyonuna başlayacaktı. Bunlar böyle ihtiyaca göre kullanılırlar. 




animation Özelliğini Kullanmak

Animasyon yaparken CSS animation özelliği de kullanabiliriz. Burada CSS açıklamalarına girmeyeceğiz, bir örnek yapalım göstermek için. Önceki transition elemanını yoruma çıkarıp yeni bir tane ekleyelim.

src/App.vue

<template>
  <button type="button" @click="flag = !flag">Toggle</button>

  <!-- transition name="fade" mode="in-out">
    <h2 v-if="flag">Merhaba Dünya!</h2>
    <h2 v-else>Öteki mesaj</h2>
  </transition -->
  <transition name="zoom">
    <h2 v-if="flag">Merhaba!</h2>
  </transition>
</template>

Bu sefer zoom efekti yapacağımız için adını zoom koyduk. Animasyona başlamadan önce h2 elemanına default bir stil verelim. 

src/App.vue

<style>
h2 {
  width: 400px;
  padding: 20px;
  margin: 20px;
}

animation özelliği @keyframes tanımlamasını kullanır. style bölümü en sonuna zoom-in ve zoom-out adlarında @keyframes bölümleri ekleyerek ayarlarını yapalım.

src/App.vue

@keyframes zoom-in {
  from {
    transform: scale(0, 0);
  }
  to {
    transform: scale(1, 1);
  }
}

@keyframes zoom-out {
  from {
    transform: scale(1, 1);
  }
  to {
    transform: scale(0, 0);
  }
}
</style>

zoom-in küçükten büyüğe zoom-out büyükten küçüğe gidecek. Daha önce gördüğümüze göre zoom-enter-active ve zoom-leave-active class değerleri kullanarak animasyonu yapabiliriz. 

.zoom-enter-active {
  animation: zoom-in 1s linear forwards;
}

.zoom-leave-active {
  animation: zoom-out 1s linear forwards;
}

@keyframes zoom-in {
...

Mesaj gösterilirken zoom-in frame'leri kullanılacak, 1 saniye sürecek, lineer süre artırımı ve animasyon bitince eleman en son halinde kalacak. Eleman yok olurken de zoom-out frame'leri kullanılacak gerisi aynı. Test edersek butona tıklayınca mesajın büyüyerek geldiğini , bir daha tıklayınca küçülerek yok olduğunu görürüz. 

transition ya da animation özelliklerinin birini ya da her ikisini kullanabiliriz. mesela ikisini birlikte kullanalım. Bir önceki örnekte yaptığımızı yapıp opacity değeri yardımıyla fade animasyonu ekleyelim bu örneğe de.

src/App.vue

.zoom-enter-active {
  animation: zoom-in 1s linear forwards;
  transition: all 1s linear;
}

.zoom-leave-active {
  animation: zoom-out 1s linear forwards;
  transition: all 1s linear;
}

.zoom-enter-from {
  opacity: 0;
}

.zoom-leave-to {
  opacity: 0;
}

Hem zoom yapıyor, hem silikten geliyor. Hem küçülerek yok oluyor, hem silinerek. Burada her iki çeşit animasyona da 1 saniye süre verdik diyelim transition seçeneğini 3 saniye yaparsak ne olur?

.zoom-enter-active {
  animation: zoom-in 1s linear forwards;
  transition: all 3s linear;
}

.zoom-leave-active {
  animation: zoom-out 1s linear forwards;
  transition: all 3s linear;
}

Test edersek transition işi bitmeden zoom bitiyor ve bittiğinde daha mesaj yarı silik oluyor, sonra tamamlıyor. Eğer istersek Vue transition komponentinde type özelliği ile hangi animasyonun şartları kullanacağını belirtebiliriz.

  <transition name="zoom" type="animation">
    <h2 v-if="flag">Merhaba!</h2>
  </transition>
</template>

 Burada diyoruz ki asıl olan animation özelliği ona göre animasyon olsun. Bu durumda 1 saniye sonunda animasyon bitirilir. Test edersek zoom'un sonunda daha mesaj yarı silik vaziyette iken animasyonun yarım kesilip son görünüme geçildiğini görürüz. Ayırt etmekte sorun yaşıyorsanız süreleri uzatarak etkisini daha net görebilirsiniz. 

Bazen animasyonu sayfa ilk yüklendiği anda çalıştırmak isteriz. Mesela mesaj başlangıçta görünür olsun.

src/App.vue

  data() {
    return {
      flag: true
    }
  }

Sayfayı yenileyince mesaj animasyonsuz olarak hemen geliyor. Animasyonun sayfa yüklenirken de çalışmasını istiyorsak Vue transition komponentine appear özelliği eklemeliyiz.

  <transition name="zoom" type="animation" appear>
    <h2 v-if="flag">Merhaba!</h2>
  </transition>

Artık sayfa yüklenirken de animasyon çalışıyor. 




JavaScript ile Animasyon Yapmak

CSS animasyon yaparken kullanabileceğimiz hafif bir yöntem. Elemanlar üzerinde daha çok hakim olmayı istersek JavaScript ile animasyon yaparız. 


Vue bize bir elemanı görsele ekleyip çıkarırken 3'er tane olay tetikler. 

  • before-enter : Eleman eklenmeden öncesi. Burada ihtiyaç olabilecek şeyler hazırlanır.
  • enter : Burası animasyonun çalışacağı yerdir.
  • after-enter : Eleman eklenmesi animasyonu bittikten sonrası. Temizlenecek şeyler varsa genelde burada yapılır.
  • before-leave : Elemanı kaldırma animasyonu başlamadan öncesi.
  • leave : Elemanı kaldırma animasyonu çalıştığı yer.
  • after-leave : Elemanı kaldırdıktan sonrası.

Uygulamamıza geri dönelim ve en son kullandığımız transition komponentini de yoruma atalım. Yeni bir transition komponent ekleyelim.

src/App.vue

  <!-- transition name="zoom" type="animation" appear>
    <h2 v-if="flag">Merhaba!</h2>
  </transition -->
  <transition>
    <h2 v-if="flag">Hey!</h2>
  </transition>
</template>

JavaScript yoluyla yapacağımız animasyonlar için olayları bu transition olaylarından takip ederiz.

  <transition
    @before-enter=""
    @enter=""
    @after-enter=""
    @before-leave=""
    @leave=""
    @after-leave=""
    >
    <h2 v-if="flag">Hey!</h2>
  </transition>
</template>

Her olaya bir metod yapıştıralım.

src/App.vue

  <transition
    @before-enter="beforeEnter"
    @enter="enter"
    @after-enter="afterEnter"
    @before-leave="beforeLeave"
    @leave="leave"
    @after-leave="afterLeave"
    >
    <h2 v-if="flag">Hey!</h2>
  </transition>

Bu arada yukarıda bahsedilmeyen 2 olay daha var, enter-cancelled ve leave-cancelled. Bu olaylar da ilgili animasyon iptal edildiğinde tetiklenir, ama pek kullanılmaz. Şimdi gidip her olaya yazdığımız metodları script bölümünde ekleyelim.

<script>
export default {
  name: 'App',
  data() {
    return {
      flag: true
    }
  },
  methods: {
    beforeEnter() {

    },
    enter() {

    },
    afterEnter() {

    },
    beforeLeave() {

    },
    leave() {

    },
    afterLeave() {

    }
  }
}
</script>

Bütün bu metodlara parametre olarak bağlı olduğu eleman gelir. Bunu kullanarak ne istersek yapabiliriz. enter ve leave metodlarına bir parametre daha olarak done diye bir metod gelir. Bu iki metodun sonunda done() metodunu çağırmak zorundayız, sisteme animasyonu bitirdik demek için. Şimdi metodlar şöyle oldu.

src/App.vue

  methods: {
    beforeEnter(el) {

    },
    enter(el, done) {
      done()
    },
    afterEnter(el) {

    },
    beforeLeave(el) {

    },
    leave(el, done) {
      done()
    },
    afterLeave(el) {

    }
  }

Hadi her metoddan bir log yazalım, mesaja elementi de ekleyelim. 

  methods: {
    beforeEnter(el) {
      console.log("before-enter olayı", el)
    },
    enter(el, done) {
      console.log("enter olayı", el)
      done()
    },
    afterEnter(el) {
      console.log("after-enter olayı", el)
    },
    beforeLeave(el) {
      console.log("before-leave olayı", el)
    },
    leave(el, done) {
      console.log("leave olayı", el)
      done()
    },
    afterLeave(el) {
      console.log("after-leave olayı", el)
    }
  }

Aslında parametreye verdiğimiz el değerini metod içinde kullanmazsak ESLint bize hata mesajı verecek de o yüzden konsola yazalım dedik. Sayfayı test edersek konsolda sıra sıra log yazılarımızı görürüz. 

Eski konu ama dikkat ettiyseniz isim vermediğimiz transition komponentinde Vue default olarak v-leave-to , v-enter-to gibi class isimleri veriyor yine de komponente. 

Buraya kadar yapıyı gördük, henüz hiç bir animasyon yapmadık. Yine bir zoom animasyonu yapalım. Tarayıcılarda ortak kullanılabilen bir JavaScript animasyon yöntemi var adı Web Animations API.  

enter animasyonunda kullanalım.

src/App.vue

    enter(el, done) {
      console.log("enter olayı", el)
      el.animate([ { transform: "scale3d(0,0,0)" }, { } ],
                  { duration: 1000 })
      done()
    },

Animasyonda ilk adım elemanı seçmek, animasyon ancak bir eleman ile gerçekleşir. Ama bizim standart JavaScrip teknikleri ile elemanı getirmemize gerek yok çünkü Vue metodumuza elemanı parametre olarak gönderiyor. Elemanın animate() fonksiyonu çağırarak animasyonu gerçekleştiriyoruz. Bu fonksiyona ilk parametre olarak bir array veririz. Bu array içinde animasyon adımları vardır.  

Burada 2 adım verdik, ilki animasyon başında elemanın özellikleri, ikinci animasyon bittikten sonra elemanın özellikleri. İlk adımda scale3d ile eleman boyutunu sıfırdan başlattık. İkinciye bir şey yazmayınca zaten boyutu neyse o olacaktır, o yüzden ikinci özellik nesnesini boş olarak girdik. 

animate() fonksiyonu 2. parametresi animasyonun nasıl gerçekleşeceğine ait özellikleri veren bir nesne. Burada da sadece duration özelliğinde süremizin 1000 mili saniye olduğunu belirttik. 

Burada bir hata var. animate() fonksiyonunun çalışmasını bitirmesi zaman alacak, ama bu arada done() metodu çağrılacak. sonra animasyon hala devam ediyor. Vue tam olarak animasyonun bittiğini algılayamıyor. Bunu konsola atılan mesajlarda görebiliriz. v-enter-to class değeri elemanda takılı kalıyor. Yani Vue enter işleminin bittiğini anlayamadı.  

Bunu önlemek için done() metodunu animate() çalışmayı bitirdikten sonra çağırmalıyız. 

src/App.vue

    enter(el, done) {
      console.log("enter olayı", el)
      const animasyon = el.animate([ { transform: "scale3d(0,0,0)" }, { } ],
                  { duration: 1000 })
      animasyon.onfinish = () => {
        done()
      }
    },

Eleman kaldırılırken de benzer animasyon yapıyoruz.

src/App.vue

    leave(el, done) {
      console.log("leave olayı", el)
      const animasyon = el.animate([ {}, { transform: "scale3d(0,0,0)" } ],
                  { duration: 1000 })
      animasyon.onfinish = () => {
        done()
      }
    },

Sadece başlangıç ve bitiş değerlerinin verildiği array içinde olayın başını sonunu değiştirdik. Test edip çalışmasını kontrol edelim. 

Vue animasyona bakarken önce CSS animasyon var mı ona bakıyor sonra JavaScript animasyon var mı ona bakıyor. class değerleri yazıldığına göre CSS animasyon yapar gibi Vue komponenti işlemeye devam ediyor demektir. Bunu transition komponentine css özelliği bağlaması yaparak engeler ve performans artırabiliriz.

  <transition
    @before-enter="beforeEnter"
    @enter="enter"
    @after-enter="afterEnter"
    @before-leave="beforeLeave"
    @leave="leave"
    @after-leave="afterLeave"
    :css="false">
    <h2 v-if="flag">Hey!</h2>
  </transition>

Gitti güzelim class isimleri.



Vue'de CSS ve JavaScript Animasyon Birlikte Kullanımı

CSS animasyonu kapatmıştık, önce orayı iptal ederek başlayalım. Animasyonun kontrollerini CSS ile yaparsak JavaScript içinde animasyonlar devam ederken çalıştırmak istediğimiz arkaplan işlerini yapabiliriz. Bunları yaparken CSS ile animasyon yaptığımız için done() metodunu çağırmamıza gerek olmaz, Vue animasyonun bittiği zamanı bilir. Örnek olsun diye transition komponentimize yine fade ismini vererek ilk yaptığımız animasyonun olmasını sağlayalım. JavaScript içinde yaptığımız animasyonu da iptal edelim.

src/App.vue

  <transition
    @before-enter="beforeEnter"
    @enter="enter"
    @after-enter="afterEnter"
    @before-leave="beforeLeave"
    @leave="leave"
    @after-leave="afterLeave"
    name="fade"
  >
    <h2 v-if="flag">Hey!</h2>
  </transition>

ve

  methods: {
    beforeEnter(el) {
      console.log("before-enter olayı", el)
    },
    enter(el) {
      console.log("enter olayı", el)
    },
    afterEnter(el) {
      console.log("after-enter olayı", el)
    },
    beforeLeave(el) {
      console.log("before-leave olayı", el)
    },
    leave(el) {
      console.log("leave olayı", el)
    },
    afterLeave(el) {
      console.log("after-leave olayı", el)
    }
  }

Animasyonu JavaScriptle yapmadığımız için done() metod çağırmalarını ve done metodunu parametrede vermeyi de iptal ettik. Test edersek animasyonun CSS ile çalıştığını ama JavaScript metodların da hala aktif olduğunu görürüz. Mecbur kalmadıkça bu yapıya sadık kalıp görsel işleri CSS arkaplan işleri JavaScript ile yapmak hem mantığa hem performansa uygun olacaktır.




Vue Listelerde Animasyon

Şimdiye kadar gördüğümüz animasyonlar hep elemanın var ya da yok olması ile ilgili. Ama mesela bir liste olunca listeden bir item silinecek ya da eklenecek. Şimdi de onu nasıl yaparızı görelim.

Önce uygulamamıza basit bir liste ekleyelim.

src/App.vue

  data() {
    return {
      flag: true,
      sayılar: [1, 2, 3, 4, 5]
    }
  },

Çok basit bir liste amaç işi öğrenmek. Önceki transition elemanını da yoruma çıkarıp görsele sayılar değerini gösterecek bir liste koyalım.

src/App.vue

    <h2 v-if="flag">Hey!</h2>
  </transition -->
  <ul>
    <li v-for="sayı in sayılar" :key="sayı">
      {{ sayı }}
    </li>
  </ul>
</template>

:key özelliğini girmek zorunda değiliz desek de eğer girmezsek Vue CLI bize hata mesajı verip :key özelliği ister. Çünkü Vue kullanıyor ve bir liste yayınlıyorsak o key bize lazım olacak elbet.

Sayfayı açtığımızda sayılarımızı listede göreceğiz. Devam etmeden stile bir küçük ilave ile liste elemanlarını daha görünür yapalım. Cursor de parmak olsun.

src/App.vue

<style>
li {
  font-size: 22px;
  cursor: pointer;
}

Demek ki ileride liste içinde seçme yapmayı düşünüyoruz. Listeye yeni item eklemek için bir buton koyalım görsele.

  <button @click="addItem">Ekle</button>
  <ul>
    <li v-for="sayı in sayılar" :key="sayı">

addItem metodu tanımlı değil, methods bölümüne ekleyeceğiz. Antreman olsun listede rastgele bir yere 1-100 arası rastgele bir sayı ekleyelim.

src/App.vue

  methods: {
    addItem() {
      const num = Math.floor(Math.random() * 100 + 1)
      const ind = Math.floor(Math.random() * (this.sayılar.length + 1))
      this.sayılar.splice(ind, 0, num)
    },

splice metodu 3 parametreli kullanıldığında ilk parametre silinecek item'ların başladığı index, ikinci parmetre silinecek item sayısı ki burada sıfır verilmiş , item silinmeyecek. Üçüncü parametre de varsa bu index noktasına eklenecek item değeri olur. (this.sayılar.length + 1) ise liste sonuna ekleyebilmeliyiz yani length + 1 olacabilmeli index

Şimdi üzerine tıklanan item listeden silinsin. Bunu yapabilmek için item'ın index değerini bilmemiz gerekiyor. İlk başlarda v-for anlatırken index değerini nasıl alacağımızı göstermiştik.

src/App.vue

    <li v-for="(sayı, index) in sayılar" :key="sayı"
        @click="removeItem(index)"
    >
      {{ sayı }}
    </li>

Metodu tanımlayalım

  methods: {
    removeItem(index) {
      this.sayılar.splice(index, 1)
    },

Bu sefer splice metodunu sadece silme amaçlı kullandık. Test edip çalışmasını görelim.

Eh, liste kodumuz beklendiği gibi çalışıyor , sıra geldi animasyona. Fade animasyon yapalım listeye eklenen ve çıkan oldukça. İlk adım li elemanını transition-group komponenti içine almak. 

src/App.vue

  <ul>
    <transition-group>
      <li v-for="(sayı, index) in sayılar" :key="sayı"
          @click="removeItem(index)"
      >
        {{ sayı }}
      </li>
    </transition-group>
  </ul>

transition ve transition-group komponentleri çalıştırma bakımından pek farklı değil. Farkı transition komponenti var-yok içindeki tüm elemanlara etki ederken, transition-group komponenti iterasyon içindeki elemanlara ayrı ayrı çalışır. Bir de transition-group komponenti mode özelliği almaz (in-out vardı ya). Şimdi name özelliği ile fade animasyonu aktif edelim. 

    <transition-group name="fade">
      <li v-for="(sayı, index) in sayılar" :key="sayı"

Bu kadar. fade-enter-from , fade-enter-active ve fade-leave-to CSS değerleri zaten önceden tanımlamıştık. Animasyon çalışmaya başlayacaktır.

Test Ettiğimizde göreceğimiz bir şey var. Butona bastığımızda listede pat diye yer açılıyor, sonra yeni eleman animasyon ile geliyor. Bu yer açmayı da kaydırarak yapmak için fade-move class adını ekleriz.

src/App.vue

.fade-leave-to {
  transition: all 1.5s linear;
  opacity: 0;
}

.fade-move {
  transition: all 1s linear;
}

Sayfayı yenileyelim. Butona bastıkça arada yer açmak için artık sonraki elemanlar kayarak yeni yerine gidiyor. Ama listeden bir değeri sildiğimizde kaymayı göremiyoruz. *-move class değeri yeni eleman eklenirken diğer elemanlar daha önce hareket ederek boşluk açtığı için çalışıyor. Ama silinirken eleman animasyon sonunda yok oluyor ve sayfada aniden bir boşluk oluşuyor diğer elemanlar kaymıyor. Kaymayı sağlamanın en basit yolu elemanlara silinme olurken absolute position değeri vermek. Bu amaçla fade-leave-active class değerini kullanırız.

.fade-move {
  transition: all 1s linear;
}

.fade-leave-active {
  position: absolute;
}

Silinmekte olan elemanın pozisyonunu absolute yapınca eleman yapısından kendini ayırır. Bunun sonucu boşalan yere diğer elemanların kayması animasyonu aktif olur. 




Vue'de Animate.css Kullanmak

Animate.css çok kullanılan bir CSS animasyon kütüphanesi. Web sitesini incelerseniz çok ilginç animasyonları hazır olarak veriyor. Bunları Vue uygulamamızda kolayca kullanabiliriz. Ancak Animate.css class isimleri bizim Vue'de animasyonda kullandığımız class isimleriyle uyuşmuyor. Bunu transition komponentinin özelliklerinde class isimlerinin Animate.css karşılıklarını girerek sağlayabiliriz. 

Animate.css web sayfasında animasyonları test edip beğendiğimizin class isimlerini kopyalayabiliriz. Örnek olarak flipInX ve flipOutX kullanmak istiyoruz diyelim. Animate.css gereken animasyonu ayarlar, biz sadece enter-active-class ve leave-active-class değerlerini vereceğiz. 

src/App.vue

    <transition-group name="fade"
      enter-active-class="animate__animated animate__flipInX"
      leave-active-class="animate__animated animate__flipOutX"
    >
      <li v-for="(sayı, index) in sayılar" :key="sayı"
          @click="removeItem(index)"
      >
        {{ sayı }}
      </li>
    </transition-group>

Tabii ki Animate.css dosyasını da sayfamıza dahil etmeliyiz. index.html dosyamızın head kısmına ekleyelim

public/index.html

    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" />
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>

Bu kadar basit. Sayfayı yenileyip test edersek animasyonı göreceğiz. Ama mesela animasyon süresini değiştirmek istersek. style bölümünde aynı class adıyla değerini değiştirebiiliriz. 

src/App.vue

<style>
.animate__animated {
  animation-duration: 1.5s;
}

Standart süre kütüphanede 1 saniye olarak tanımlıdır. Bu şekilde stil üzerine yazarak 1.5 saniye olarak değiştiridik. Bir de dikkat edersek listeden eleman silinirken kayma yine olmamaya başladı. leave-active class adına müdahale edince gitti bizim position: absolute

<style>
.animate__flipOutX {
  position: absolute;
}

Benzer şekillerde başka animasyon kütüphaneleri de kullanılabilir. 

Öğrenecek konuların sonuna geldik gibi. Bundan sonra örnek gelişmiş uygulamalar yapacağız inşallah. Sonraki yazılarda buluşmak ümidiyle , kalın sağlıcakla..





Hiç yorum yok:

Yorum Gönder