<!DOCTYPE html> <html> <head> <style> body { margin:0; } section { position:relative; height: 100vh; box-sizing: border-box; } nav { position:fixed; right:50px; bottom:50px; z-index:10; } h2 { width:500px; margin:0 auto; text-align:center; } h2[data-spy='true'] { background:black; color: white;; } </style> </head> <body> <nav> <a href="#top" >Top</a> <a href="#order">Order</a> <a href="#video">Video</a> <a href="#about">About</a> <a href="#specs">Specs</a> <a href="#email">Sign-up</a> </nav> <section id="video"> <h2 data-spy="0.2|0.8">Videos</h2> </section> <section id="about"> <h2 data-spy="0.2|0.8">About</h2> </section> <section id="specs"> <h2 data-spy="0.2|0.8">Specs</h2> </section> <script> var Time = { Jobs:[], Stamp:false, Queue:false, Loop:false, Add:function(inJob) { if(Time.Loop) { console.log("cannot modify queue while processing"); return; } if(!Time.Queue) { window.requestAnimationFrame(Time.Update); } Time.Queue = true; Time.Jobs.push(inJob); }, Remove:function(inJob) { if(Time.Loop) { console.log("cannot modify queue while processing"); return; } var index = Time.Jobs.indexOf(inJob); if(index > -1) { Time.Jobs.splice(index, 1); } }, Update:function(inTimestamp) { var delta; var i; if(!Time.Stamp) { Time.Stamp = inTimestamp; } delta = inTimestamp - Time.Stamp; Time.Stamp = inTimestamp; Time.Loop = true; for(i=0; i<Time.Jobs.length; i++) { if(Time.Jobs[i](delta) === false) { Time.Jobs.splice(i, 1); i--; } } Time.Loop = false; if(Time.Jobs.length > 0) { window.requestAnimationFrame(Time.Update); } else { Time.Stamp = false; Time.Queue = false; } } }; function JobDuration(inDuration, inHandler, inDone) { var timeCurrent = 0; var timeLimit = inDuration*1000; var timeRelative = 0; var timeMaxed = false; return function(inDelta) { timeCurrent += inDelta; timeMaxed = timeCurrent > timeLimit; if(timeMaxed) { inHandler(1); inDone(); return false; } return inHandler(timeCurrent / timeLimit); }; } document.querySelector("nav").addEventListener("click", function(inEvent) { var domHtml = document.querySelector("html"); var domGoal = document.querySelector(inEvent.target.getAttribute("href")); var posStart = domHtml.scrollTop - 200; var posRange = domGoal.getBoundingClientRect().top; var evtTick = function(inProgress){ domHtml.scrollTop = posStart + posRange*Math.sqrt(1 - Math.pow(1-(inProgress), 2)); }; var evtDone = function(){ Spy.Resume(); }; Spy.Suspend(); Time.Add( JobDuration(0.4, evtTick, evtDone) ); inEvent.preventDefault(); }); </script> <script> var Spy = { Attribute:"data-spy", Members:[], Defaults:[0, 1, 0, 1], Disabled:false, Suspend:function() { Spy.Disabled = true; }, Resume:function() { Spy.Disabled = false; Spy.UpdateAll(); }, UpdateAll:function() { var i, member, aabb, top, bottom, left, right; var visible; if(Spy.Disabled){ return; } for(i=0; i<Spy.Members.length; i++) { member = Spy.Members[i]; top = window.innerHeight * member.Bounds[0]; bottom = window.innerHeight * member.Bounds[1]; left = window.innerWidth * member.Bounds[2]; right = window.innerWidth * member.Bounds[3]; aabb = member.Element.getBoundingClientRect(); visible = (aabb.top < bottom && aabb.bottom > top) && (aabb.left < right && aabb.right > left); if(visible != member.Visible) { member.Element.setAttribute(Spy.Attribute, visible); member.Visible = visible; member.Change(visible); } } }, Create:function(inElement) { var j, bounds; var attr; var obj; attr = inElement.getAttribute(Spy.Attribute)||""; inElement.removeAttribute(Spy.Attribute); bounds = attr.split("|"); for(j=0; j<Spy.Defaults.length; j++) { if(bounds[j]) { bounds[j] = parseFloat(bounds[j]); } else { bounds[j] = Spy.Defaults[j]; } } obj = { Element:inElement, Bounds:bounds, Visible:undefined, Change:function(){} }; Spy.Members.push(obj); return obj; }, CreateAll:function() { var i, elements; elements = document.querySelectorAll("*["+Spy.Attribute+"]"); for(i=0; i<elements.length; i++) { Spy.Create(elements[i]); } }, Init:function() { Spy.CreateAll(); Spy.UpdateAll(); document.addEventListener("scroll", Spy.UpdateAll, {passive:true}); window.addEventListener("resize", Spy.UpdateAll, {passive:true}); } }; Spy.Init(); </script> </body> </html>