Framer Motion ile Animasyona Giriş

SHFT
10 min readFeb 3, 2024

Framer Motion, gerek kullanım kolaylığı gerekse istenilen çoğu animasyonun yapılabilmesi nedeniyle birçok developer tarafından kullanılan popüler bir animasyon kütüphanesidir.

Neden Framer Motion Kullanmalıyım?

React uygulamalarınızı kolayca hareketli ve kullanıcıyı çeken hale getirmek için animasyonlar çokça kullanılıyor. Bu animasyonları yapmak için birçok tercihiniz var fakat hem kullanım kolaylığı hem esnekliği hem de topluluk desteğiyle Framer-Motion oldukça öne çıkıyor. Aynı zamanda üzerine gelme efektinden tutun ekranda göründüğünde X olayı gerçekleşsin gibi birçok kurguyu framer-motion kullanarak gerçekleştirebilirsiniz.

Kurulum

Framer Motion kurulumu oldukça basit. Tek yapmanız gereken React
projenize, framer-motion paketini eklemek.

yarn add framer-motion
# veya
npm install framer-motion

Kurulum işlemi tamamlandıktan sonra framer-motion kullanmaya başlayabilirsiniz.

Kullanım

Framer Motion’da en çok kullanacağınız element motion elementi. Bu element div’e ve diğer html özelliklerinde framer motion özelliklerini eklemek için kullanılmakta. Bunu kullanmak için kodunuza, motion elementini framer-motion paketinden import edin.

İlk yapacağımız basit animasyon girişte opaklığı sıfır olan ve 0.5 saniye sonra opaklığı bir olan animasyon yapalım.

import React from 'react';

import { motion } from "framer-motion";

const Home: React.FC = () => {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
className="size-32 bg-[#bfe031]"
/>
);
}

export default Home;

⚠️ Eğer projenizde veya gündelik yaşamınızda NextJS kullanmıyor, React kullanıyorsanız herhangi bir hata almazsınız ve aşağıdaki işlemleri yapmaya gerek duymazsınız.

Yukarıdaki bileşen bir NextJS projesinin Home sayfasında yaptığımız örnek bir animasyon fakat bunu kaydedip, uygulamaya baktığımızda aşağıdaki hatayı göreceğiz.

Server Error Error: (0 , react__WEBPACK_IMPORTED_MODULE_0__.createContext) is not a function hatası

Bu hatanın sebebi NextJS 14+ ile eğer context, state, effect gibi özellikler kullanacaksak bu özellikleri kullandığımız sayfanın başına “use client” yazmalıyız. Fakat sayfalarda “use client” kullanmaktan kaçınmalıyız. Bu durumda önümüzde 2 seçenek çıkıyor.

1. Local Component yapmak:
Burada yapacağımız animasyonu “Animation.tsx” olarak başka bir komponente aktarıp, bu komponentin en üstüne “use client” yazmak.

Örnek olarak;

"use client";

import React from "react";

import { motion } from "framer-motion";

const AnimationBox: React.FC = () => {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
className="size-32 bg-[#bfe031]"
/>
);
};

export default AnimationBox;

2. “use client” ile birlikte export etmek
motionElements.ts adında bir dosya açın. Bu dosyadan “use client” olarak exportlar alacağız.

"use client";

import { motion } from "framer-motion";

export const MotionDiv = motion.div;
export const MotionSpan = motion.span;
export const MotionP = motion.p;
export const MotionImg = motion.img;
export const MotionH1 = motion.h1;
export const MotionH2 = motion.h2;
export const MotionH3 = motion.h3;
export const MotionH4 = motion.h4;
export const MotionH5 = motion.h5;
export const MotionH6 = motion.h6;
export const MotionA = motion.a;
export const MotionButton = motion.button;
export const MotionInput = motion.input;
export const MotionLabel = motion.label;
export const MotionSelect = motion.select;
export const MotionOption = motion.option;
export const MotionTextarea = motion.textarea;
export const MotionForm = motion.form;
export const MotionSvg = motion.svg;
export const MotionPath = motion.path;
export const MotionRect = motion.rect;
export const MotionCircle = motion.circle;
export const MotionEllipse = motion.ellipse;
export const MotionPolygon = motion.polygon;
export const MotionPolyline = motion.polyline;
export const MotionLine = motion.line;
export const MotionG = motion.g;
export const MotionDefs = motion.defs;

