Bildgalleri med thumbnails i CSS Grid och en lightbox-vy med tangentbordsnavigering
Kul med teknik

Bildgalleri med JavaScript – bygg ett eget från grunden

Det finns otaliga JavaScript-galleri-plugins. Lightbox, Fancybox, Swiper — listan är lång. Men vill du ha fullständig kontroll över beteende, design och prestanda kan det vara värt att bygga ditt eget bildgalleri med JavaScript från grunden. Det är inte så komplicerat som det kan verka, och du slipper ladda in ett helt bibliotek för något som egentligen kräver ett par hundra rader kod.

Här bygger vi ett komplett bildgalleri med thumbnails, lightbox-vy och tangentbordsnavigering — med vanilla JavaScript, utan beroenden.

HTML-strukturen för galleriet

Vi börjar med en enkel, semantisk HTML-struktur. Varje bild är en <a>-tagg som pekar till fullstorlek-versionen, med en thumbnail som <img> inuti. Det ger en fungerande fallback även utan JavaScript — klickar man på en thumbnail öppnas bilden direkt.

<div class="gallery" id="gallery">
  <a href="bild-1-full.jpg" class="gallery-item">
    <img src="bild-1-thumb.jpg" alt="Beskrivning av bild 1" loading="lazy" />
  </a>
  <a href="bild-2-full.jpg" class="gallery-item">
    <img src="bild-2-thumb.jpg" alt="Beskrivning av bild 2" loading="lazy" />
  </a>
  <a href="bild-3-full.jpg" class="gallery-item">
    <img src="bild-3-thumb.jpg" alt="Beskrivning av bild 3" loading="lazy" />
  </a>
</div>

Attributet loading="lazy" ser till att bilder längre ner på sidan inte laddas förrän de behövs. Det är gratis prestanda som du alltid bör ha med i ett bildgalleri på webben.

CSS för responsivt rutnät

Griden bygger vi med CSS Grid och auto-fill, så att antalet kolumner anpassar sig till tillgänglig bredd utan mediaqueries:

.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 8px;
}

.gallery-item {
  overflow: hidden;
  border-radius: 4px;
  aspect-ratio: 1;
}

.gallery-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.3s ease;
}

.gallery-item:hover img {
  transform: scale(1.05);
}

aspect-ratio: 1 ger kvadratiska thumbnails oavsett bildens originalformat, och object-fit: cover ser till att bilderna fyller ytan utan att förvrängas. Hover-effekten med scale(1.05) ger subtil interaktionsfeedback.

Lightbox med JavaScript

Nu till den intressanta delen. Lightboxen skapar vi dynamiskt med JavaScript — den läggs till i DOM:en vid första klicket och återanvänds sedan:

const gallery = document.getElementById('gallery');
const items = gallery.querySelectorAll('.gallery-item');
let currentIndex = 0;
let lightbox = null;
let lightboxImg = null;

function createLightbox() {
  lightbox = document.createElement('div');
  lightbox.className = 'lightbox';
  lightbox.innerHTML = `
    <button class="lightbox-close" aria-label="Stäng">&times;</button>
    <button class="lightbox-prev" aria-label="Föregående">&#8249;</button>
    <button class="lightbox-next" aria-label="Nästa">&#8250;</button>
    <img class="lightbox-img" src="" alt="" />
  `;
  document.body.appendChild(lightbox);
  lightboxImg = lightbox.querySelector('.lightbox-img');

  lightbox.querySelector('.lightbox-close').addEventListener('click', closeLightbox);
  lightbox.querySelector('.lightbox-prev').addEventListener('click', () => navigate(-1));
  lightbox.querySelector('.lightbox-next').addEventListener('click', () => navigate(1));
  lightbox.addEventListener('click', (e) => {
    if (e.target === lightbox) closeLightbox();
  });
}

function openLightbox(index) {
  if (!lightbox) createLightbox();
  currentIndex = index;
  const item = items[currentIndex];
  lightboxImg.src = item.href;
  lightboxImg.alt = item.querySelector('img').alt;
  lightbox.classList.add('active');
  document.body.style.overflow = 'hidden';
}

function closeLightbox() {
  lightbox.classList.remove('active');
  document.body.style.overflow = '';
}

