add animated headings

This commit is contained in:
Enrico Bühler 2024-07-14 14:12:22 +02:00
parent 9676ff9327
commit 1fd4d1cbbe
6 changed files with 101 additions and 16 deletions

View File

@ -0,0 +1,17 @@
<script>
import animateHeading from "@/lib/animate-heading";
const init = () => {
const headings = document.querySelectorAll(".animated-heading");
headings.forEach((e) => {
animateHeading(e);
});
};
init();
document.addEventListener("astro:after-swap", () => {
init();
});
</script>

View File

@ -3,18 +3,25 @@ import { cn } from "@/lib/utils";
import type { HTMLAttributes } from "astro/types"; import type { HTMLAttributes } from "astro/types";
import { type VariantProps, cva } from "class-variance-authority"; import { type VariantProps, cva } from "class-variance-authority";
const heading = cva("max-w-3xl m-[var(--section-heading-margin)]", { const heading = cva(
variants: { "max-w-3xl m-[var(--section-heading-margin)] overflow-hidden",
main: { {
true: "font-bold", variants: {
false: "font-semibold", main: {
}, true: "font-bold animated-heading",
padding: { false: "font-semibold",
true: "py-4", },
false: "p-0", animated: {
true: "animated-heading",
false: "",
},
padding: {
true: "py-4",
false: "p-0",
},
}, },
}, },
}); );
export interface Props export interface Props
extends HTMLAttributes<"h1" | "h2">, extends HTMLAttributes<"h1" | "h2">,
@ -22,22 +29,28 @@ export interface Props
subtitle?: string; subtitle?: string;
} }
const { main = false, padding = true, subtitle } = Astro.props; const {
main = false,
padding = true,
subtitle,
id,
class: propsClass,
} = Astro.props;
const titleClass = cn( const titleClass = cn(
heading({ main, padding }), heading({ main, padding, class: propsClass }),
subtitle ? "mb-[calc(var(--section_heading-margin-bottom)_/_2)]" : "" subtitle ? "mb-[calc(var(--section_heading-margin-bottom)_/_2)]" : "",
); );
--- ---
<div class="z-0 relative"> <div class="z-0 relative">
{ {
main ? ( main ? (
<h1 class={titleClass}> <h1 id={id} class={titleClass}>
<slot /> <slot />
</h1> </h1>
) : ( ) : (
<h2 class={titleClass}> <h2 id={id} class={titleClass}>
<slot /> <slot />
</h2> </h2>
) )

View File

@ -4,6 +4,7 @@ import Header from "@/components/Layout/Header.astro";
import "@/styles/global.css"; import "@/styles/global.css";
import "keen-slider/keen-slider.min.css"; import "keen-slider/keen-slider.min.css";
import { ViewTransitions } from "astro:transitions"; import { ViewTransitions } from "astro:transitions";
import AnimateHeadings from "@/components/AnimateHeadings.astro";
export interface Props { export interface Props {
title: string; title: string;
@ -27,6 +28,7 @@ const { title } = Astro.props;
<meta name="generator" content={Astro.generator} /> <meta name="generator" content={Astro.generator} />
<title>vspace.one - {title}</title> <title>vspace.one - {title}</title>
<ViewTransitions /> <ViewTransitions />
<AnimateHeadings />
</head> </head>
<body> <body>
<Header /> <Header />

View File

@ -0,0 +1,37 @@
import { animate, spring, stagger } from "motion";
export default function animateHeading(e: Element, delay?: number) {
const id = Math.floor(Math.random() * 100).toString();
e.id = id;
if (e.textContent) {
e.innerHTML = e.textContent.replace(
/\S/g,
"<span class='letter'>$&</span>",
);
const updatedElement = document.getElementById(id)!;
console.log("updated", updatedElement);
const letters = updatedElement.querySelectorAll(`:scope > .letter`);
const observer = new IntersectionObserver(
() => {
console.log("inview");
animate(
letters,
{ y: [100, 0] },
{
easing: spring({ stiffness: 200, damping: 20 }),
delay: stagger(0.02, { start: delay !== undefined ? delay : 0 }),
},
);
},
{ threshold: 0.01 },
);
observer.observe(e);
}
}

View File

@ -2,6 +2,7 @@
import "pannellum/build/pannellum.css"; import "pannellum/build/pannellum.css";
import pano from "../../public/pic/panorama/maschinenraum_half.jpg"; import pano from "../../public/pic/panorama/maschinenraum_half.jpg";
import { Image } from "astro:assets"; import { Image } from "astro:assets";
import Heading from "@/components/Heading.astro";
--- ---
<div class="h-[60vh] w-full flex justify-center items-center"> <div class="h-[60vh] w-full flex justify-center items-center">
@ -15,7 +16,14 @@ import { Image } from "astro:assets";
max-md:w-[90vw]" max-md:w-[90vw]"
> >
<!--h1>vspace.one</h1--> <!--h1>vspace.one</h1-->
<h2 class="text-4xl text-center">Ein Makerspace und Hackerspace</h2> <Heading
padding={false}
animated
id="heading-makerspace"
class="text-4xl text-center"
>
Ein Makerspace und Hackerspace
</Heading>
<p> <p>
<a class="text-xl" href="/tour">Hier gehts zur virtuellen Space-Tour!</a <a class="text-xl" href="/tour">Hier gehts zur virtuellen Space-Tour!</a
> >
@ -42,10 +50,12 @@ import { Image } from "astro:assets";
import { animate } from "motion"; import { animate } from "motion";
import { ease } from "@unom/style"; import { ease } from "@unom/style";
import "pannellum"; import "pannellum";
import animateHeading from "@/lib/animate-heading";
const init = () => { const init = () => {
const panoBg = document.getElementById("pano-bg")!; const panoBg = document.getElementById("pano-bg")!;
const panoImg = document.getElementById("pano-img")! as HTMLImageElement; const panoImg = document.getElementById("pano-img")! as HTMLImageElement;
const headingMakerspace = document.getElementById("heading-makerspace")!;
const viewer = pannellum.viewer(panoBg, { const viewer = pannellum.viewer(panoBg, {
panorama: panoImg.src, panorama: panoImg.src,
@ -63,6 +73,8 @@ import { Image } from "astro:assets";
viewer.on("load", () => { viewer.on("load", () => {
console.log("loaded"); console.log("loaded");
animateHeading(headingMakerspace, 0.5);
animate( animate(
panoBg, panoBg,
{ scale: [0.9, 1], opacity: [0, 1] }, { scale: [0.9, 1], opacity: [0, 1] },

View File

@ -140,3 +140,7 @@ li {
opacity: 0; opacity: 0;
} }
} }
.letter {
display: inline-block;
}