UI lir

Mikrointeraktioner og animationer til dit UI

UI Lir Fede effekter Flotte animationer Høje lyde Livlige og levende webløsninger UI & UX Interaktioner Mikroanimationer Mega fedt! Dit site bliver bedre!

Intro

En masse små kodestumper med interaktioner og animationer klar til at blive copy-pastet og tilpasset.

Kodestumperne forudsætter at du bruger starteren DiDe starter templaten med Astro, Tailwind, Alpine, Motion One mm..

Indholdsfortegnelse

Rolling banner

Hvem er ikke vild med et rolling banner (aka marquee banner)? Måske nogen af jer tænker hvad er det? Se toppen af sitet, lige under hero unit'en. Der har jeg lavet et:-)

Et rolling banner kan laves relativt nemt med lidt html, et par keyframe animationer og lidt css/tailwind. Først html'en: I dette eksempel er html'en placeret i Header.astro-filen, da den er en del af Hero unit'en. Men den kan placeres hvor du ønsker det:


<section class="whitespace-nowrap overflow-x-hidden bg-pink-800 text-white py-4">
    <div class="inline-block animate-infinite-scroll whitespace-nowrap [&_span]:mx-6 ">
        <span>UI Lir</span>
        <span>Fede effekter</span>
        <span>Flotte animationer</span>
        <span>Høje lyde</span>
        <span>Livlige og levende webløsninger</span>
        <span>UI & UX</span>
        <span>Interaktioner</span>
        <span>Mikroanimationer</span>
        <span>Mega fedt!</span>
        <span>Dit site bliver bedre!</span>
    </div>
    <div class="inline-block animate-infinite-scroll whitespace-nowrap [&_span]:mx-6 " aria-hidden="true">
        <span>UI Lir</span>
        <span>Fede effekter</span>
        <span>Flotte animationer</span>
        <span>Høje lyde</span>
        <span>Livlige og levende webløsninger</span>
        <span>UI & UX</span>
        <span>Interaktioner</span>
        <span>Mikroanimationer</span>
        <span>Mega fedt!</span>
        <span>Dit site bliver bedre!</span>
    </div>
</section>
	

For at få banneret til at scrolle i uendelighed, skal indholdet i banneret laves to gange, og derefter animeres: Derfor er der to <div>'er, der er ens.

Dernæst animate-infinite-scroll-animationen: Den tilføjes og tilpasses via tailwind.css i @theme sektionen:


	@theme {
		/*Infinite scroll animation*/
		--animate-infinite-scroll: infinite-scroll 25s linear infinite;  

		@keyframes infinite-scroll {    
		from { transform: translateX(0);}    
		to { transform: translateX(-100%);}  
		}
	}
	

OBS! Det er vigtigt at indholdet af de <div>'en er længere end containeren, de skal udfylde!

Lydeffekter

Det er ret nemt at lave lydeffekter på et website, specielt når alpine.js kan bruges til det.

Find et par gode lydeffekter

Du kan finde lydeffekter flere steder, f.eks.:

Filerne ligger i forskellige formater – sørg for at konvertere til mp3. Det kan du gøre ved hjælp af Adobe Media Encoder eller online på f.eks. https://cloudconvert.com/

Upload din lydfil til public-mappen i dit Astro-site.

Definer "scopet"

Tilføj en x-data attribut for at definere “scopet” for lyden - du kan typisk bruge en section eller en component til at definere scopet for lyden. I dette eksempel, har jeg sat scopet til at være denne section:


	<section id="lydeffekter" class="" x-data>
	

Tilføj html audio element

Tilføj et audio element til html’en https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio


	<audio x-ref="trykforatafspillelyd">
		<source src="applaus.mp3" type="audio/mpeg">
	</audio>
	

Afspil lyden

For at afspille lyden bruges igen en lille stump alpine. I eksemplet afspilles lyden, når der klikkes på et foto:

Kodestumpen består af lidt html med en alpine @click attribut:


	<Image src={radio} alt="" type="image/webp" loading="lazy" 
	class="cursor cursor-(--handcursor)"
	@click="$refs.trykforatafspillelyd.play()"></Image>
	

Tilføj flere lyde

Gennemløb ovenstående for hver lyd, du vil tilføje.

Hvis du vil afspille en lyd ved hover i stedet for click, kan du bruge @mouseenter eller @mouseleave i stedet for @click. Besynderligt nok kræver @moseenter at nogen har klikket et vilkårligt sted på siden for at fungere...!?

Custom Cursor

Det er nemt og sjovt at tilføje sine egne cursors.

Lav grafikken

Først og fremmest gælder det om at få fat på en (lille) grafik-fil (max. 128 x 128 pixels). Se sektionen "usage notes" på https://developer.mozilla.org/en-US/docs/Web/CSS/cursor#usage_notes vedr. filstørrelser og typer, for det med at lave grafikfilerne kan drille en del!

Så: lav/design en lille cursor-fil, og placer den i “public” mappen i dit Astro website.

Implementer i Tailwind

Jvf. https://tailwindcss.com/docs/cursor#using-custom-values kan cursors tilpasses i tailwind.

Det kan gøres i tailwind.css filen, hvor man kan tilføje diverse nye CSS-regler til tailwind – se mit eksemplet nedenfor, der først tilføjer en ny CSS cursor-variabel til Tailwind.


	@theme {
    /*custom cursor*/
    --handcursor: url(/cursor.svg) 25 25, pointer;
	}
	

Giv din cursor et navn (I eksemplet er cursoren navngivet 'handcursor').

Lav den css der skal til: en url() med stien til dit cursor-billede og afslut med “, pointer” (det er et obligatorisk fallback). I mit eksempel står der også 25 25, som er et par værdier, der justerer placeringen af din cursor. Se https://developer.mozilla.org/en-US/docs/Web/CSS/cursor#syntax for yderligere info.

Brug din nye cursor ved at tilføje classerne "cursor cursor-(--handcursor)" til de html-elementer, der skal have din custom cursor på hover, jvf. eksemplet nedenfor, der tilføjer cursoren til Link komponenten.


	---
	// Henter parametrene fra Astro.props
	const { href="#", target="_self"} = Astro.props; 
	---
	<a href={href} class="underline cursor cursor-(--handcursor)" target={target}><slot /></a>
	

Custom cursor på mange elementer

Hvis cursoren skal bruges på mange elementer, f.eks. alle links og knapper, kan du også tilføje en @apply regel til tailwind.css filen.

Det kan gøres jvf. eksemplet nedenfor.


	@layer base {
	a, button{
		@apply cursor-(--handcursor);
	}
	}	
	

@layer base betyder at vi “bygger videre” på Tailwind, og er en konvention for det med at arbejde med Tailwind.

Dernæst defineres de elementer (a og button), som cursoren skal være aktiv ved – du kan tilføje alle de elementer du vil. @apply tilføjer Tailwind-classes til de valgte elementer, i dette tilfælde den nye custom cursor class.

:hover video

Internettet elsker videoer. Også små effektvideoer og GIF'er. GIF'er skal dog (typisk) ikke være GIF'er - brug små videoer i stedet. Men alt i alt: Der skal nogen på dit website!

Først og fremmest kan du lave små videoer, der afspiller når brugeren kører musen over dem (eller noget tekst?), som i eksemplet nedenfor.

Definer scopet

Det er ret nemt. Start med at tilføje en x-data attribut for at definere “scopet” for videoen - du kan typisk bruge en section eller en component til at definere scopet for videoen. I dette eksempel, har jeg sat scopet til at være denne section:


	<section id="videoonhover" x-data>
		

Tilføj videoen og start/stop afspilning

Som det næste, skal der tilføjes en video. Videoen skal have lidt JS til at starte og stoppe afspilning, når musen køres over. Det klares med Alpine JS:


	<video x-ref="hovervideo" @mouseenter="$refs.hovervideo.play()" @mouseleave="$refs.hovervideo.pause()">
	<source src="regnvejr.mp4" type="video/mp4">
	<source src="regnvejr.webm" type="video/webm">
	</video>
		

Hvis du hellere vil starte/stoppe afspilning ved at køre musen over noget andet (tekst?), kan html'en se ud som følgende:


	<span class="font-bold" @mouseenter="$refs.hovervideo.play()" @mouseleave="$refs.hovervideo.pause()">eksempelvis tekst</span>
		

Overvej hvordan det skal fungere/erstattes på mobil, for det der med :hover fungerer ikke super godt på mobil:-/

Video popup

Du kan også få videoen til at poppe op et andet sted.

For at gøre det, skal du definere en variabel i din x-data attribut:


	<section id="videoonhover" class="" x-data="{ videopopup: false }">
		

Dernæst kan du et element, tekst eksempelvis, der ændrer variablens værdi mellem true og false, når musen kører over det:


	<span class="font-bold" @mouseover="videopopup = ! videopopup" @mouseleave="videopopup = ! videopopup">poppe op et andet sted</span>
		