Bu şekilde export ettiğimiz elementler artık client component olarak dışarı aktarılmış olacak ve kullandığımız yerlerde artık MotionDiv olarak kullanabilir hale geleceğiz. O halde kodumuzu aşağıdaki hale getirebiliriz.

import { MotionDiv } from "./components/MotionElemets";

const Home: React.FC = () => {
return (
<MotionDiv
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
className="size-32 bg-[#bfe031]"
/>
);
}

export default Home;

Ekranda göreceğiniz çıktı:

💡 Not: Bu şekilde hata veren bazı kütüphanelerde de bu yolu tercih edebilirsiniz. Örneğin; Boostrap gibi kütüphanelerde server componentslerle uyumlu olmadığı için, burada kullanacağınız özellikleri de yaptığınız dosyadan dışarı aktarıp kullanımınızı daha kolay hale getirecektir.

Peki nedir bu “initial” , “animate” ve “transition”?

Initial adından da anlaşılacağı gibi komponentin varsayılan görünümünü belirler, animate ise o animasyonun sonunda ne olacağını belirlediğimiz alan. Bu initial ve animate’e ek olarak exit adında bir özellik daha var. Bu özellik ise çıkış durumunda yapılacak animasyonu belirler. Transition ise en çok seveceğiniz parametrelerden biri.

transition={{
duration: 0.5,
bounce: 0.5,
type: "spring",
damping: 10,
delay: 2,
when: "beforeChildren",
delayChildren: 2,
staggerChildren: 0.5,
...
}}

Transition yukarıdaki gibi çok fazla özellik barındırmakta. Burada animasyonun pürüzsüzlüğünü, hangi durumlarda olacağını, eğer bir liste ise her liste elemanlarının index değerine göre geç gelmesi gibi birçok detayı burada ayarlayabilirsiniz.

Önerilen Kullanım Şekilleri

Bazen initial, animate, exit gibi özelliklerimiz çok uzun olabiliyor. Bu durumda elemanlarınızın karmaşık olmaması, kod tekrarını azaltmanız için önerimiz var. Yapacağınız initial, animate, exit gibi özellikleri başka bir .ts dosyasında yapıp oradan içeri aktarabilirsiniz.

      <MotionDiv
transition={{
duration: 0.5,
repeat: Infinity,
repeatType: "reverse",
repeatDelay: 1,
}}
initial={{
opacity: 0,
borderRadius: 0,
scale: 0,
backgroundColor: "#fff",
}}
animate={{
opacity: 1,
borderRadius: 100,
scale: 2,
backgroundColor: "#bfe031",
}}
className="size-32 bg-[#bfe031]"
/>

İlk olarak variants.ts adında bir dosya açalım.

export const firstAnimationVariant = {
initial: {
opacity: 0,
borderRadius: 0,
scale: 0,
backgroundColor: "#fff",
},
animate: {
opacity: 1,
borderRadius: 100,
scale: 1,
backgroundColor: "#bfe031",
},
};

Sonrasında ise kodumuzu aşağıdaki hale getirebiliriz.

import React from 'react'

import { MotionDiv } from "./components/MotionElemets";
import { firstAnimationVariant } from "./variants";

const Home: React.FC = () => {
return (
<>
<div className="flex flex-col items-center justify-center min-h-screen py-2">
<MotionDiv
transition={{
duration: 0.5,
repeat: Infinity,
repeatType: "reverse",
repeatDelay: 1,
}}
variants={firstAnimationVariant}
initial="initial"
animate="animate"
className="size-32 bg-[#bfe031]"
/>
</div>
</>
);
}

💡 Variant içinde belirlediğiniz objenin keylerini initial ve animate kısımlarına aktarmayı unutmayın.

Ekranda göreceğiniz animasyon aşağıdaki gibi olacak:

Gelelim olayları biraz daha derinleştirmeye. Şimdi birden fazla çözmemiz gereken senaryolara ve bu senaryolar da çözüm yolları aramaya bakalım.

Senaryo 1: Exit animasyonu yapılması gereken bir ögenin DOM’dan silinmesi.

Örnek senaryomuzda Frontend Developer olarak çalışan çoğu kişinin aklına gelecek bir örnek vereceğiz. Bir modalın açılışında animasyon yapabilirken, modalın kapatılmasında animasyon yapamamamız. Gelin bu örneği çözen anahtar özelliği sizinle tanıştırayım. “AnimatePresence”!

Hemen bu konuyu daha iyi pekiştirebilmek için verdiğimiz senaryoya uygun bir kod yazalım.

