Search Something...

BlobSlider

Showcases a series of images within a fluidly morphing blob shape, providing a captivating, dynamic visual presentation.

Blob it !

Experience the mesmerizing beauty of dynamic forms, where images flow seamlessly within a continuously morphing blob. This component not only enhances visual engagement but also reflects the fluid nature of our ever-changing perceptions.

First
Second
Third
Fourth

Installation

1Install dependencies

This library primarily relies on these libraries to function.

Text copied
npm i framer-motion clsx tailwind-merge
2Create a lib folder

In your app or src/app create a lib folder.

3Create a utils.ts file

In your lib folder create a utils.ts file.

4Add the function

Copy paste this function in your utils.ts.

Text copied
import { ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); }
5Copy the following code

In your BlobSlider.tsx component

Text copied
"use client"; import { motion } from "framer-motion"; import Image from "next/image"; export default function BlobSlider({ imgArray, imgChangeSpeed = 8, blobColor = "#5180e0", }: BlobSliderProps) { const imagesArrayLength = imgArray.length; return ( <div className="relative size-full"> <div className="absolute size-full"> <svg viewBox="0 0 100 100" height={0} width={0}> <defs> <clipPath id="blobClip" clipPathUnits="objectBoundingBox" transform="scale(0.01 0.01)" > <path> <animate attributeName="d" dur="25000ms" repeatCount="indefinite" values={blobValues.join("")} /> </path> </clipPath> </defs> </svg> <div style={{ clipPath: "url(#blobClip)", WebkitClipPath: "url(#blobClip)", backgroundColor: blobColor, }} className="relative size-full " > {imgArray.map((image, imageIndex) => { const imgPos = imageIndex + 1; return ( <ImgSlider key={imageIndex} imageIndex={imageIndex} imagesArrayLength={imagesArrayLength} imgAlt={image.imgAlt} imgSrc={image.imgSrc} imgChangeSpeed={imgChangeSpeed} imgPos={imgPos} /> ); })} </div> </div> <div className="absolute -z-50 size-full blur-md"> <svg viewBox="0 0 100 100" height={0} width={0}> <defs> <clipPath id="blobClip2" clipPathUnits="objectBoundingBox" transform="scale(0.01 0.01)" > <path> <animate attributeName="d" dur="25000ms" repeatCount="indefinite" values={blobValues.join("")} /> </path> </clipPath> </defs> </svg> <div style={{ clipPath: "url(#blobClip2)", WebkitClipPath: "url(#blobClip2)", backgroundColor: blobColor, }} className="relative size-full " > <div style={{ backgroundColor: blobColor, }} className="blur-xl" /> </div> </div> </div> ); } export const ImgSlider = ({ imgPos, imageIndex, imagesArrayLength, imgAlt, imgChangeSpeed, imgSrc, }: ImgSliderProps) => { return ( <motion.div style={{ zIndex: 2 - imgPos, }} animate={{ opacity: [0, 1, 1, 1, 0], transition: { duration: imgChangeSpeed, ease: "easeIn", repeat: Infinity, delay: imgChangeSpeed * imageIndex, repeatDelay: imgChangeSpeed * (imagesArrayLength - 1), }, }} key={imageIndex} className="absolute left-1/2 top-1/2 z-[2] size-full -translate-x-1/2 -translate-y-1/2" > <Image className="object-cover" src={imgSrc} alt={imgAlt} fill /> </motion.div> ); }; const blobValues = [ "M88.5,67Q84,84,67,87Q50,90,33.5,86.5Q17,83,15,66.5Q13,50,17.5,36Q22,22,36,18Q50,14,62.5,19.5Q75,25,84,37.5Q93,50,88.5,67Z;", "M87.5,63.5Q77,77,63.5,81Q50,85,37,80.5Q24,76,12.5,63Q1,50,13,37.5Q25,25,37.5,15.5Q50,6,67.5,10.5Q85,15,91.5,32.5Q98,50,87.5,63.5Z;", "M83.5,63Q76,76,63,87.5Q50,99,37,87.5Q24,76,15.5,63Q7,50,13.5,35Q20,20,35,18Q50,16,66,17Q82,18,86.5,34Q91,50,83.5,63Z;", "M83.5,67Q84,84,67,88Q50,92,37,84Q24,76,14,63Q4,50,11.5,34.5Q19,19,34.5,11.5Q50,4,63,14Q76,24,79.5,37Q83,50,83.5,67Z;", "M85,67Q84,84,67,85.5Q50,87,37.5,81Q25,75,13.5,62.5Q2,50,11.5,35.5Q21,21,35.5,13Q50,5,66.5,11Q83,17,84.5,33.5Q86,50,85,67Z;", "M85.5,61Q72,72,61,77Q50,82,34.5,81.5Q19,81,12,65.5Q5,50,15.5,38Q26,26,38,15Q50,4,64.5,12.5Q79,21,89,35.5Q99,50,85.5,61Z;", "M88,63.5Q77,77,63.5,87Q50,97,38.5,85Q27,73,17,61.5Q7,50,15,36.5Q23,23,36.5,13Q50,3,63,13.5Q76,24,87.5,37Q99,50,88,63.5Z;", "M84.5,61Q72,72,61,78Q50,84,34,83Q18,82,10.5,66Q3,50,15,38.5Q27,27,38.5,20Q50,13,64.5,17Q79,21,88,35.5Q97,50,84.5,61Z;", "M88.5,67Q84,84,67,87Q50,90,33.5,86.5Q17,83,15,66.5Q13,50,17.5,36Q22,22,36,18Q50,14,62.5,19.5Q75,25,84,37.5Q93,50,88.5,67Z;", ]; type BlobSliderProps = { imgArray: { imgSrc: string; imgAlt: string; }[]; imgChangeSpeed?: number; blobShapeChangeTimes?: number; blobShapeChangeSpeed?: number; blobColor?: string; }; type ImgSliderProps = { imgPos: number; imgChangeSpeed: number; imageIndex: number; imagesArrayLength: number; imgSrc: string; imgAlt: string; };
6How to use

Read the code below to understand how to use the component.

Text copied
const imgArray = [ { imgSrc: "/myFolder/firstImage.png", imgAlt: "First Image", }, { imgSrc: "/myFolder/secondImage.png", imgAlt: "Second Image", }, { imgSrc: "/myFolder/thirdImage.png", imgAlt: "Third Image", }, ]; <div className="relative size-96"> <BlobSlider imgArray={imgArray} /> </div>
7Props

All the things you need to know about this component.

PropTypeDescription
imgArrayarrayAn array of objects with two properties (imgSrc : string & imgAlt : string).
imgChangeSpeednumberThe current slider speed.
blobColorstringColor of the blob.
8How to custom

Example of customization.

Text copied
<BlobSlider imgArray={imgArray} imgChangeSpeed={14} blobColor="#fff" />