Som det sidste, skal der også laves et video-element, der bruger true/false variablen og alpinejs x-show attributten til at vise/skjule videoen, alt afhængig af true/false variablens værdi:


	<div class="videopopup fixed top-16 right-16 p-8 border-black border-4 bg-pink-500 z-50 drop-shadow-xl" x-show="videopopup">
	<video x-ref="videopopup" autoplay class="m-0 p-0 border-2 border-black">
	<source src="regnvejr.mp4" type="video/mp4">
	<source src="regnvejr.webm" type="video/webm">			 
	</video>
	</div>
		

Animation med Motion One

For at bruge Motion One, skal du i bunden af din side/template/komponent importere Motion One biblioteket i et script tag:


	<script>
	/* Import af Motion One bibliotek */
	import { animate, stagger, inView, scroll } from "motion";
	</script>
	

Så er Motion One installeret og klar til at blive brugt:-)

OBS! En del af animationerne kan komme til at gøre viewporten for bred, så der kommer horisontale scrollbars. Så det kan blive nødvendigt at sætte "overflow-hidden" på dele af sitet. Forsøg at være så restriktiv som muligt i brugen af overflow-hidden: Sørg for at gøre det på sektioner/komponenenter i stedet for hele sitet, da det i givet fald kan give problemer med sticky elementer:


	<header class="bg-pink-200">
		<Header></Header>
	</header>
	
	<div class="overflow-hidden">
		<main class="container mx-auto p-8 max-w-4xl">
			<slot />
		</main>
	</div>

	<footer class="bg-gradient-to-r to-pink-500 from-yellow-300">
		<Footer></Footer>
	</footer>
	

Entrance animation

Lad os starte med noget simpelt - at tilføje en entrance animation, der får hele websitet til at fade ind, når det loades - prøv at reloade siden for at se animationen.

Sådan en animation kan tilføjes i dit script-tag med ganske få kodestumper, lige efter det import statementet, der blev tilføjet i afsnittet "I gang med Motion One":


	animate( "body", { opacity: [ 0, 1] }, { duration: 1 })
	

Mere animate()

Prøv at gøre animate() funktionen lidt mere kompleks, som eksempelvis i overskriften herover. Her er der flere parametre i keyframe animationen: Opacity, scale, rotate, x, y, delay, duration, direction mm.


	/* Mere animate() */
	animate(".mereanimate", 
	{ opacity: [ 0.4, 1, 0, 0.8], scale: [0.95, 1, 0.7, 0.9], rotate: [0, 180, 270, 360], x: [0, 50, 0, -50 ], y:[0, 50, 0, -50 ]}, 
	{ delay: 2, duration: 4, repeatType: "reverse", repeat: Infinity }
	)
	

NB: Jeg har tilføjet en tailwind class til overskriften: origin-top-left. Det placerer orienteringspunktet for animationen øverst til venstre på elementet:


	<h2 class="text-6xl font-extrabold font-black origin-top-left mereanimate ">Mere animate()</h2>
	

inView animation

Indtil videre er animationerne bare blevet eksekveret når sitet er loadet. Men rigtig ofte vil vi gerne eksekvere animationerne, når brugeren ser det pågældende element i viewporten. Det kan vi gøre ved hjælp af inview()-funktionen. Fotoet herunder skubbes ind fra siden, når brugeren scroller ned til det

inView()-funktionen ser således ud for dette eksempel:


	/* inView animation */
	inView( ".fotoinview" , () =>{
	animate( ".fotoinview", { x: [-2000, 0 ] }, { duration: 3 })
	})
	

I de følgende eksempler bruges inView til at "omkranse" mange af animationerne. Animationerne behøver ikke nødvendigvis først at starte "inView," men vil i mange tilfælde være det. Derfor er "inView" inkluderet i eksemplerne.

Stagger animation

Stagger funktionen giver en række elementer den samme animation, men med et delay mellem hvert element. Se f.eks. listen nedenfor (reload evt. for at se animationen):

Stagger animationer kan laves med følgende kodestump:


	/* Stagger animation */
	inView( "#stagger" , () =>{
		animate(
		".staggeranimation li",
		{ opacity: [0, 1] },
		{ 
			delay: stagger(1, { startDelay: 2 })
		}
		)
	})
	

I ovenstående eksempel har jeg brugt en unordered list og stagget alle list items. Men du kan sagtens bruge den på andre elementer - f.eks. billeder i en section, som nedenfor. Og du kan også lave din keyframe animation vildere:-) Og du behøver ikke nødvendigvis at pakke stagger animationen ind i inView-funktionen.

Timeline animation

Med en timeline kan flere animationer bindes sammen (chaines), så de eksekveres én efter én. Se animationerne på billedet nedenfor (reload eventuelt for at se animationen):

Timeline animationen er lavet med følgende kodestump:


	/* Timeline animation */
	inView( "#timelineanimationimage" , () =>{
		const timelineanimation = [
		[".timelineanimation", { opacity: 1 }, { duration: 5 }],
		[".timelineanimation", { transform: "rotate(360deg)" }, { duration: 3 }],
		[".timelineanimation", { transform: "scale(1.5)" }, { duration: 0.5 }],
		[".timelineanimation", { transform: "scale(1)" }, { duration: 1 }],
		[".timelineanimation", { opacity: 0.3 }, { duration: 5 }],
		]
		animate( timelineanimation, { duration: 10, repeat: 2 } );
	})
	

OBS! Den første animation tager 5 sekunder, og ændrer "opacity" til 1. Opacity på 1 er også default-værdien, så for at gøre elementet usynligt til at starte med, skal html-element have tailwind-class'en opacity-0:


	<Image src={dobbeltexp} alt="" type="image/webp" loading="lazy" class="opacity-0 timelineanimation" id="timelineanimationimage"></Image>
	

inView & Return animation

Indtil videre er animationerne blevet eksekveret én gang. Hvis du gerne vil have dem til at starte (forfra) hver gang elementet vises i viewporten, kan du bruge inView()s return funktion. Se eksempelvis billedet herunder. Det er roteret 180 grader, når det kommer ind i viewporten, og bliver så roteret tilbage til 0 grader med animate()-funktionen. Scroller du væk, vender det tilbage til 180 grader, og næste gang det kommer ind i viewporten igen, roterer det til 0 grader igen."

Return funktionen fyres af efter den første animate()-funktionen, jvf. nedenstående eksempel:


	inView( ".inviewreturn", (element) => {
  	animate(
      element,
      { rotate: [180, 0] },
      { delay: 1, duration: 2 },
  	) 
    return () => {
      animate(
       element,
        { rotate: 180 }
      )
  	}
	})  
	

Læg også mærke til: ({ element } ). Element betyder at callback funktionen får sendt det valgte element med - altså selectoren ".inviewreturn"

Det kan så bruges animate-funktionens selector til at sige "vælg den værdi der er i element" - altså .inviewreturn

Crossfade on scroll

Lad os se på hvordan man kan crossfade mellem to billeder baseret på hvor langt brugeren er scrollet i en container, som i eksemplet nedenfor:

(Astro) HTML'en:


	<section class="crossfadeimages mx-auto relative h-screen lg:w-screen">
		<img src="kodakelektralitefarver.webp" alt="" class="crossfadetopimg object-cover h-screen absolute top-0 z-0 my-0">
		<img src="kodakelektralitesh.webp" alt="" class="crossfadebottomimg object-cover h-screen absolute top-0 z-0 my-0">
	</section>
	

HTML'en består af to billeder, der ligger præcis samme sted ved hjælp af absolute/relative positionering. Parent elementet er positioneret "relative," så child elementerne (billeder) positioneres absolut i forhold til det relative element.

I eksemplet bruges et img elementet med billeder i public-mappen, men du kan med fordel bruge Astros Image component i stedet - Image componenten driller når man skal lave kode-dokumentation, derfor har jeg brugt et img-element.

Script-stumpen:


	/* Crossfade to billeder */
	scroll(
	animate(".crossfadebottomimg", {
		opacity: [1, 0]
	}), {
		target: document.querySelector(".crossfadeimages"),
		offset: ["-25vh", "25vh"]
	}
	);
	

Script stumpen bruger Motion One scroll-funktionen til at checke hvor langt ned i elementet med "crossfadeimages" classen (den relative container), brugeren er scrollet. Animate-funktionen bruges til at ændre opacity fra 1 til 0 på det nederste billede. Offset bruges til at styre hvornår crossfadet skal starte/være færdiggjort. I dette tilfælde starter opacity-animationen 25vh FØR det relative element kommer ind på skærmen.

Scrollzoom (eller scroll og Zoom)

I eksemplet nedenfor, scrolles først ned i et billede, og efter at billedet er scrollet et stykke ned, bliver der zoomet i det.

Det kan gøres ret let med en scrool og animate funktion. Først html'en:


	<section class="scrollzoom overflow-clip">
	<Image src={doubleshot} class="scrollzoomimg w-full" alt="">
	</section> 
		

