Vertrektijdenbord, licht New
Light departure board as a real table: time, destination, train type and platform, with delays, platform changes and cancellations in orange. A sped-up demo clock lets trains depart (the row slides away, a new one comes in) and changes are announced through a live region. With pause button; with reduced motion it stays still. For transport operators, events, trade fairs, swimming pools and desk planning.
Rotterdam Centraal · vertrek
| Tijd | Naar | Trein | Spoor |
|---|
How to use it
- Paste the HTML where the numbers & charts should appear.
- Add the CSS to your stylesheet. All classes are prefixed, so nothing clashes with your own styles.
- Add the JavaScript and call init() with the element that contains the HTML.
<!-- Vertrektijdenbord, licht · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="vtb">
<div class="vtb__hd"><p class="vtb__t">Rotterdam Centraal<span> · vertrek</span></p><p class="vtb__clk" aria-hidden="true">14:32</p><button type="button" class="vtb__pp" aria-pressed="false">Pauze</button></div>
<table class="vtb__tbl"><caption class="vtb__sr">Vertrektijden Rotterdam Centraal, voorbeelddata</caption><thead><tr><th scope="col" class="vtb__c1">Tijd</th><th scope="col">Naar</th><th scope="col" class="vtb__srt">Trein</th><th scope="col" class="vtb__c4">Spoor</th></tr></thead><tbody></tbody></table>
<p class="vtb__sr vtb__live" aria-live="polite"></p>
</div>
</div>/* Vertrektijdenbord, licht · NedDev · eigen werk. */
.vtb{position:absolute;inset:0;box-sizing:border-box;padding:10px 14px;background:#fff;color:#0a0a0a;font-family:'Switzer',sans-serif;display:flex;flex-direction:column;gap:6px;container-type:size}
.vtb p{margin:0}
.vtb__hd{display:flex;align-items:center;gap:10px}
.vtb__t{font-size:13px;font-weight:600;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.vtb__t span{font-weight:400;color:#5f5e5a}
.vtb__clk{font-size:15px;font-weight:600;font-variant-numeric:tabular-nums}
.vtb__pp{min-height:26px;min-width:58px;padding:0 8px;border:1px solid #d6d3ce;border-radius:4px;background:#fff;color:#0a0a0a;font:600 12px 'Switzer',sans-serif;cursor:pointer}
.vtb__pp:hover{border-color:#0a0a0a}
.vtb__pp:focus-visible{outline:2px solid #F5480F;outline-offset:2px}
.vtb__tbl{width:100%;border-collapse:collapse;table-layout:fixed;font-size:13px}
.vtb__tbl th{font-size:12px;font-weight:500;color:#6b6a66;text-align:left;padding:0 6px 4px;border-bottom:1px solid #0a0a0a}
.vtb__tbl td{height:27px;padding:0 6px;border-bottom:1px solid #eeece9;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.vtb__c1{width:66px}.vtb__srt{width:72px}.vtb__c4{width:44px;text-align:right!important}
.vtb__tbl td:last-child{text-align:right}
.vtb__tbl th:first-child,.vtb__tbl td:first-child{padding-left:0}.vtb__tbl th:last-child,.vtb__tbl td:last-child{padding-right:0}
.vtb__tm{font-weight:600;font-variant-numeric:tabular-nums}
.vtb__dl{color:#c23a0b;font-weight:600;font-size:12px;margin-left:3px}
.vtb__x .vtb__tm{text-decoration:line-through;color:#8a8883}
.vtb__note{color:#c23a0b;font-weight:600;font-size:12px;margin-left:6px}
.vtb__k{color:#5f5e5a}
.vtb__sp{display:inline-block;min-width:22px;text-align:center;font-weight:600;font-variant-numeric:tabular-nums}
.vtb__sp.is-new{background:#F5480F;color:#0a0a0a;border-radius:3px}
.vtb__tbl tr{transition:opacity .4s,transform .4s}
.vtb__tbl tr.is-weg{opacity:0;transform:translateX(-12px)}
.vtb__tbl tr.is-nieuw{animation:vtb-in .45s ease both}
.vtb__tbl tr.is-flash td{animation:vtb-flash 1.2s ease}
@keyframes vtb-in{from{opacity:0;transform:translateY(6px)}}
@keyframes vtb-flash{0%,40%{background:#fdebe4}}
.vtb__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
@container (max-width:470px){.vtb__srt,.vtb__tbl td.vtb__srt{display:none}}
@container (max-width:380px){.vtb__t span{display:none}}
@container (max-height:235px){.vtb__tbl tbody tr:nth-child(n+5){display:none}}
@media(prefers-reduced-motion:reduce){.vtb__tbl tr,.vtb__tbl tr.is-nieuw,.vtb__tbl tr.is-flash td{transition:none;animation:none}}// Vertrektijdenbord, licht · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
var w=root.querySelector('.vtb');if(!w)return;
var Q=[{t:874,n:'Den Haag Centraal',k:'Sprinter',s:'7'},{t:877,n:'Amsterdam Centraal',k:'Intercity',s:'12',d:3},{t:881,n:'Breda',k:'Intercity',s:'9'},{t:885,n:'Utrecht Centraal',k:'Intercity',s:'15',ns:'14'},{t:888,n:'Dordrecht',k:'Sprinter',s:'2',x:1},
{t:892,n:'Vlissingen',k:'Intercity',s:'11'},{t:895,n:'Gouda',k:'Sprinter',s:'4',d:2},{t:901,n:'Leiden Centraal',k:'Sprinter',s:'8'},{t:904,n:'Eindhoven Centraal',k:'Intercity',s:'15'},{t:908,n:'Schiedam Centrum',k:'Sprinter',s:'3'}];
var EV={876:[2,'d',2],883:[5,'ns','13'],890:[6,'d',4]};
var tb=w.querySelector('tbody'),clk=w.querySelector('.vtb__clk'),pp=w.querySelector('.vtb__pp'),live=w.querySelector('.vtb__live');
var now=872,qi=0,round=0,rows=[],timer=null,run=false,reduce=matchMedia('(prefers-reduced-motion:reduce)').matches;
function hhmm(m){m=((m%1440)+1440)%1440;var h=Math.floor(m/60),mm=m%60;return (h<10?'0':'')+h+':'+(mm<10?'0':'')+mm;}
function next(){var o=Object.assign({},Q[qi]);o.t+=round*40;o.id=round+'-'+qi;qi++;if(qi>=Q.length){qi=0;round++;}return o;}
function lab(o){return hhmm(o.t)+' '+o.k+' naar '+o.n+', spoor '+(o.ns||o.s)+(o.x?', rijdt niet':o.d?', '+o.d+' minuten later':'')+(o.ns?', spoorwijziging':'');}
function fill(tr,o){tr.className=o.x?'vtb__x':'';tr.innerHTML='<td><span class="vtb__tm">'+hhmm(o.t)+'</span>'+(o.d&&!o.x?'<span class="vtb__dl" aria-label="'+o.d+' minuten later">+'+o.d+'</span>':'')+'</td><td>'+o.n+(o.x?'<span class="vtb__note">Rijdt niet</span>':o.ns?'<span class="vtb__note">Ander spoor</span>':'')+'</td><td class="vtb__srt vtb__k">'+o.k+'</td><td><span class="vtb__sp'+(o.ns?' is-new':'')+'"'+(o.ns?' aria-label="spoor '+o.ns+', gewijzigd"':'')+'>'+(o.ns||o.s)+'</span></td>';}
function add(o,anim){var tr=document.createElement('tr');fill(tr,o);if(anim)tr.classList.add('is-nieuw');tb.appendChild(tr);rows.push({o:o,tr:tr});}
for(var i=0;i<6;i++)add(next(),false);
function tick(){now++;clk.textContent=hhmm(now);
var ev=EV[((now-872)%40)+872];if(ev){var r=rows.filter(function(x){return x.o.id===Math.floor((now-872)/40)+'-'+ev[0];})[0];if(r){r.o[ev[1]]=ev[2];fill(r.tr,r.o);if(r.o.x)r.tr.classList.add('vtb__x');r.tr.classList.remove('is-flash');void r.tr.offsetWidth;r.tr.classList.add('is-flash');live.textContent=ev[1]==='d'?r.o.k+' naar '+r.o.n+' vertrekt '+ev[2]+' minuten later.':r.o.k+' naar '+r.o.n+' vertrekt nu van spoor '+ev[2]+'.';}}
var gone=rows.filter(function(x){return x.o.t+(x.o.x?0:(x.o.d||0))<=now;});
gone.forEach(function(x){rows.splice(rows.indexOf(x),1);x.tr.classList.add('is-weg');setTimeout(function(){x.tr.remove();},reduce?0:420);add(next(),true);});}
function loop(){timer=setTimeout(function(){if(!run||!w.isConnected)return;tick();loop();},2400);}
function setRun(v){run=v;pp.textContent=v?'Pauze':'Afspelen';pp.setAttribute('aria-pressed',v?'false':'true');pp.setAttribute('aria-label',v?'Demoklok pauzeren':'Demoklok afspelen');clearTimeout(timer);if(v)loop();}
pp.addEventListener('click',function(){setRun(!run);});
clk.textContent=hhmm(now);
if(reduce){setRun(false);return;}
if(!('IntersectionObserver' in window)){setRun(true);return;}
var io=new IntersectionObserver(function(es){if(es[0].isIntersecting){io.disconnect();setRun(true);}},{threshold:.3});io.observe(w);
}
init(document.querySelector('.jouw-container'));
More numbers & charts
Seen something you like?
Add it to your selection and send it over. We build your site around it, in your brand.
Get in touch →