Hover

A hover effect for a gallery of images

nothing image

Installation

Run the following command

It will create a new file hover.tsx inside the components/gallery/hover.tsx directory.

mkdir -p components/gallery && touch components/gallery/hover.tsx

Paste the code

Open the newly created file and paste the following code:

"use client";
 
import { cn } from "@/lib/utils";
import { AnimatePresence, motion } from "framer-motion";
import { useState } from "react";
 
type Element = {
  id: number;
  bg: string;
  img: string;
};
 
const HoverGallery = () => {
  const [isHovering, setIsHovering] = useState(false);
  const items = [
    {
      elements: [
        {
          id: 1,
          bg: "#22c55e",
          img: "/others/photo-1.jpg",
        },
        {
          id: 2,
          bg: "#ef4444",
          img: "/others/photo-2.jpg",
        },
        {
          id: 3,
          bg: "#eab308",
          img: "/others/photo-3.jpg",
        },
      ],
    },
  ];
 
  const allElements = items.flatMap((column) => column.elements);
 
  const elements = [
    ...allElements,
    { id: 4, bg: "#3b82f6", img: "/others/photo-4.jpg" },
  ];
 
  return (
    <div className="size-full center">
      <motion.div
        onMouseEnter={() => setIsHovering(true)}
        onMouseLeave={() => setIsHovering(false)}
        className="h-[500px] w-[500px] border rounded-[30px] px-4 py-5 relative overflow-hidden"
      >
        <motion.div
          className="w-full h-full gap-2 flex flex-col items-start justify-center"
          layout
          transition={{ duration: 0.5, ease: "easeInOut" }}
        >
          {items.map((column, index) => (
            <motion.div
              className={cn(
                "flex flex-col items-center justify-center gap-10 w-full  h-full"
              )}
              key={index}
              layout
              animate={{
                opacity: isHovering ? 0 : 1,
                willChange: "auto",
              }}
            >
              <div className="rounded-2xl cursor-pointer grid place-items-center flex-[2] w-full">
                {column.elements.map((item, index) => (
                  <Gallery item={item} index={index} key={index} />
                ))}
              </div>
              <motion.div
                className={cn(
                  "rounded-2xl cursor-pointer center overflow-hidden border h-full w-full flex-1"
                )}
                layoutId={`box-4`}
              >
                <img
                  src="/others/photo-4.jpg"
                  alt="nothing image"
                  className="size-full object-cover"
                />
              </motion.div>
            </motion.div>
          ))}
        </motion.div>
        {isHovering && (
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1, willChange: "auto" }}
            transition={{ duration: 0.5, ease: "easeInOut" }}
            className="absolute inset-0 w-full h-full overflow-hidden "
          >
            <AnimatePresence mode="popLayout">
              <motion.div
                className="grid grid-cols-2 gap-4 justify-center items-center h-full w-full p-4"
                animate={{ opacity: 1, y: 0 }}
                transition={{ delay: 0.3 }}
              >
                {elements.map(({ id, bg, img }, index) => (
                  <motion.div
                    key={index}
                    className={cn(
                      "rounded-2xl cursor-pointer center overflow-hidden border h-full"
                    )}
                    layoutId={`box-${id}`}
                  >
                    <img
                      src={img}
                      alt="nothing image"
                      className="size-full object-cover"
                    />
                  </motion.div>
                ))}
              </motion.div>
            </AnimatePresence>
          </motion.div>
        )}
      </motion.div>
    </div>
  );
};
 
const Gallery = (props: { item: Element; index?: number }) => {
  return (
    <motion.div
      className={cn(
        "rounded-2xl center border h-[80%] w-[80%] max-h-[222px] origin-bottom overflow-hidden"
      )}
      layoutId={`box-${props.item.id}`}
      animate={{
        rotate: props.index === 0 ? -12 : props.index === 2 ? 12 : undefined,
      }}
      style={{
        gridRow: 1,
        gridColumn: 1,
        backgroundColor: props.item.bg,
      }}
    >
      <img src={props.item.img} className="size-full object-cover" />
    </motion.div>
  );
};
 
export default HoverGallery;

Credits

Built by Bossadi Zenith