Dernæst er der javascript-stumpen, som benytter scroll-funktionen:


	scroll(
    animate(".scrollzoomimg", { 
        scale: [1, 1.5]    
    }),
    { 
      target: document.querySelector(".scrollzoom"),
      offset: ["0.75 end", "end end"]
    }
    )
	

Parallax med tekstboks

Parallax-sektioner ses ofte anvendt på webdoks. Parallax-sektioner kan laves med Motion Ones scroll-funktion. Scroll-funktionen lytter på hvor langt man er scrollet ned i et givent element, og så kan man animere et andet element oven på dette for at lave parallax-effekten. Se sektionen nedenfor, hvor teksten bevæger sig hurtigere end baggrunden.

Cupcake lollipop caramels toffee pastry. Bear claw dessert cotton candy ice cream cotton candy lemon drops jelly biscuit. Topping marzipan tart bonbon powder.

HTML'en består af en section med et baggrundsbillede og en paragraph med tekst. Begge stylet med Tailwind:


	<section class="bg-teal-100 mt-8 h-screen w-full bg-cover bg-center" style="background-image: url('/hurtig.jpg');">
	<p class="parallaxelement w-3/4 mx-auto p-10 bg-slate-50 text-xl">Cupcake lollipop caramels toffee pastry. Bear claw dessert cotton candy ice cream cotton candy lemon drops jelly biscuit. Topping marzipan tart bonbon powder.</p>
	</section>
	

Så er der javascript-stumpen, der kan laves kort og præcist med Motion Ones scroll-funktion:


	/* Parallax med tekstboks */ 
	scroll(animate(".parallaxelement", { y: [50, 500] }), {
		target: document.querySelector(".parallaxelement"),
	});
	

Parallaxgrafik

Parallax-sektioner kan også nemt bruges til at lave en fin, lille grafisk kollage:

HTML'en består af en section med class'en parallaxgrafik, som bruges til JS/Motion One stumpten. Dertil er der en <div> med et baggrundsbillede (inline styles) og et 12-kolonnet grid, som bruges til at placere de grafiske elementer (de små både). Alt er stylet med Tailwind, bort set fra baggrundsbilledet:


	<section class="parallaxgrafik fullwidthsection">	
		<div class="bg-teal-100 mt-8 h-screen w-full bg-cover bg-center grid grid-cols-12 overflow-hidden" style="background-image: url('/havet.jpg');">
			<Image src={baad1} alt="" class="col-start-2 parallaxgrafik1"></Image>
			<Image src={baad2} alt="" class="col-start-5 parallaxgrafik2"></Image>
			<Image src={baad3} alt="" class="col-start-3 col-span-2 parallaxgrafik3"></Image>
		</div>
	</section>
	

Så er der javascript-stumpen, der animerer grafik-elementerne med Motion Ones scroll-funktion:


	/* Parallaxgrafik */ 
	scroll(animate(".parallaxgrafik1", { y: [400, -600] }), {
		target: document.querySelector(".parallaxgrafik"), offset: ["start end", "end start"]
	});
	scroll(animate(".parallaxgrafik2", { x:[-100, 100], y: [800, -1800] }), {
		target: document.querySelector(".parallaxgrafik"), offset: ["start end", "end start"]
	});
	scroll(animate(".parallaxgrafik3", { x: [-1500, 2000], y: [400, 0] }), {
		target: document.querySelector(".parallaxgrafik"), offset: ["start end", "end start"]
	});
	

scroll()-funktionen bruges tre gange til at animere de forskellige grafik-elementer. Funktionen er stort set ens i alle tre tilfælde:

Sticky

"Sticky" sektioner ses også ofte anvendt i webfeatures - indimellem kombineret med parallax. Målet er at give brugerne lidt ro til at læse tekstbidderne. Sticky-sektioner kan laves nemt bare med Tailwind. Se de tre sektioner nedenfor, som "sticyk'er" teksten fast i toppen af skærmen.

Well, the way they make shows is, they make one show. That show's called a pilot.

Then they show that show to the people who make shows, and on the strength of that one show they decide if they're going to make more shows.

Some pilots get picked and become television programs. Some don't, become nothing.

For at lave sticky-sektioner gælder det egentlig bare om at tilføje tailwinds sticky class, og så definere hvor den skal være sticky - her top-0:


	<div class="flex items-center h-screen w-full bg-cover bg-center" style="background-image: url('/astro.jpg')" >
		<p class="w-3/4 lg:w-1/2 mx-auto p-10 bg-slate-50 text-xl sticky top-1/4">Well, the way they make shows is, they make one show. That show's called a pilot.</p>
	</div>
	

