SVG Mask Effect
A mask reveal effect, hover the cursor over a container to reveal what is underneath.
The first rule of MRR Club is you do not talk about MRR
Club. The second rule of MRR Club is you DO NOT talk about MRR Club.
The first rule of MRR Club is you do not talk about MRR Club. The second rule of MRR Club is you DO NOT talk about MRR Club.
<script lang="ts">
import SvgMaskEffect from './SvgMaskEffect.svelte';
</script>
<div class="flex h-[40rem] w-full items-center justify-center overflow-hidden">
<SvgMaskEffect size={10} revealSize={700} className="h-[40rem] border rounded-md">
<p slot="revealText" class="mx-auto max-w-4xl text-center text-4xl font-bold text-slate-800">
The first rule of MRR Club is you do not talk about MRR Club. The second rule of MRR Club is
you DO NOT talk about MRR Club.
</p>
The first rule of{' '}<span class="text-red-500">MRR Club</span> is you do not talk about MRR
Club. The second rule of MRR Club is you DO NOT talk about{' '}<span class="text-red-500"
>MRR Club</span
>.
</SvgMaskEffect>
</div>
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));
}
Add mask in static
folder
static/mask.svg
<svg
width="1298"
height="1298"
viewBox="0 0 1298 1298"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="649" cy="649" r="649" fill="black" />
</svg>
Copy the source code
src/lib/components/ui/SvgMaskEffect/SvgMaskEffect.svelte
<script lang="ts">
import { onMount } from 'svelte';
import { Motion } from 'svelte-motion';
import { cn } from '$lib/utils';
export let size: number | undefined = 10;
export let revealSize: number | undefined = 600;
export let className: string | undefined = undefined;
let isHovered = false;
let mousePosition: { x: number | null; y: number | null } = { x: null, y: null };
let containerRef: HTMLDivElement;
const updateMousePosition = (e: any) => {
const rect = containerRef.getBoundingClientRect();
mousePosition = { x: e.clientX - rect.left, y: e.clientY - rect.top };
};
onMount(() => {
containerRef.addEventListener('mousemove', updateMousePosition);
return () => {
if (containerRef) {
containerRef.removeEventListener('mousemove', updateMousePosition);
}
};
});
$: maskSize = isHovered ? revealSize : size;
</script>
<Motion
let:motion
animate={{
backgroundColor: isHovered ? 'var(--slate-900)' : 'var(--white)'
}}
>
<div use:motion bind:this={containerRef} class={cn('relative h-screen', className)}>
<Motion
let:motion
animate={{
WebkitMaskPosition:
mousePosition.x &&
mousePosition.y &&
maskSize &&
`${mousePosition?.x - maskSize / 2}px ${mousePosition.y - maskSize / 2}px`,
WebkitMaskSize: `${maskSize}px`
}}
transition={{ type: 'tween', ease: 'backOut', duration: 0.1 }}
>
<div
use:motion
class="absolute flex h-full w-full items-center justify-center bg-black text-6xl text-white bg-grid-white/[0.2] [mask-image:url(/mask.svg)] [mask-repeat:no-repeat] [mask-size:40px]"
>
<div class="absolute inset-0 z-0 h-full w-full bg-black opacity-50" />
<div
on:mouseenter={() => {
isHovered = true;
}}
on:mouseleave={() => {
isHovered = false;
}}
class="relative z-20 mx-auto max-w-4xl text-center text-4xl font-bold text-white"
>
<slot />
</div>
</div>
</Motion>
<div class="flex h-full w-full items-center justify-center text-white">
<slot name="revealText" />
</div>
</div>
</Motion>
src/lib/components/ui/SvgMaskEffect/index.ts
import SvgMaskEffect from './SvgMaskEffect.svelte';
export { SvgMaskEffect };
Props
SvgMaskEffect
Prop | Type | Description |
---|---|---|
size | number | undefined | Size of the mask |
revealSize | number | undefined | Hovered over size of the mask |
className | string | undefined | The class name of the child component. |