Wereldbol met vestigingen New
A rotating dotted globe on canvas 2D (orthographic projection, 144 x 72 land mask from Natural Earth, public domain) with pins for branches. Click a branch in the list and the globe turns to it and draws the line from head office. Drag or arrow keys rotate it yourself; still with reduced motion and when off screen. Works without WebGL, so everywhere: for logistics, wholesale and companies with offices abroad.
Vrachtlijn op 6 plekken
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.
<!-- Wereldbol met vestigingen · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="wbv"><div class="wbv__in">
<canvas class="wbv__cv" tabindex="0" role="img" aria-label="Wereldbol met zes vestigingen van Vrachtlijn. Sleep of gebruik de pijltjestoetsen om te draaien."></canvas>
<div class="wbv__side">
<b class="wbv__h">Vrachtlijn op 6 plekken</b>
<ul class="wbv__list">
<li><button class="wbv__it" type="button" data-i="0" aria-pressed="false">Rotterdam<span>hoofdkantoor</span></button></li>
<li><button class="wbv__it" type="button" data-i="1" aria-pressed="false">Hamburg<span>havenkantoor</span></button></li>
<li><button class="wbv__it" type="button" data-i="2" aria-pressed="false">Gdańsk<span>overslag</span></button></li>
<li><button class="wbv__it" type="button" data-i="3" aria-pressed="false">Valencia<span>douane</span></button></li>
<li><button class="wbv__it" type="button" data-i="4" aria-pressed="false">New York<span>verkoop</span></button></li>
<li><button class="wbv__it" type="button" data-i="5" aria-pressed="false">Singapore<span>Azië</span></button></li>
</ul>
</div>
</div></div>
</div>/* Wereldbol met vestigingen · NedDev · eigen werk. */
.wbv{position:absolute;inset:0;background:#f4f3f1;color:#0a0a0a;font-family:'Switzer',sans-serif;text-align:left;container-type:inline-size}
.wbv__in{position:absolute;inset:0;display:grid;grid-template-columns:1fr 176px}
.wbv__cv{display:block;width:100%;height:100%;cursor:grab;touch-action:pan-y;outline:none}
.wbv__cv:active{cursor:grabbing}
.wbv__cv:focus-visible{box-shadow:inset 0 0 0 2px #F5480F}
.wbv__side{display:flex;flex-direction:column;justify-content:center;padding:10px 12px 10px 0;min-width:0}
.wbv__h{font:600 14px/1.2 'Switzer',sans-serif;margin:0 0 6px 8px}
.wbv__list{list-style:none;margin:0;padding:0}
.wbv__it{display:flex;align-items:baseline;justify-content:space-between;gap:6px;width:100%;min-height:26px;padding:4px 8px;border:0;border-radius:4px;background:transparent;color:#0a0a0a;font:500 13px/1.2 'Switzer',sans-serif;text-align:left;cursor:pointer}
.wbv__it span{font-weight:400;font-size:12px;color:#5c5a57;white-space:nowrap}
.wbv__it:hover{background:#e9e6e1}
.wbv__it[aria-pressed="true"]{background:#0a0a0a;color:#fff}
.wbv__it[aria-pressed="true"] span{color:#d6d3ce}
.wbv__it:focus-visible{outline:2px solid #F5480F;outline-offset:1px}
@container (max-width:370px){.wbv__in{grid-template-columns:1fr 124px}.wbv__it span{display:none}.wbv__h{font-size:13px}}// Wereldbol met vestigingen · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
var w=root.querySelector('.wbv');if(!w)return;
var cv=w.querySelector('.wbv__cv'),ctx=cv.getContext('2d'),items=[].slice.call(w.querySelectorAll('.wbv__it'));
var rm=matchMedia('(prefers-reduced-motion: reduce)').matches;
/* landmasker 144 x 72 (2,5 graden per cel), rij 0 = 90N, kolom 0 = 180W; 1 bit per cel, base64 */
var M='AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/5///gAAAAAAAAAAAAAAAAEz/P//8ABwAAAADAAAAAAAABKZAP//8AAAAA4AH4AAAAAAADwDQAP/8AAAAGAH/4AIAAgEABfG/gH/4AAAAGHf//9/gAB///1Ds4H/gAAf4F6///////8/////w+H8BAB/0/////////AP////UcHwCAD3/////////+A////8BgDgAAPn////////74AeB//+B6AAAAPn///////4CAAIAf//h+AAACFP///////gOAAAAP//7/gAANH////////wMAAAAH////wAAD/////////wAAAAAD///04AAD/////////wAAAAAD///8AAAB//X//////gAAAAAD///wAAAB6+D//////EAAAAAD///gAAAPhvb/////8IAAAAAB///AAAAPAp/////+IIAAAAAB///AAAAA8Af/////EwAAAAAAf/+AAAAH8AP/////BAAAAAAAX/4AAAAP/uv/////gAAAAAAAP8EAAAAP///v////gAAAAAAAD4EAAAA///3z////AAAAAAAAB4AAAAA///3+H//+gAAAAAAAA4gAAAB///7/D+fgAAAAAAAAA9gYAAA///7+B8PQAAAAAAAAAHgAAAB///94B4HggAAAAAAAAA4AAAB////gAwHwwAAAAAAAAAAoAAA///+QAwFgAAAAAAAAAAF/AAAf///wAAEAIAAAAAAAAAB/gAAPv//wAICAAAAAAAAAAAB/8AAAD//gAAGGAAAAAAAAAAD/8AAAD//AAADOAAAAAAAAAAD//AAAD/+AAADOjAAAAAAAAAD//4AAD/8AAABAg8AAAAAAAAD//8AAB/8AAAAgAeAAAAAAAAD//8AAB/8AAAABoIAAAAAAAAB//4AAB/8AAAAAAAAAAAAAAAB//wAAB/8QAAAADAAAAAAAAAAf/wAAB/8wAAAAfkAAAAAAAAAP/wAAB/wwAAAAf+AAAAAAAAAP/wAAB/xgAAAD//AAAAAAAAAP/AAAA/xgAAAH//AAAAAAAAAP+AAAA/gAAAAH//gAAAAAAAAf8AAAAfgAAAAD//gAAAAAAAAf8AAAAfAAAAAD//gAAAAAAAAf4AAAAcAAAAADw/gAAAAAAAAfgAAAAAAAAAAAAPAAAAAAAAAfAAAAAAAAAAAAACACAAAAAAA8AAAAAAAAAAAAACAEAAAAAAAcAAAAAAAAAAAAAAAIAAAAAAA4AAAAAAAAAAAAAAAQAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAwgAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////';
var bin=atob(M),GW=144,GH=72,pts=[];
for(var r=0;r<GH;r++){var lat=90-(r+.5)*180/GH,step=Math.max(1,Math.round(1/Math.max(.15,Math.cos(lat*Math.PI/180))));
for(var c=0;c<GW;c+=step){var i=r*GW+c;if(bin.charCodeAt(i>>3)&(128>>(i&7)))pts.push([lat*Math.PI/180,(-180+(c+.5)*360/GW)*Math.PI/180]);}}
var P=[[51.92,4.48],[53.55,9.99],[54.35,18.65],[39.47,-.38],[40.71,-74.0],[1.35,103.82]].map(function(p){return[p[0]*Math.PI/180,p[1]*Math.PI/180];});
var lon0=-0.1,lat0=0.62,sel=-1,W=0,H=0,R=0,dpr=1,t0=performance.now(),vis=true,drag=false,lx=0,ly=0,lastInt=-1e9,anim=null;
function size(){dpr=Math.min(2,window.devicePixelRatio||1);W=cv.clientWidth;H=cv.clientHeight;cv.width=Math.round(W*dpr);cv.height=Math.round(H*dpr);R=Math.min(W,H)*.43;}
function proj(la,lo){var cl=Math.cos(la),d=lo-lon0;return[cl*Math.sin(d),Math.cos(lat0)*Math.sin(la)-Math.sin(lat0)*cl*Math.cos(d),Math.sin(lat0)*Math.sin(la)+Math.cos(lat0)*cl*Math.cos(d)];}
function draw(now){ctx.setTransform(dpr,0,0,dpr,0,0);ctx.clearRect(0,0,W,H);var cx=W/2,cy=H/2;
ctx.fillStyle='#fff';ctx.beginPath();ctx.arc(cx,cy,R,0,6.2832);ctx.fill();ctx.strokeStyle='rgba(10,10,10,.18)';ctx.lineWidth=1;ctx.stroke();
var ds=Math.max(1.3,R/70);
for(var k=0;k<pts.length;k++){var q=proj(pts[k][0],pts[k][1]);if(q[2]<=0.02)continue;ctx.globalAlpha=.25+.6*q[2];ctx.fillStyle='#0a0a0a';ctx.fillRect(cx+q[0]*R-ds/2,cy-q[1]*R-ds/2,ds,ds);}
ctx.globalAlpha=1;
if(sel>0){var a=P[0],b=P[sel];/* grootcirkel van hoofdkantoor naar keuze, licht boven het oppervlak */
var va=[Math.cos(a[0])*Math.cos(a[1]),Math.cos(a[0])*Math.sin(a[1]),Math.sin(a[0])],vb=[Math.cos(b[0])*Math.cos(b[1]),Math.cos(b[0])*Math.sin(b[1]),Math.sin(b[0])];
var om=Math.acos(Math.min(1,va[0]*vb[0]+va[1]*vb[1]+va[2]*vb[2])),prog=anim?Math.min(1,(now-anim)/900):1;
ctx.strokeStyle='#F5480F';ctx.lineWidth=2;ctx.beginPath();var started=false;
for(var s=0;s<=60*prog;s++){var t=s/60,sa=Math.sin((1-t)*om)/Math.sin(om),sb=Math.sin(t*om)/Math.sin(om),x=sa*va[0]+sb*vb[0],y=sa*va[1]+sb*vb[1],z=sa*va[2]+sb*vb[2];
var la=Math.asin(z),lo=Math.atan2(y,x),q2=proj(la,lo),h=1+.12*Math.sin(Math.PI*t)*Math.min(1,om);if(q2[2]<-0.1){started=false;continue;}
var X=cx+q2[0]*R*h,Y=cy-q2[1]*R*h;if(!started){ctx.moveTo(X,Y);started=true;}else ctx.lineTo(X,Y);}ctx.stroke();}
for(var j=0;j<P.length;j++){var p=proj(P[j][0],P[j][1]);if(p[2]<=0)continue;var X2=cx+p[0]*R,Y2=cy-p[1]*R,on=j===sel;
if(!rm){var ph=((now/1600)+j*.17)%1;ctx.strokeStyle='rgba(245,72,15,'+(0.6*(1-ph)).toFixed(3)+')';ctx.lineWidth=1.5;ctx.beginPath();ctx.arc(X2,Y2,4+ph*(on?16:10),0,6.2832);ctx.stroke();}
ctx.fillStyle=on||j===0?'#F5480F':'#0a0a0a';ctx.beginPath();ctx.arc(X2,Y2,on?6:4,0,6.2832);ctx.fill();ctx.strokeStyle='#fff';ctx.lineWidth=1.5;ctx.stroke();}}
function ease(t){return 1-Math.pow(1-t,3);}
var tw=null;function turnTo(i){var tl=P[i][1],tla=Math.max(-.5,Math.min(1,P[i][0]));var d=((tl-lon0)%6.2832+9.4248)%6.2832-3.1416;
var sL=lon0,sA=lat0,st=performance.now();anim=st;if(rm){lon0=sL+d;lat0=tla;tw=null;return;}tw={f:function(now){var t=Math.min(1,(now-st)/900),e=ease(t);lon0=sL+d*e;lat0=sA+(tla-sA)*e;if(t>=1)tw=null;}};}
items.forEach(function(b,i){b.addEventListener('click',function(){sel=sel===i?-1:i;items.forEach(function(x,k){x.setAttribute('aria-pressed',k===sel?'true':'false');});lastInt=performance.now();if(sel>=0)turnTo(sel);if(rm)draw(performance.now());});});
cv.addEventListener('pointerdown',function(e){drag=true;tw=null;lx=e.clientX;ly=e.clientY;try{cv.setPointerCapture(e.pointerId);}catch(_){}});
cv.addEventListener('pointermove',function(e){if(!drag)return;lon0-=(e.clientX-lx)/R;lat0=Math.max(-1,Math.min(1.2,lat0+(e.clientY-ly)/R));lx=e.clientX;ly=e.clientY;lastInt=performance.now();if(rm)draw(lastInt);});
function up(){drag=false;lastInt=performance.now();}cv.addEventListener('pointerup',up);cv.addEventListener('pointercancel',up);
cv.addEventListener('keydown',function(e){var k=e.key;if(k==='ArrowLeft')lon0+=.15;else if(k==='ArrowRight')lon0-=.15;else if(k==='ArrowUp')lat0=Math.min(1.2,lat0+.12);else if(k==='ArrowDown')lat0=Math.max(-1,lat0-.12);else return;e.preventDefault();tw=null;lastInt=performance.now();if(rm)draw(lastInt);});
var last=performance.now();
function frame(now){requestAnimationFrame(frame);if(!vis)return;var dt=Math.min(50,now-last);last=now;if(tw)tw.f(now);else if(!drag&&sel<0&&now-lastInt>2500)lon0+=dt*0.00012;draw(now);}
size();
if('ResizeObserver' in window)new ResizeObserver(function(){size();draw(performance.now());}).observe(cv);
if(rm){draw(performance.now());}else{new IntersectionObserver(function(es){es.forEach(function(e){vis=e.isIntersecting;});}).observe(w);requestAnimationFrame(frame);}
}
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 →