Why is there a weird stretching animation when clicking on an item in my bookshelf UI using Framer Motion and Tailwind CSS in Next.js?

I’m working on a bookshelf UI in Next.js using Framer Motion for animations and Tailwind CSS for styling. Each book is an interactive li element with hover and click functionality. The problem I’m facing is that when I click on a book, there’s a weird “stretching” animation happening, and I can’t figure out why.

Behavior:

  1. When a book is hovered over, it slightly lifts (translate-y).
  2. When clicked, the selected book expands to show details.
  3. However, on clicking, the book appears to “stretch” or resize unexpectedly before settling into its final state.

Here is the current state of this issue: website

Code:

Here is the relevant code:

// Book.js
import { motion } from "framer-motion";
import Image from "next/image";
import { useState } from "react";

export default function Book({ data, isSelected, onSelect, isAnyHovered, onHover }) {
  const { title, author, route, year } = data;
  const [isHovered, setIsHovered] = useState(false);
  const [imageSize, setImageSize] = useState({ width: 0, height: 0 });

  const handleImageLoad = ({ target }) => {
    setImageSize({ width: target.naturalWidth / 4, height: target.naturalHeight / 4 });
  };

  const getImageClassName = () => {
    let className = "transition-all duration-800";

    if (isHovered) {
      className += " opacity-100 -translate-y-2";
    } else {
      className += " opacity-40 translate-y-0";
    }

    return className;
  };

  return (
    <motion.li
      initial={{ x: -50, opacity: 0 }}
      animate={{ x: 0, opacity: 1 }}
      exit={{ x: 50, opacity: 0 }}
      transition={{ duration: 0.4 }}
      layout
      className="relative flex gap-2 items-end"
    >
      <button onClick={() => onSelect(data)}>
        <Image
          alt={`Book spine of ${title}`}
          width={imageSize.width}
          height={imageSize.height}
          src={`/images/${route}`}
          onLoad={handleImageLoad}
          className={getImageClassName()}
          onMouseEnter={() => {
            setIsHovered(true);
            onHover(data);
          }}
          onMouseLeave={() => {
            setIsHovered(false);
            onHover(null);
          }}
        />
      </button>
      {isSelected && (
        <div className="pr-2">
          <h3 className="text-2xl font-bold">{title}</h3>
          <span>by {author}</span>
          <span>{year}</span>
        </div>
      )}
    </motion.li>
  );
}
// page.js
import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";
import Book from "./Book";

export default function Home() {
  const [books, setBooks] = useState([]);
  const [selectedBook, setSelectedBook] = useState(null);
  const [hoveredBook, setHoveredBook] = useState(null);

  const handleSelectBook = (book) => {
    setSelectedBook(selectedBook === book ? null : book);
  };

  return (
    <ul className="flex relative overflow-x-scroll">
      <AnimatePresence>
        {books.map((book) => (
          <Book
            key={book.id}
            data={book}
            isSelected={selectedBook === book}
            onSelect={handleSelectBook}
            isAnyHovered={hoveredBook !== null}
            onHover={setHoveredBook}
          />
        ))}
      </AnimatePresence>
    </ul>
  );
}

Observations:

  • The motion.li element uses layout from Framer Motion, which might be
    causing the stretching effect.
  • The Image component dynamically
    calculates its size with naturalWidth and naturalHeight. Could this
    recalculation be contributing to the issue?
  • Tailwind’s transition and
    transform classes (translate-y, transition-all, etc.) might be
    conflicting with Framer Motion’s layout.

What I’ve Tried:

  1. Removing layout from motion.li—but this breaks the animations.
  2. Disabling Tailwind transition-all classes—this did not fix the issue.
  3. Hardcoding the Image width and height instead of calculating them dynamically—this reduced, but did not eliminate, the stretching effect.

Question:

  • Why is this stretching animation happening when a book is clicked?
  • How can I prevent the weird resizing/stretching effect while keeping the animations for hover and select intact?

Any insights into how Tailwind CSS and Framer Motion might be interacting (or conflicting) here would be much appreciated. Let me know if additional context or code is needed!