En anden måde at bruge "sticky" sektioner på er at sticky'e hele sektioner, eventuelt med en lille parallaxe-effekt på:

Well, the way they make shows is, they make one show. That show's called a pilot.

Then they show that show to the people who make shows, and on the strength of that one show they decide if they're going to make more shows.

Some pilots get picked and become television programs. Some don't, become nothing.

Her er sticky class'en placeret på elementerne med billedbaggrunde på:


	<div class="flex items-center h-screen w-full bg-cover bg-center sticky top-0" style="background-image: url('/astro.jpg')" >
		<p class="w-3/4 lg:w-1/4 lg:ml-64 p-10 bg-slate-50 text-xl stickyparallax1">Well, the way they make shows is, they make one show. That show's called a pilot.</p>
	</div>
	

Parallax'en er lavet med en JS/Motion One kodestump efter samme fremgangsmåde som Parallax med tekstboks" dokumentationen:


	scroll(animate(".stickyparallax1", { y: [-300, 0] }), {
		target: document.querySelector(".stickyparallax1"),
	});
	

Stickyscroll med infobokse

Stickyscroll-sektioner med infobokse ses ofte på blandt andet DR's webfeatures. De er nemme at lave med Tailwind - helt uden alle de der besværlige Javascrip-stumper.

Scrollende indhold 1

Scrollende indhold 2

Fast placeret indhold 1

Fast placeret indhold 2

Scrollende indhold 1

Scrollende indhold 2

Teknikken bag

Koden er realtivt simpel - det vigtige er at lave en container, som indeholder 3 elementer:


	<article><!-- containeren -->
		<div class="sticky top-0 left-0 right-0 bottom-0 h-screen w-full bg-cover bg-center" style="background-image: url('/astro.jpg')" ></div><!-- sticky elementet -->
		<div class="relative flex justify-end"><!-- relative elementet -->
			<div class="flex flex-col w-full md:w-1/2 h-full justify-center gap-4 p-4 ">
				<div class="bg-slate-100 ">
					<p class="text-center">Scrollende indhold 1</p>
				</div>
				<div class="bg-slate-100">
					<p class="text-center">Scrollende indhold 2</p>
				</div>
			</div>
		</div>
		<div class="h-[100vh]"></div><!-- afstandsstykket -->
	</article>
	

I eksemplet bruges flexbox til at organisere indholdet, men det kan i princippet lige vel være CSS grid. I sektion 2 er der et par fast positionerede indholdssektioner - det er i bund og grund bare lidt ekstra indhold i sticky-sektionen:


	<article>
		<div class="sticky top-0 left-0 right-0 bottom-0 h-screen w-full bg-cover bg-center" style="background-image: url('/havet.jpg')" ><!-- sticky elementet med lidt ekstra indhold -->
			<div class="flex justify-start h-full">
				<div class="flex flex-col w-full md:w-1/2  justify-center gap-4 p-4 ">
					<div class="bg-slate-100">
						<p class="text-center">Fast placeret indhold 1</p>
					</div>
					<div class="bg-slate-100">
						<p class="text-center">Fast placeret indhold 2</p>
					</div>
				</div>
			</div>
		</div>
		<div class="relative flex justify-end">
			<div class="flex flex-col w-full md:w-1/2 h-full justify-center gap-4 p-4 ">
				<div class="bg-slate-100 ">
					<p class="text-center">Scrollende indhold 1</p>
				</div>
				<div class="bg-slate-100">
					<p class="text-center">Scrollende indhold 2</p>
				</div>
			</div>
		</div>
		<div class="h-[100vh]"></div>
	</article>
	

Horisontal scroll

For at bryde det vertikale flow (den uendelige scrollen nedad), kan det være sjovt at tilføje en horisontalt scrollende sektion, som reagerer på et op/nedadgående scroll. Se f.eks. Banana Blossom Salads, der bruger det rigtig fint til at fremvise en menu. Eller eksemplet nedenfor:

  • foto
    Dette fine, lille kamera er et Diana F, der skyder med 120-film.
  • foto
    Dette fine, lille kamera er et Diana F, der skyder med 120-film.
  • foto
    Dette fine, lille kamera er et Diana F, der skyder med 120-film.
  • foto
    Dette fine, lille kamera er et Diana F, der skyder med 120-film.
  • foto
    Dette fine, lille kamera er et Diana F, der skyder med 120-film.

Teknikken bag horisontal scroll

I eksemplet her er der en række forskellige ting, der skal arbejde sammen. Først og fremmest i html'en:


	<div class="overflow-clip">
		<section id="horisontalscrollsection" class="h-[500vw]">
			<ul id="horisontalliste" class="list-none pl-0 flex sticky top-[10vh]">
			<li class="pl-0 flex w-[100vw] items-start justify-center flex-col overflow-hidden flex-grow-0 flex-shrink-0 flex-auto"><figure class="flex flex-row max-w-[800px]"><Image src={lomo} class="w-2/3" alt="foto" /><figcaption class="pl-4 italic w-1/3">Dette fine, lille kamera er et Diana F, der skyder med 120-film.</figcaption></figure></li>
			<li class="flex w-[100vw] items-start justify-center flex-col overflow-hidden flex-grow-0 flex-shrink-0 flex-auto"><figure class="flex flex-row max-w-[800px]"><Image src={lomo} class="w-2/3" alt="foto" /><figcaption class="pl-4 italic w-1/3">Dette fine, lille kamera er et Diana F, der skyder med 120-film.</figcaption></figure></li>
			<li class="flex w-[100vw] items-start justify-center flex-col overflow-hidden flex-grow-0 flex-shrink-0 flex-auto"><figure class="flex flex-row max-w-[800px]"><Image src={lomo} class="w-2/3" alt="foto" /><figcaption class="pl-4 italic w-1/3">Dette fine, lille kamera er et Diana F, der skyder med 120-film.</figcaption></figure></li>
			<li class="flex w-[100vw] items-start justify-center flex-col overflow-hidden flex-grow-0 flex-shrink-0 flex-auto"><figure class="flex flex-row max-w-[800px]"><Image src={lomo} class="w-2/3" alt="foto" /><figcaption class="pl-4 italic w-1/3">Dette fine, lille kamera er et Diana F, der skyder med 120-film.</figcaption></figure></li>
			<li class="flex w-[100vw] items-start justify-center flex-col overflow-hidden flex-grow-0 flex-shrink-0 flex-auto"><figure class="flex flex-row max-w-[800px]"><Image src={lomo} class="w-2/3" alt="foto" /><figcaption class="pl-4 italic w-1/3">Dette fine, lille kamera er et Diana F, der skyder med 120-film.</figcaption></figure></li>
			</ul>
		</section>
	</div>
	

Et par vigtige ting ved html'en er:

Og så er der javascript bidden:


	/* Horisontal scroll sektion */
	scroll(
	animate("#horisontalliste", {
		transform: ["none", "translateX(-500vw)"]
	}),
	{ target: document.querySelector("#horisontalscrollsection") }
	); 
	

Sektion med mange elementer der animerer ind

Hvordan man kan animere mange ting ind i skærmen på én gang, lidt ligesom på DR's webfeature om Facebook?

1
2
3
4
5
6
7
8
9
10

Teknikken bag

I html'en: Først har jeg lavet en container/article det hele. Dernæst to <div>'er: Den første <div> består af et grid på 5 kolonner. Inde i det grid er der en masse <div>'er, med hver sin class (mangeelementer1...10). <div>en er position: sticky. <div> nr. 2 fungerer bare som fyldstof:


	<article class="">
	<div class="sticky top-1/4 mangeelementerinview grid grid-cols-5">
		  <div class="p-8 bg-green-700 text-center text-white mangeelementer1">1</div>
		  <div class="p-8 bg-blue-500 text-center text-white mangeelementer2">2</div>
		  <div class="p-8 bg-orange-400 text-center text-white mangeelementer3">3</div>
		  <div class="p-8 bg-black text-center text-white mangeelementer4">4</div>
		  <div class="p-8 bg-purple-600 text-center text-white mangeelementer5">5</div>
		  <div class="p-8 bg-lime-600 text-center text-white mangeelementer6">6</div>
		  <div class="p-8 bg-slate-400 text-center text-white mangeelementer7">7</div>
		  <div class="p-8 bg-orange-600 text-center text-white mangeelementer8">8</div>
		  <div class="p-8 bg-red-400 text-center text-white mangeelementer9">9</div>
		  <div class="p-8 bg-pink-500 text-center text-white mangeelementer10">10</div>
	</div>
	<div class="h-screen">
	</div>
	</article>  
	

Dernæst skal der lidt javascript til. Her har jeg brugt inView funktionen til at checke at elementet er i viewporten. Dernæst bliver hver enkelt element animeret med animate()-funktionen. Jeg animerer hver enkelt element, så de kan forskydes uafhængigt af hinanden. Efter animate-funktionerne er der en retur/callback-funktion, som egentlig bare gentager animationerne. Så vil animationen virke hver gang elementet kommer ind i viewporten.:


/* Mange elementer der animerer ind */
	inView( ".mangeelementerinview", () =>{
	animate( ".mangeelementer1", { x: [-2000, 0 ], y: [-50, 0 ] }, { duration: 1 }) 
	animate( ".mangeelementer2", { x: [-1500, 0 ], y: [-100, 0 ] }, { duration: 3 })
	animate( ".mangeelementer3", { x: [-1000, 0 ], y: [-150, 0 ] }, { duration: 3 })
	animate( ".mangeelementer4", { x: [1750, 0 ] }, { duration: 2 })
	animate( ".mangeelementer5", { x: [-2000, 0 ], y: [-100, 0 ]  }, { duration: 1 })
	animate( ".mangeelementer6", { x: [2000, 0 ] }, { duration: 2 })
	animate( ".mangeelementer7", { x: [1500, 0 ], y: [50, 0 ] }, { duration: 3 })
	animate( ".mangeelementer8", { x: [1000, 0 ], y: [150, 0 ] }, { duration: 2 })
	animate( ".mangeelementer9", { x: [1500, 0 ], y: [0, 0 ] }, { duration: 1 })
	animate( ".mangeelementer10", { x: [2000, 0 ] , y: [50, 0 ] }, { duration: 2 })
	return () => {
		animate( ".mangeelementer1", { x: [-2000, 0 ], y: [-50, 0 ] }, { duration: 1 }) 
		animate( ".mangeelementer2", { x: [-1500, 0 ], y: [-100, 0 ] }, { duration: 3 })
		animate( ".mangeelementer3", { x: [-1000, 0 ], y: [-150, 0 ] }, { duration: 3 })
		animate( ".mangeelementer4", { x: [1750, 0 ] }, { duration: 2 })
		animate( ".mangeelementer5", { x: [-2000, 0 ], y: [-100, 0 ]  }, { duration: 1 })
		animate( ".mangeelementer6", { x: [2000, 0 ] }, { duration: 2 })
		animate( ".mangeelementer7", { x: [1500, 0 ], y: [50, 0 ] }, { duration: 3 })
		animate( ".mangeelementer8", { x: [1000, 0 ], y: [150, 0 ] }, { duration: 2 })
		animate( ".mangeelementer9", { x: [1500, 0 ], y: [0, 0 ] }, { duration: 1 })
		animate( ".mangeelementer10", { x: [2000, 0 ] , y: [50, 0 ] }, { duration: 2 })
   		}
  	})
	

Progress-bar

Læg mærke til toppen af sitet - der er en progress-bar/læseindikator, der viser hvor langt nede på siden brugeren er. Du kan lave en progress-bar ved hjælp af to stumper: En html-og-tailwind stump, der styrer udseendet, og en JS/Motion One stump, der styrer hvor meget af progress-baren, der vises.

Først og fremmest er progress-baren "designet" med Tailwind. Jeg har placeret den nederst i html'en, da den alligevel bare bliver positioneret "fixed" over alt andet indhold:


	<!-- Progress-bar placeret i toppen af sitet -->
	<div class="progress-bar h-4 fixed w-full bg-gradient-to-r from-pink-500 to-yellow-300 left-0 origin-top-left top-0 right-0"></div>
	

Dernæst er der en JS/Motion One stump, der bruger Motion Ones scroll-funktion:


	/*Progress-baren/læseindikatoren */
	scroll( animate(".progress-bar", { scaleX: [0, 1] }));
	

Her udfører scroll-funktionen en animate-funktion, der skalerer progress-baren op i størrelse fra 0 til 1 (fuld størrelse), alt afhængig af hvor langt brugeren er scrollet ned på siden.

En progress-bar behøver ikke at være fastgjort til toppen - den kan f.eks. også være i venstre eller højre side af skærmen - så skal html'en og JS/Motion One stumpen tilpasses lidt:


	<!-- Progress-bar placeret i venstre side af sitet -->
	<div class="progress-bar h-4 fixed h-full bg-gradient-to-b from-pink-500 to-yellow-300 left-0 origin-top-left top-0 right-[99%]"></div>
	

Og JS'en:


	/*Progress-baren/læseindikatoren */
	scroll( animate(".progress-bar", { scaleY: [0, 1] }));