function navigate(direction) {
  currentIndex = (currentIndex + direction + items.length) % items.length;
  const item = items[currentIndex];
  lightboxImg.src = item.href;
  lightboxImg.alt = item.querySelector('img').alt;
}

Modulo-operationen (currentIndex + direction + items.length) % items.length gör att navigeringen loopar — trycker du ”nästa” på sista bilden hamnar du på den första, och vice versa.

Tangentbordsnavigering i js-galleriet

Tangentbordsstöd är inte bara bra för tillgänglighet — det är också bekvämt för alla som vill bläddra snabbt:

document.addEventListener('keydown', (e) => {
  if (!lightbox || !lightbox.classList.contains('active')) return;

  switch (e.key) {
    case 'ArrowLeft':
      navigate(-1);
      break;
    case 'ArrowRight':
      navigate(1);
      break;
    case 'Escape':
      closeLightbox();
      break;
  }
});

items.forEach((item, index) => {
  item.addEventListener('click', (e) => {
    e.preventDefault();
    openLightbox(index);
  });
});

Escape stänger lightboxen, piltangenterna navigerar. Det är det beteende användare förväntar sig.

CSS för lightboxen

Lightboxens styling använder position: fixed för att täcka hela viewporten och opacity med transition för en mjuk in- och utfadning:

.lightbox {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.9);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
  z-index: 1000;
}

.lightbox.active {
  opacity: 1;
  pointer-events: auto;
}

.lightbox-img {
  max-width: 90vw;
  max-height: 85vh;
  object-fit: contain;
  border-radius: 4px;
}

.lightbox-close,
.lightbox-prev,
.lightbox-next {
  position: absolute;
  background: none;
  border: none;
  color: #fff;
  font-size: 2.5rem;
  cursor: pointer;
  padding: 1rem;
  opacity: 0.7;
  transition: opacity 0.2s;
}

.lightbox-close:hover,
.lightbox-prev:hover,
.lightbox-next:hover {
  opacity: 1;
}

.lightbox-close { top: 1rem; right: 1.5rem; }
.lightbox-prev { left: 1rem; top: 50%; transform: translateY(-50%); }
.lightbox-next { right: 1rem; top: 50%; transform: translateY(-50%); }

Att använda inset: 0 istället för top: 0; right: 0; bottom: 0; left: 0 är en modern shorthand som sparar en rad och gör koden tydligare.

Prestanda: förladda nästa bild

En sista förfining — förladda bilden som kommer härnäst i sekvensen, så att den dyker upp direkt vid navigering:

function navigate(direction) {
  currentIndex = (currentIndex + direction + items.length) % items.length;
  const item = items[currentIndex];
  lightboxImg.src = item.href;
  lightboxImg.alt = item.querySelector('img').alt;

  // Förladda nästa bild
  const nextIndex = (currentIndex + 1) % items.length;
  const preload = new Image();
  preload.src = items[nextIndex].href;
}

Touch-stöd: swipe i bildgalleriet

På mobil förväntar sig användare att kunna svepa mellan bilder. Grundläggande swipe-detektering kräver bara att du spårar touchstart och touchend:

let touchStartX = 0;

lightbox.addEventListener('touchstart', (e) => {
  touchStartX = e.changedTouches[0].screenX;
}, { passive: true });

lightbox.addEventListener('touchend', (e) => {
  const diff = touchStartX - e.changedTouches[0].screenX;
  if (Math.abs(diff) > 50) {
    navigate(diff > 0 ? 1 : -1);
  }
});

Tröskeln på 50 pixlar förhindrar att oavsiktliga tryck tolkas som swipes. Attributet { passive: true } på touchstart talar om för webbläsaren att du inte kommer att anropa preventDefault(), vilket ger bättre scroll-prestanda.

Sammanfattning

Att bygga ett bildgalleri med JavaScript behöver inte vara komplicerat. Med CSS Grid får du ett responsivt rutnät utan mediaqueries. En dynamiskt skapad lightbox håller DOM:en ren tills den behövs. Tangentbordsnavigering med piltangenter och Escape ger ett beteende som användare förväntar sig. Och loading="lazy" plus förladdning av nästa bild ser till att galleriet känns snabbt. Hela lösningen kräver inga externa beroenden — det är vanilla JavaScript som fungerar i alla moderna webbläsare.

Kommentera artikeln

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *