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
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
/** @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. |
Get beautiful, hand-crafted templates and components with Aceternity UI Pro
Professional, beautiful and elegant templates for your business. Get the best component packs and templates with Aceternity UI Pro.
The work that Manu did laid the foundation of online education that we provide today. The website he built for us is used by thousands of students every day. He took the requirements and built the ...
Jagvinder Kour
Chairperson at Golden Bells Academy