"use client";

import React, { useState } from "react";

import { MotionDiv } from "./components/MotionElemets";

const Home: React.FC = () => {
const [isOpen, setIsOpen] = useState(false);

return (
<div className="flex flex-col items-center justify-center min-h-screen py-2">
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded"
onClick={() => setIsOpen(true)}
>
Modalı Aç
</button>

{isOpen && (
<MotionDiv
variants={{
hidden: { opacity: 0, scale: 0 },
visible: { opacity: 1, scale: 1 },
exit: { opacity: 0, scale: 0 },
}}
initial="hidden"
animate="visible"
exit="exit"
className="bg-white rounded-lg p-4"
>
<div className="flex flex-col items-start justify-center">
<div className="w-full flex items-center justify-between border-b pb-2 mb-2 border-gray-200">
<h2 className="text-xl font-medium text-gray-900">
Modal Başlığı
</h2>
<button
className="text-gray-400 hover:text-gray-500"
onClick={() => setIsOpen(false)}
>
X
</button>
</div>

<p className="text-md text-gray-500 max-w-lg mb-4">
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Voluptates, quos.
</p>

<div className="flex justify-end w-full">
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded"
onClick={() => setIsOpen(false)}
>
Kapat
</button>
</div>
</MotionDiv>
</div>
)}
</div>
);
}

export default Home;

Gördüğünüz videoda girişte çok güzel bir animasyon var fakat exit animasyonum olmasına rağmen domdan silindiği için bu animasyonu gerçekleştirmiyor. Hadi sihirli elemanımız AnimatePresence’ı kullanalım Tek yapmamız gereken condition eklediğimiz yeri <AnimatePresence> ile sarmak.

"use client";

import React, { useState } from "react";

import { AnimatePresence } from "framer-motion";

import { MotionDiv } from "./components/MotionElemets";

const Home: React.FC = () => {
const [isOpen, setIsOpen] = useState(false);

return (
<div className="relative">
<div className="flex flex-col items-center justify-center min-h-screen py-2">
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded"
onClick={() => setIsOpen(true)}
>
Modalı Aç
</button>
<AnimatePresence>
{isOpen && (
<MotionDiv
key="modal"
variants={{
hidden: { opacity: 0, scale: 0 },
visible: { opacity: 1, scale: 1 },
exit: { opacity: 0, scale: 0 },
}}
initial="hidden"
animate="visible"
exit="exit"
className="bg-white rounded-lg p-4 absolute transform -translate-x-1/2 -translate-y-1/2"
>
<div className="flex flex-col items-start justify-center">
<div className="w-full flex items-center justify-between border-b pb-2 mb-2 border-gray-200">
<h2 className="text-xl font-medium text-gray-900">
Modal Başlığı
</h2>
<button
className="text-gray-400 hover:text-gray-500"
onClick={() => setIsOpen(false)}
>
X
</button>
</div>

<p className="text-md text-gray-500 max-w-lg mb-4">
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Voluptates, quos.
</p>

<div className="flex justify-end w-full">
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded"
onClick={() => setIsOpen(false)}
>
Kapat
</button>
</div>
</div>
</MotionDiv>
)}
</AnimatePresence>
</div>
</div>
);
}

export default Home;

Bu kodun çıktısını da aşağıdaki gibi görebilirsiniz.

AnimationPresence ile bu senaryomuzu başarıyla çözdük! 🚀

Senaryo 2: Bir listem var ve bu listedeki elemanların animasyonlarının tek tek animasyon olmasını istiyorum fakat animasyonlar tek seferde gerçekleşiyor.

Hadi gelin bu problemi örnekleyen bir kod yazalım!

"use client";

import React from 'react';

import { motion } from "framer-motion";

const Home: React.FC = () => {
return (
<div className="flex flex-col items-center justify-center min-h-screen py-2">
<ul className="grid grid-cols-2 grid-rows-2 gap-4">
{Array.from({ length: 4 }).map((_, i) => (
<motion.li
key={i}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
className="size-4 bg-blue-500 rounded-full"
/>
))}
</ul>
</div>
);
}

export default Home;

Yukarıdaki kodumuzu çalıştırdığınızda aşağıdaki gibi görünüm elde edeceksiniz. Gördüğünüz gibi tüm noktalarda aynı anda animasyon gerçekleşiyor.

Fakat bizim istediğimiz bu noktaların belirli bir süre aralıkla gerçekleşmesini sağlamak. Hadi gelin bunu nasıl çözebiliriz ona odaklanalım.

"use client";

import React from 'react';

import { motion } from "framer-motion";

const Home: React.FC = () => {
return (
<div className="flex flex-col items-center justify-center min-h-screen py-2">
<ul className="grid grid-cols-2 grid-rows-2 gap-4">
{Array.from({ length: 4 }).map((_, i) => (
<motion.li
key={i}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5, delay: i * 0.1 }}
className="size-4 bg-blue-500 rounded-full"
/>
))}
</ul>
</div>
);
}

export default Home;

Daha önce de bahsettiğim gibi transition özelliğinde birçok şey yapabiliyorsunuz bunlardan biri map döngüsüne soktuğunuz dataların keylerini istediğimiz gibi kullanabiliyoruz. Yukarıdaki kodda delay’ı index’ten çıkan değerle çarparsak istediğimiz görünümü elde edebiliriz.

Bu şekilde istediğimiz animasyonu elde etmiş olduk!

3. Senaryo: “Ben üzerine gelme veya basılı tutma animasyonu yapmak istiyorum” diyenler için.

Framer Motion geliştiricileri bu konuda da bizi yalnız bırakmamış ve whileTap, whileFocus, whileHover gibi propslar eklemize olanak sağlamışlar. Gelin bunlara da küçük küçük göz atalım.

“whileHover” Kullanımı

"use client";

import React from 'react';

import { motion } from "framer-motion";

const Home: React.FC = () => {
return (
<div className="relative">
<div className="flex flex-col items-center justify-center min-h-screen py-2">
<motion.button
whileHover={{ scale: 1.1 }}
className="bg-blue-500 text-white font-bold py-2 px-4 rounded"
>
Button
</motion.button>
</div>
</div>
);
}

export default Home;

Yukarıdaki kodun çalışma görüntüsü

“whileTap” Kullanımı

"use client";

import React from 'react';

import { motion } from "framer-motion";

const Home: React.FC = () => {
return (
<div className="flex flex-col items-center justify-center min-h-screen py-2">
<motion.button
whileTap={{ scale: 1.1 }}
className="bg-blue-500 text-white font-bold py-2 px-4 rounded"
>
Button
</motion.button>
</div>
);
}

export default Home;

Yukarıdaki kodun çıktısı da aşağıdaki gibi olacaktır.

Son olarak Framer Motion’ın avantajları

  • Her şey sizin kontrolünüzde: Framer Motion kullanarak yapmamız gereken şeyde kontrol sizde! Framer Motion sadece sizin işinizi kolaylaştıracak her türlü aracı veriyor. Bunun dışında sizde istediğiniz şekilde yapı kurup işlemlerinizi yapabilirsiniz.
  • Öğrenmesi zevkli: Çoğu şeyleri öğrenmenin başları sıkıcı ve bunaltıcı, sonlara doğru ise zevkli hale gelmekte. Fakat Framer Motion’da işler tam olarak böyle yürümüyor. Başından sonuna kadar tüm işlemlerinizde, tüm animasyonlarınızda keyif alıyorsunuz. Derin öğrenme yaptıkça da keyfiniz daha fazla artıyor.
  • Dökümantasyon ve Topluluk: Kendi sitesi ve birçok örnek sitede birden fazla detaylı örnek bulabilirsiniz ve aynı zamanda gerek Medium’da gerek başka blog sitelerinde Framer Motion hakkında bir çok detay var.
  • React ile tam uyumlu: React’taki çoğu özellik ile tam uyumlu! Yukarıdaki örneklerde de gördüğün gibi çok kolay bir şekilde kurup hemen kullanmaya başlayabilirsin.
  • Performans odaklı: Framer Motion, yüksek performanslı animasyonlar sunar. Web uygulamanızın hızını ve performansını olumsuz etkilemeden etkileyici animasyonlar oluşturmanıza olanak tanır. Bu da kullanıcı deneyimini artırır.
  • Mobil uyumlu: Framer Motion, mobil cihazlarda da mükemmel performans gösterir. Böylece, responsive tasarımlarda animasyonlarınızın her cihazda sorunsuz çalıştığından emin olabilirsiniz.

Framer Motion hakkında daha detaylı bilgi almak için dökümanını ziyaret edebilirsiniz.

Okuduğunuz için teşekkürler, gelecek yazılarımızda görüşmek üzere 🙌

Hüsnü Lübnan | Front End Developer

--

--