Animated Tooltip

A cool tooltip that reveals on hover, follows mouse pointer.

John Doe
Robert Johnson
Jane Smith
Emily Davis
Tyler Durden
Dora

Installation

Install Dependencies

npm i svelte-motion clsx tailwind-merge

Add util file

src/lib/utils/cn.ts
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
        
export function cn(...inputs: ClassValue[]) {
    return twMerge(clsx(inputs));
}

Copy the source code

src/lib/components/ui/AnimatedTooltip/AnimatedTooltip.svelte
<script lang="ts">
	import { Motion, useTransform, AnimatePresence, useMotionValue, useSpring } from 'svelte-motion';

	export let items: {
		id: number;
		name: string;
		designation: string;
		image: string;
	}[];

	let hoveredIndex: number | null = null;
	const springConfig = { stiffness: 100, damping: 5 };
	const x = useMotionValue(0); // going to set this value on mouse move
	// rotate the tooltip
	const rotate = useSpring(useTransform(x, [-100, 100], [-45, 45]), springConfig);
	// translate the tooltip
	const translateX = useSpring(useTransform(x, [-100, 100], [-50, 50]), springConfig);
	const handleMouseMove = (event: MouseEvent) => {
		// @ts-ignore
		const halfWidth = event.target?.offsetWidth / 2;
		x.set(event.offsetX - halfWidth); // set the x value, which is then used in transform and rotate
	};
</script>

<div class="group flex flex-row">
	{#each items as item, idx (item.name)}
		<div
			class="relative -mr-4"
			on:mouseenter={() => (hoveredIndex = item.id)}
			on:mouseleave={() => (hoveredIndex = null)}
		>
			<AnimatePresence show={true}>
				{#if hoveredIndex === item.id}
					<Motion
						let:motion
						initial={{ opacity: 0, y: 20, scale: 0.6 }}
						animate={{
							opacity: 1,
							y: 0,
							scale: 1,
							transition: {
								type: 'spring',
								stiffness: 260,
								damping: 10
							}
						}}
						exit={{ opacity: 0, y: 20, scale: 0.6 }}
						style={{
							translateX: translateX,
							rotate: rotate,
							whiteSpace: 'nowrap'
						}}
					>
						<div
							use:motion
							class="absolute -left-1/2 -top-16 z-50 flex translate-x-1/2 flex-col items-center justify-center rounded-md bg-black px-4 py-2 text-xs shadow-xl"
						>
							<div
								class="absolute inset-x-10 -bottom-px z-30 h-px w-[20%] bg-gradient-to-r from-transparent via-emerald-500 to-transparent"
							/>
							<div
								class="absolute -bottom-px left-10 z-30 h-px w-[40%] bg-gradient-to-r from-transparent via-sky-500 to-transparent"
							/>
							<div class="relative z-30 whitespace-nowrap text-base font-bold text-white">
								{item.name}
							</div>
							<div class="whitespace-nowrap text-xs text-white">{item.designation}</div>
						</div>
					</Motion>
				{/if}
			</AnimatePresence>
			<img
				on:mousemove={handleMouseMove}
				height={100}
				width={100}
				src={item.image}
				alt={item.name}
				class="relative !m-0 h-14 w-14 rounded-full border-2 border-white object-cover object-top !p-0 transition duration-500 group-hover:z-30 group-hover:scale-105"
			/>
		</div>
	{/each}
</div>
src/lib/components/ui/AnimatedTooltip/index.ts
import AnimatedTooltip from './AnimatedTooltip.svelte';

export { AnimatedTooltip };

Props

AnimatedTooltip
Prop Type Description
items Array<{id: number, name: string, designation: string, image: string}> An array of objects, each representing an item. Each object in the array should have the following properties: id, name, designation, image