Projectkaart wordt hero Nieuw
Klik een projectkaart en de foto groeit uit tot de hero van de detailpagina, de titel reist mee; terug gaat het omgekeerd (ook met Esc). Gedeeld element via View Transitions, zonder support wisselt de weergave direct. Multi-page: @view-transition { navigation: auto; } en op de overzichtspagina én de detailpagina dezelfde view-transition-name op de foto (bv. project-12-foto).
Bouwbedrijf Van Dam · projecten3 van 48
Zo gebruik je het
- Plak de HTML op de plek waar het onderdeel moet komen.
- Zet de CSS in je stylesheet. Alle klassen hebben een eigen voorvoegsel, dus niets botst met je eigen opmaak.
- Voeg de JavaScript toe en roep init() aan met het element waar de HTML in staat.
<!-- Projectkaart wordt hero · NedDev · eigen werk. -->
<div class="jouw-container">
<!-- Echte multi-page site:
1. CSS op beide pagina's: @view-transition { navigation: auto; }
2. Overzicht: geef ALLEEN de aangeklikte kaartfoto een naam, bv. style="view-transition-name: project-12-foto" (uniek per kaart)
3. Detailpagina: dezelfde naam op de hero-foto. De browser morpht positie en formaat zelf.
4. Neem de ::view-transition-old/new(vth-img) regels over (height: 100%; object-fit: cover)
5. Reduced motion: @media (prefers-reduced-motion: reduce) { ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; } } -->
<div class="vth">
<div class="vth__list">
<div class="vth__top"><span class="vth__kick">Bouwbedrijf Van Dam · projecten</span><span class="vth__count">3 van 48</span></div>
<div class="vth__grid">
<button class="vth__card" data-i="0"><span class="vth__img" style="background-image:url(https://neddev.com/lab-media/vt-thumbnail-naar-hero-1.webp)"></span><span class="vth__t">Aanbouw met schuifpui</span><span class="vth__m">Zwolle · 2026</span></button>
<button class="vth__card" data-i="1"><span class="vth__img" style="background-image:url(https://neddev.com/lab-media/vt-thumbnail-naar-hero-2.webp)"></span><span class="vth__t">Dakkapel jaren dertig</span><span class="vth__m">Amersfoort · 2026</span></button>
<button class="vth__card" data-i="2"><span class="vth__img" style="background-image:url(https://neddev.com/lab-media/vt-thumbnail-naar-hero-3.webp)"></span><span class="vth__t">Badkamer met terrazzo</span><span class="vth__m">Delft · 2025</span></button>
</div>
</div>
<div class="vth__detail" hidden>
<span class="vth__hero"></span>
<button class="vth__back"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M10 3L5 8l5 5"/></svg>Alle projecten</button>
<div class="vth__body">
<h3 class="vth__dt" tabindex="-1"></h3>
<dl class="vth__facts"></dl>
<p class="vth__dp"></p>
</div>
</div>
<span class="vth__sr" aria-live="polite"></span>
</div>
</div>/* Projectkaart wordt hero · NedDev · eigen werk. */
.vth{position:absolute;inset:0;background:#fff;color:#0a0a0a;font-family:'Switzer',system-ui,sans-serif;overflow:hidden}
.vth__list{position:absolute;inset:0;padding:14px 16px;display:flex;flex-direction:column}
.vth__top{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:10px;gap:8px}
.vth__kick{font:500 10px 'JetBrains Mono',monospace;letter-spacing:.12em;text-transform:uppercase;color:rgba(10,10,10,.6)}
.vth__count{font:500 10px 'JetBrains Mono',monospace;color:rgba(10,10,10,.45);white-space:nowrap}
.vth__grid{flex:1;display:grid;grid-template-columns:repeat(3,1fr);gap:10px;min-height:0}
.vth__card{all:unset;box-sizing:border-box;display:flex;flex-direction:column;min-width:0;cursor:pointer;border-radius:4px}
.vth__img{display:block;flex:1;min-height:0;border-radius:4px;background:#ddd center/cover no-repeat;transition:filter .2s}
.vth__t{display:block;margin-top:7px;font-size:12.5px;font-weight:600;line-height:1.2;width:max-content;max-width:100%}
.vth__m{display:block;margin-top:2px;font:500 10px 'JetBrains Mono',monospace;color:rgba(10,10,10,.55)}
.vth__card:hover .vth__img{filter:brightness(.9)}
.vth__card:hover .vth__t{text-decoration:underline;text-decoration-color:#F5480F;text-underline-offset:3px}
.vth__card:focus-visible{outline:2px solid #F5480F;outline-offset:3px}
.vth__card:active .vth__img{filter:brightness(.82)}
.vth__detail{position:absolute;inset:0;display:grid;grid-template-columns:1.15fr 1fr;background:#fff}
.vth__detail[hidden]{display:none}
.vth__hero{display:block;background:#ddd center/cover no-repeat;grid-row:1/3}
.vth__back{all:unset;box-sizing:border-box;position:absolute;top:10px;left:10px;display:inline-flex;align-items:center;gap:5px;font-size:11.5px;font-weight:600;padding:6px 10px 6px 7px;border-radius:999px;background:#fff;color:#0a0a0a;cursor:pointer;box-shadow:0 1px 3px rgba(10,10,10,.2);transition:background-color .15s,color .15s}
.vth__back svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.vth__back:hover{background:#0a0a0a;color:#fff}
.vth__back:focus-visible{outline:2px solid #F5480F;outline-offset:2px}
.vth__body{padding:16px 16px 12px;display:flex;flex-direction:column;min-width:0;overflow:hidden}
.vth__dt{margin:0 0 10px;font-size:19px;line-height:1.15;font-weight:700;letter-spacing:-.01em;width:max-content;max-width:100%}
.vth__dt:focus{outline:none}
.vth__facts{margin:0 0 10px;display:grid;grid-template-columns:auto 1fr;gap:3px 10px;font-size:11.5px}
.vth__facts dt{color:rgba(10,10,10,.55);margin:0}.vth__facts dd{margin:0;font-family:'JetBrains Mono',monospace;font-size:11px;font-variant-numeric:tabular-nums}
.vth__dp{margin:0;font-size:12px;line-height:1.5;color:rgba(10,10,10,.72)}
.vth__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
@media (max-width:420px){.vth__list{padding:12px}.vth__grid{gap:7px}.vth__t{font-size:11.5px}.vth__m{font-size:9.5px}.vth__detail{grid-template-columns:1fr;grid-template-rows:42% 1fr}.vth__hero{grid-row:auto}.vth__body{padding:10px 12px}.vth__dt{font-size:16px;margin-bottom:6px}.vth__dp{display:none}}
/* gedeeld element: foto en titel */
::view-transition-group(vth-img),::view-transition-group(vth-title){animation-duration:.45s;animation-timing-function:cubic-bezier(.2,.75,.15,1)}
::view-transition-group(vth-img){overflow:clip}
::view-transition-old(vth-img),::view-transition-new(vth-img){height:100%;object-fit:cover;animation-duration:.45s}
::view-transition-old(vth-title),::view-transition-new(vth-title){height:100%;width:auto;animation-duration:.3s}
@media (prefers-reduced-motion:reduce){::view-transition-group(vth-img),::view-transition-group(vth-title),::view-transition-old(vth-img),::view-transition-new(vth-img){animation:none}}// Projectkaart wordt hero · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
var w=root.querySelector('.vth');if(!w)return;var list=w.querySelector('.vth__list'),det=w.querySelector('.vth__detail'),hero=w.querySelector('.vth__hero'),dt=w.querySelector('.vth__dt'),facts=w.querySelector('.vth__facts'),dp=w.querySelector('.vth__dp'),back=w.querySelector('.vth__back'),sr=w.querySelector('.vth__sr');
var rm=matchMedia('(prefers-reduced-motion: reduce)');var busy=false,open=-1;
var D=[{f:[['Oppervlak','24 m²'],['Doorlooptijd','6 weken'],['Opgeleverd','juni 2026']],p:'Uitbouw van 4 meter met een stalen schuifpui over de volle breedte. Fundering op schroefpalen, zodat de tuin bleef staan.'},
{f:[['Breedte','3,6 m'],['Doorlooptijd','2 dagen'],['Opgeleverd','maart 2026']],p:'Prefab dakkapel in antraciet, in één dag geplaatst. Extra slaapkamer met stahoogte in een woning uit 1934.'},
{f:[['Oppervlak','7 m²'],['Doorlooptijd','3 weken'],['Opgeleverd','nov 2025']],p:'Terrazzo vloer, inloopdouche met stalen wand en een eiken meubel op maat. Leidingwerk volledig vernieuwd.'}];
var cards=[].slice.call(w.querySelectorAll('.vth__card'));
function setN(img,t,on){img.style.viewTransitionName=on?'vth-img':'';t.style.viewTransitionName=on?'vth-title':'';}
function run(from,to,upd,after){if(!document.startViewTransition||rm.matches){upd();after();return;}busy=true;setN(from[0],from[1],true);
var t=document.startViewTransition(function(){setN(from[0],from[1],false);upd();var x=to();setN(x[0],x[1],true);});
t.finished.finally(function(){var x=to();setN(x[0],x[1],false);busy=false;after();});}
function openP(i){if(busy||open>-1)return;var c=cards[i];
run([c.querySelector('.vth__img'),c.querySelector('.vth__t')],function(){return [hero,dt];},function(){
hero.style.backgroundImage=c.querySelector('.vth__img').style.backgroundImage;dt.textContent=c.querySelector('.vth__t').textContent;
facts.innerHTML=D[i].f.map(function(r){return '<dt>'+r[0]+'</dt><dd>'+r[1]+'</dd>';}).join('');dp.textContent=D[i].p;
list.hidden=true;list.inert=true;det.hidden=false;open=i;sr.textContent='Project '+dt.textContent+' geopend';},function(){back.focus({preventScroll:true});});}
function closeP(){if(busy||open<0)return;var c=cards[open];
run([hero,dt],function(){return [c.querySelector('.vth__img'),c.querySelector('.vth__t')];},function(){det.hidden=true;list.hidden=false;list.inert=false;open=-1;sr.textContent='Terug naar alle projecten';},function(){c.focus({preventScroll:true});});}
cards.forEach(function(c,i){c.addEventListener('click',function(){openP(i);});});
back.addEventListener('click',closeP);
w.addEventListener('keydown',function(e){if(e.key==='Escape'&&open>-1){e.preventDefault();closeP();}});
}
init(document.querySelector('.jouw-container'));
Meer uit navigatie & menu's
Iets gezien dat je mooi vindt?
Zet het in je selectie en stuur hem op. Dan bouwen we je site eromheen, in jouw huisstijl.
Neem contact op →