Layout Animation — Web Animation

Layout Animation Layout animation menganimasi perubahan posisi dan ukuran elemen yang disebabkan oleh perubahan layout (reflow). Ini adalah salah satu animasi p

Layout Animation

Layout animation menganimasi perubahan posisi dan ukuran elemen yang disebabkan oleh perubahan layout (reflow). Ini adalah salah satu animasi paling sulit dilakukan secara manual, tapi Framer Motion membuatnya sangat mudah.

layout Prop

Cukup tambahkan prop layout dan Framer Motion akan menganimasi perubahan layout apapun:

import { motion } from 'motion/react';

function ExpandableCard({ isExpanded }) {
  return (
    <motion.div
      layout
      className={isExpanded ? 'w-full p-8' : 'w-64 p-4'}
      style={{ borderRadius: 12 }}
    >
      <motion.h3 layout="position">Card Title</motion.h3>
      {isExpanded && (
        <motion.p
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
        >
          Extended content here...
        </motion.p>
      )}
    </motion.div>
  );
}

Bagaimana Layout Animation Bekerja

Framer Motion menggunakan teknik FLIP (First, Last, Invert, Play):

  1. First — catat posisi elemen sebelum perubahan
  2. Last — render perubahan, catat posisi baru
  3. Invert — gunakan transform untuk memindahkan elemen kembali ke posisi awal
  4. Play — animasi dari posisi awal ke posisi baru

Semua ini terjadi otomatis — kamu hanya perlu menambahkan layout prop.

layoutId — Shared Layout Animation

layoutId memungkinkan animasi antar komponen yang berbeda tapi berbagi identitas:

function TabBar({ activeTab, tabs }) {
  return (
    <div className="flex gap-2">
      {tabs.map((tab) => (
        <button
          key={tab.id}
          onClick={() => setActiveTab(tab.id)}
          className="relative px-4 py-2"
        >
          {tab.label}

          {activeTab === tab.id && (
            <motion.div
              layoutId="activeTab"
              className="absolute inset-0 bg-blue-100 rounded-lg"
              style={{ zIndex: -1 }}
              transition={{
                type: 'spring',
                stiffness: 400,
                damping: 30,
              }}
            />
          )}
        </button>
      ))}
    </div>
  );
}

Indicator activeTab akan bergerak smooth dari tab satu ke tab lainnya!

List Reordering

import { motion, AnimatePresence } from 'motion/react';

function SortableList({ items }) {
  return (
    <ul>
      <AnimatePresence>
        {items.map((item) => (
          <motion.li
            key={item.id}
            layout
            initial={{ opacity: 0, scale: 0.8 }}
            animate={{ opacity: 1, scale: 1 }}
            exit={{ opacity: 0, scale: 0.8 }}
            transition={{
              layout: { type: 'spring', stiffness: 350, damping: 25 },
              opacity: { duration: 0.2 },
            }}
          >
            {item.name}
          </motion.li>
        ))}
      </AnimatePresence>
    </ul>
  );
}

Saat list di-sort ulang, setiap item akan bergerak smooth ke posisi barunya.

Expand / Collapse Pattern

function Accordion({ items }) {
  const [openId, setOpenId] = useState(null);

  return (
    <div className="space-y-2">
      {items.map((item) => (
        <motion.div
          key={item.id}
          layout
          className="bg-white rounded-lg overflow-hidden"
        >
          <motion.button
            layout="position"
            className="w-full p-4 text-left font-semibold"
            onClick={() =>
              setOpenId(openId === item.id ? null : item.id)
            }
          >
            {item.title}
          </motion.button>

          <AnimatePresence>
            {openId === item.id && (
              <motion.div
                initial={{ opacity: 0, height: 0 }}
                animate={{ opacity: 1, height: 'auto' }}
                exit={{ opacity: 0, height: 0 }}
                className="px-4 pb-4"
              >
                {item.content}
              </motion.div>
            )}
          </AnimatePresence>
        </motion.div>
      ))}
    </div>
  );
}

Shared Layout Animation (Hero Pattern)

Animasi dari thumbnail ke detail view:

// Thumbnail grid
function ImageGrid({ images, onSelect }) {
  return (
    <div className="grid grid-cols-3 gap-4">
      {images.map((img) => (
        <motion.div
          key={img.id}
          layoutId={\`image-${img.id}\`}
          onClick={() => onSelect(img)}
          className="cursor-pointer rounded-lg overflow-hidden"
        >
          <img src={img.thumbnail} alt={img.alt} />
        </motion.div>
      ))}
    </div>
  );
}

// Detail view
function ImageDetail({ image, onClose }) {
  return (
    <motion.div
      className="fixed inset-0 flex items-center justify-center"
      onClick={onClose}
    >
      <motion.div
        layoutId={\`image-${image.id}\`}
        className="w-3/4 rounded-xl overflow-hidden"
      >
        <img src={image.full} alt={image.alt} />
      </motion.div>
    </motion.div>
  );
}

Elemen akan beranimasi smooth dari posisi thumbnail ke posisi detail — termasuk ukuran, posisi, dan border-radius!

Tips layout Prop

Layout animation membuat transisi antar state terasa natural dan membantu user memahami hubungan antar elemen.