Tailwind CSS and Framer Motion are a great way to build modern websites.
Visit Aceternity UI for amazing Tailwind and Framer Motion components.
Installation
Install util dependencies
npm i framer-motion clsx tailwind-merge
Install component dependencies
npm i @radix-ui/react-hover-card qss
Add util file
lib/utils.ts
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Add microlink
in next.config
file
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
domains: [
"api.microlink.io", // Microlink Image Preview
],
},
};
module.exports = nextConfig;
Copy the source code
components/ui/link-preview.tsx
"use client";
import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
import Image from "next/image";
import { encode } from "qss";
import React from "react";
import {
AnimatePresence,
motion,
useMotionValue,
useSpring,
} from "framer-motion";
import Link from "next/link";
import { cn } from "@/lib/utils";
type LinkPreviewProps = {
children: React.ReactNode;
url: string;
className?: string;
width?: number;
height?: number;
quality?: number;
layout?: string;
} & (
| { isStatic: true; imageSrc: string }
| { isStatic?: false; imageSrc?: never }
);
export const LinkPreview = ({
children,
url,
className,
width = 200,
height = 125,
quality = 50,
layout = "fixed",
isStatic = false,
imageSrc = "",
}: LinkPreviewProps) => {
let src;
if (!isStatic) {
const params = encode({
url,
screenshot: true,
meta: false,
embed: "screenshot.url",
colorScheme: "dark",
"viewport.isMobile": true,
"viewport.deviceScaleFactor": 1,
"viewport.width": width * 3,
"viewport.height": height * 3,
});
src = `https://api.microlink.io/?${params}`;
} else {
src = imageSrc;
}
const [isOpen, setOpen] = React.useState(false);
const [isMounted, setIsMounted] = React.useState(false);
React.useEffect(() => {
setIsMounted(true);
}, []);
const springConfig = { stiffness: 100, damping: 15 };
const x = useMotionValue(0);
const translateX = useSpring(x, springConfig);
const handleMouseMove = (event: any) => {
const targetRect = event.target.getBoundingClientRect();
const eventOffsetX = event.clientX - targetRect.left;
const offsetFromCenter = (eventOffsetX - targetRect.width / 2) / 2; // Reduce the effect to make it subtle
x.set(offsetFromCenter);
};
return (
<>
{isMounted ? (
<div className="hidden">
<Image
src={src}
width={width}
height={height}
quality={quality}
layout={layout}
priority={true}
alt="hidden image"
/>
</div>
) : null}
<HoverCardPrimitive.Root
openDelay={50}
closeDelay={100}
onOpenChange={(open) => {
setOpen(open);
}}
>
<HoverCardPrimitive.Trigger
onMouseMove={handleMouseMove}
className={cn("text-black dark:text-white", className)}
href={url}
>
{children}
</HoverCardPrimitive.Trigger>
<HoverCardPrimitive.Content
className="[transform-origin:var(--radix-hover-card-content-transform-origin)]"
side="top"
align="center"
sideOffset={10}
>
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0, y: 20, scale: 0.6 }}
animate={{
opacity: 1,
y: 0,
scale: 1,
transition: {
type: "spring",
stiffness: 260,
damping: 20,
},
}}
exit={{ opacity: 0, y: 20, scale: 0.6 }}
className="shadow-xl rounded-xl"
style={{
x: translateX,
}}
>
<Link
href={url}
className="block p-1 bg-white border-2 border-transparent shadow rounded-xl hover:border-neutral-200 dark:hover:border-neutral-800"
style={{ fontSize: 0 }}
>
<Image
src={isStatic ? imageSrc : src}
width={width}
height={height}
quality={quality}
layout={layout}
priority={true}
className="rounded-lg"
alt="preview image"
/>
</Link>
</motion.div>
)}
</AnimatePresence>
</HoverCardPrimitive.Content>
</HoverCardPrimitive.Root>
</>
);
};
Static Image Preview Example
Visit Aceternity UI and for amazing Tailwind and Framer Motion components.
I listen to this guy and I watch this movie twice a day
This example shows images being generated
from a url
AND images being fetched
from local folder with a different url
for link.
Props
Prop Name | Type | Default Value | Description |
---|---|---|---|
children | React.ReactNode | None | The content to be displayed inside the link component. |
url | string | None | The URL for the link and for generating the preview image if isStatic is false. |
className | string | None | Additional CSS classes to apply to the link component. |
width | number | 200 | Width of the preview image. |
height | number | 125 | Height of the preview image. |
quality | number | 50 | Quality of the preview image. |
layout | string | "fixed" | Layout type of the image, affects how the image resizes. |
isStatic | boolean | false | Determines if the image source is static or dynamically generated from the URL. |
imageSrc | string | "" | Source of the image when isStatic is true. If isStatic is false, this prop should not be used. |