Skip to content

Instantly share code, notes, and snippets.

@thisismatu
Created January 11, 2025 11:42
Show Gist options
  • Save thisismatu/e4c1bf0fddf7e20bce0181e10311033a to your computer and use it in GitHub Desktop.
Save thisismatu/e4c1bf0fddf7e20bce0181e10311033a to your computer and use it in GitHub Desktop.
Simple viewport awrare tooltip in Svelte 5
<script lang="ts">
import DOMPurify from 'dompurify';
import { onMount, type Snippet } from 'svelte';
const { content, children }: { content: string; children?: Snippet } = $props();
let triggerEl: HTMLSpanElement | undefined = undefined;
let isHovered = $state(false);
let tooltipWidth = $state(0);
let tooltipHeight = $state(0);
let tooltipX = $state(0);
let tooltipY = $state(0);
function updatePosition() {
if (triggerEl === undefined) return;
const { top, bottom, left, width } = triggerEl.getBoundingClientRect();
const offset = 4;
const aboveTrigger = top - tooltipHeight - offset;
const belowTrigger = bottom + offset;
tooltipX = left + width / 2 - tooltipWidth / 2;
tooltipY = aboveTrigger > 0 ? aboveTrigger : belowTrigger;
}
function onEnter() {
updatePosition();
isHovered = true;
}
function onLeave() {
updatePosition();
isHovered = false;
}
$effect(() => {
updatePosition();
});
onMount(() => {
document.addEventListener('scroll', onLeave, true);
return () => {
document.removeEventListener('scroll', onLeave, true);
};
});
</script>
<span
role="button"
bind:this={triggerEl}
onmouseenter={onEnter}
onmouseleave={onLeave}
onfocusin={onEnter}
onfocusout={onLeave}
tabindex="0"
>
{@render children?.()}
</span>
{#if isHovered}
<div
role="tooltip"
bind:offsetWidth={tooltipWidth}
bind:offsetHeight={tooltipHeight}
style="top: {tooltipY}px; left: {tooltipX}px;"
>
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html DOMPurify.sanitize(content)}
</div>
{/if}
<style>
span {
cursor: default;
position: relative;
}
div {
position: fixed;
width: fit-content;
max-width: 18rem;
height: fit-content;
white-space: pre-wrap;
padding: 0.5rem 0.75rem;
background-color: var(--color-gray-9);
color: var(--color-gray-1);
font-size: var(--text-xs);
font-weight: var(--font-regular);
line-height: var(--leading-normal);
border-radius: var(--radius-sm);
box-shadow: var(--shadow-md);
pointer-events: none;
z-index: 10;
opacity: 0;
animation: 0.15s fadeIn 0.3s ease forwards;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment