Et billede fortæller ikke altid hele historien.

Men to billeder kan gøre en forskel.

Forestil dig en have, hvor et stort træ er blevet fældet. Ejeren vil gerne sammenligne området før og efter arbejdet.

Hvor meget lys er der kommet ind?

Hvordan ser haven ud uden den store krone?

Og hvilke dele af området er blevet ændret?

Det er et godt eksempel på en lille webfunktion, som kan være nyttig i mange forskellige projekter.

I denne guide bygger vi et før-og-efter-værktøj med HTML, CSS og JavaScript.

Brugeren vælger to billeder fra sin egen computer. Billederne bliver vist ved siden af hinanden i browseren.

Vi bruger ingen eksterne biblioteker og ingen backend.

Det betyder, at vores demo ikke uploader billederne til en server.

Hvad skal værktøjet kunne?

Vi holder kravene enkle.

Brugeren skal kunne:

  • Vælge et førbillede.
  • Vælge et efterbillede.
  • Se begge billeder på samme side.
  • Bruge værktøjet på mobil.
  • Fjerne billederne igen.

Derudover skal formularen være tilgængelig med tastatur.

Vi skal heller ikke gemme billederne permanent.

Det er et bevidst valg, fordi mange billeder fra private haver kan vise boliger, biler eller andre personlige oplysninger.

1. Opret projektets filer

Vi bruger tre filer:

index.html style.css script.js 

Du behøver ikke et framework.

Åbn blot filerne i din foretrukne kodeeditor.

2. Byg HTML-strukturen

Vi begynder med en overskrift, to filfelter og to områder til billedvisning.

Gem dette som index.html.

<!doctype html> <html lang="da"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Før og efter - Billedsammenligning</title> <link rel="stylesheet" href="style.css"> <script src="script.js" defer></script> </head> <body> <main class="container"> <header> <h1>Før og efter</h1> <p> Sammenlign to billeder direkte i browseren. Billederne sendes ikke til en server. </p> </header> <section aria-labelledby="uploadTitle"> <h2 id="uploadTitle">Vælg billeder</h2> <div class="upload-grid"> <div class="field"> <label for="beforeInput"> Billede før arbejdet </label> <input type="file" id="beforeInput" accept="image/*"> </div> <div class="field"> <label for="afterInput"> Billede efter arbejdet </label> <input type="file" id="afterInput" accept="image/*"> </div> </div> </section> <section aria-labelledby="resultTitle"> <h2 id="resultTitle">Sammenligning</h2> <div class="comparison-grid"> <figure> <img id="beforeImage" alt="Førbillede" hidden> <figcaption>Før</figcaption> </figure> <figure> <img id="afterImage" alt="Efterbillede" hidden> <figcaption>Efter</figcaption> </figure> </div> </section> <button type="button" id="resetButton"> Fjern billeder </button> <p id="status" role="status" aria-live="polite"></p> </main> </body> </html> 

Bemærk brugen af label og figure.

Hvert filfelt har en synlig label. Det gør det lettere at forstå, hvilket billede der skal vælges.

figure og figcaption giver en tydelig forbindelse mellem billedet og beskrivelsen.

Vi bruger også hidden, så tomme billedelementer ikke bliver vist, før brugeren vælger filer.

3. Lav et responsivt layout

Nu skal billederne placeres ved siden af hinanden på store skærme.

På mobil skal de stå under hinanden.

Gem følgende som style.css.

* { box-sizing: border-box; } body { margin: 0; padding: 24px 16px; background: #f4f7f4; color: #26352b; font-family: Arial, sans-serif; line-height: 1.6; } .container { max-width: 1000px; margin: 0 auto; padding: 28px; background: #ffffff; border-radius: 16px; } h1, h2 { line-height: 1.3; } h1 { color: #15573b; } .upload-grid, .comparison-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; } .field { margin: 12px 0 24px; } label { display: block; margin-bottom: 8px; font-weight: 700; } input[type="file"] { display: block; width: 100%; max-width: 100%; font: inherit; } figure { margin: 0; min-width: 0; padding: 12px; background: #eaf1eb; border-radius: 12px; } figure img:not([hidden]) { display: block; width: 100%; height: auto; max-height: 380px; object-fit: contain; } figcaption { padding-top: 12px; font-weight: 700; text-align: center; } button { margin-top: 24px; padding: 12px 20px; border: 0; border-radius: 8px; background: #15573b; color: #ffffff; font: inherit; font-weight: 700; cursor: pointer; } button:hover { background: #10442e; } button:focus-visible, input:focus-visible { outline: 3px solid #a35b13; outline-offset: 4px; } #status { min-height: 24px; margin-top: 18px; } @media (max-width: 650px) { .container { padding: 18px; } .upload-grid, .comparison-grid { grid-template-columns: 1fr; } } 

Her bruger vi CSS Grid.

To kolonner giver en enkel sammenligning på større skærme.

På mobil ændrer media query layoutet til én kolonne.

Vi bruger også object-fit: contain.

Det sikrer, at hele billedet kan ses, uden at vi automatisk beskærer det.

4. Vis billederne med JavaScript

Når en bruger vælger en fil, skal vi vise den i det rigtige billedelement.

Det kan vi gøre med URL.createObjectURL().

Metoden opretter en midlertidig objekt-URL, som browseren kan bruge til at vise den valgte fil.

Vi skal huske at frigive disse URL'er, når de ikke længere er nødvendige.

Gem følgende som script.js.

const beforeInput = document.querySelector("#beforeInput"); const afterInput = document.querySelector("#afterInput"); const beforeImage = document.querySelector("#beforeImage"); const afterImage = document.querySelector("#afterImage"); const resetButton = document.querySelector("#resetButton"); const status = document.querySelector("#status"); const imageUrls = { before: null, after: null }; function removeImage(type) { const image = type === "before" ? beforeImage : afterImage; if (imageUrls[type]) { URL.revokeObjectURL(imageUrls[type]); imageUrls[type] = null; } image.removeAttribute("src"); image.hidden = true; } function loadImage(file, type) { const image = type === "before" ? beforeImage : afterImage; removeImage(type); if (!file) { return; } if (!file.type.startsWith("image/")) { status.textContent = "Vælg venligst en billedfil."; return; } const url = URL.createObjectURL(file); imageUrls[type] = url; image.onload = () => { if (imageUrls[type] !== url) { return; } image.hidden = false; status.textContent = "Billedet er klar til visning."; }; image.onerror = () => { if (imageUrls[type] !== url) { return; } removeImage(type); status.textContent = "Billedet kunne ikke åbnes."; }; image.alt = type === "before" ? "Valgt billede før arbejdet" : "Valgt billede efter arbejdet"; image.src = url; } beforeInput.addEventListener("change", () => { loadImage(beforeInput.files[0], "before"); }); afterInput.addEventListener("change", () => { loadImage(afterInput.files[0], "after"); }); resetButton.addEventListener("click", () => { beforeInput.value = ""; afterInput.value = ""; removeImage("before"); removeImage("after"); status.textContent = "Begge billeder er fjernet fra visningen."; }); window.addEventListener("pagehide", () => { removeImage("before"); removeImage("after"); }); 

Nu har vi en simpel billedvisning, som fungerer uden et eksternt bibliotek.

JavaScript kontrollerer først, om den valgte fil er en billedtype.

Derefter oprettes en midlertidig URL.

Når brugeren vælger et nyt billede eller trykker på nulstil, frigives den tidligere URL.

Det er især nyttigt, hvis brugeren prøver flere store billeder i samme session.

5. Hvorfor er løsningen mere privatlivsvenlig?

I vores eksempel bliver filerne håndteret direkte i browseren.

Der er ingen kode, som sender billederne til en server.

Vi bruger heller ikke lokal lagring eller en database.

Når siden lukkes, findes der derfor ingen gemt kopi i vores applikation.

Men det betyder ikke, at alle mulige privatlivsrisici automatisk er væk.

Hvis du senere tilføjer analyseværktøjer, tredjepartsscripts eller en backend, kan situationen ændre sig.

Derfor bør privatliv tænkes ind fra begyndelsen.

I en færdig applikation kan det også være nyttigt at informere brugeren om, hvordan billeder og metadata håndteres.

6. Test med forskellige billeder

Et værktøj kan se fint ud med ét testbillede og stadig fejle med andre billeder.

Prøv derfor flere scenarier.

Et stort landskabsbillede

Kontrollér, at billedet bliver skaleret korrekt.

Et billede i højformat

Undersøg, om det stadig kan ses uden at blive beskåret.

Kun ét billede

Brugeren skal kunne se et førbillede uden at vælge et efterbillede.

En fil, som ikke er et gyldigt billede

Værktøjet skal vise en forståelig besked.

Et nyt billede i samme felt

Kontrollér, at det gamle billede bliver erstattet.

Nulstil-knappen

Begge billeder skal forsvinde, og felterne skal blive tømt.

Det kan også være nyttigt at teste med tastatur alene.

Brugeren skal kunne vælge billeder og bruge knappen uden en mus.

7. Hvad mangler før produktion?

Dette er en læringsdemo.

Den viser billeder lokalt, men er ikke et færdigt dokumentationssystem.

Hvis du vil udvikle den videre, kan du overveje:

  • Begrænsning af filstørrelser.
  • Bedre fejlbeskeder.
  • Tastatur- og skærmlæsertest.
  • Mulighed for selv at skrive billedbeskrivelser.
  • Billedbehandling med passende hukommelsesstyring.
  • Klar information om eventuel databehandling.

Hvis løsningen skal understøtte professionelle arbejdsrapporter, skal der også være en plan for dokumentation, adgangskontrol og korrekt opbevaring.

En demo med to billeder kan være nyttig, men den bør ikke præsenteres som dokumentation for, at en opgave er udført sikkert eller korrekt.

Hvorfor bruge træfældning som eksempel?

Når et stort træ fjernes, kan området ændre sig tydeligt.

Der kan komme mere dagslys ind i haven. Udsigten kan ændre sig, og der kan blive plads til nye planter eller andre formål.

Derfor kan før- og efterbilleder være interessante for både haveejere og fagfolk.

Men billeder fortæller ikke alt.

De viser ikke nødvendigvis, hvordan træets tilstand var før arbejdet, eller hvilke sikkerhedsmæssige vurderinger der blev foretaget.

Fotografier bør derfor betragtes som visuel dokumentation, ikke som en selvstændig faglig vurdering.

Dansk Træpleje har base i Vejle og arbejder med blandt andet beskæring, træpleje og professionel træfældning.

Hvis du vil læse om den virkelige faglige kontekst, kan du se, hvordan virksomheden arbejder med at fælde træer i Vejle.

Værktøjet i denne guide er et selvstændigt kodeeksempel. Det er ikke en beskrivelse af en eksisterende funktion på virksomhedens hjemmeside.

Hvad har vi lært?

Vi har bygget et lille billedværktøj med tre almindelige webteknologier.

HTML giver strukturen.

CSS skaber et responsivt layout.

JavaScript læser de valgte filer og viser dem midlertidigt i browseren.

Vi har også brugt URL.revokeObjectURL() til at rydde op efter de midlertidige objekt-URL'er.

Det vigtigste er, at løsningen ikke kræver en stor teknisk platform for at være nyttig.

Nogle gange er en lille, tydelig funktion nok.

Og hvis brugeren forstår, hvad der sker med billederne, er vi allerede kommet et godt stykke med både brugeroplevelse og privatliv.