Skip to content

Instantly share code, notes, and snippets.

@landsurveyorsunited
Created April 22, 2021 00:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save landsurveyorsunited/6275c9f8ada790ea7be87ec9bbdacc10 to your computer and use it in GitHub Desktop.
Save landsurveyorsunited/6275c9f8ada790ea7be87ec9bbdacc10 to your computer and use it in GitHub Desktop.
Tape Measure Scrollbar
<main>
<section id="section-1">
<div class="main-title">
<h1>Scroll the page...</h1>
</div>
<a class="nav-anchor bottom" href="#section-2"><span class="mdi mdi-chevron-down"></span></a>
</section>
<section id="section-2">
<a class="nav-anchor top" href="#section-1"><span class="mdi mdi-chevron-up"></span></a>
<div class="main-title">
<h1>...or drag the tape measure!</h1>
</div>
<a class="nav-anchor bottom" href="#section-3"><span class="mdi mdi-chevron-down"></span></a>
</section>
<section id="section-3">
<a class="nav-anchor top" href="#section-2"><span class="mdi mdi-chevron-up"></span></a>
<div class="main-title">
<h1>It's responsive...</h1>
<small>(Resize the page)</small>
</div>
<a class="nav-anchor bottom" href="#section-4"><span class="mdi mdi-chevron-down"></span></a>
</section>
<section id="section-4">
<a class="nav-anchor top" href="#section-3"><span class="mdi mdi-chevron-up"></span></a>
<div class="main-title">
<h1>...and mobile-friendly<small>(ish)</small></h1>
</div>
</section>
</main>
<div id="progress">
<div id="tape-body"></div>
<div id="tape-measure"></div>
</div>
const tapeBody = document.getElementById("tape-body");
const tapeMeasure = document.getElementById("tape-measure");
const main = document.getElementsByTagName('main')[0];
const touch = "ontouchstart" in window ||
(window.DocumentTouch && document instanceof DocumentTouch);
let offsetY = 0;
const cache = {
viewport: {},
rects: [],
node: {}
};
// init
window.addEventListener("load", init);
function init() {
// update the cache and check scroll position
recache();
const barWidth = getScrollBarWidth();
main.style.paddingRight = `${barWidth}px`;
tapeBody.addEventListener(touch ? "touchstart" : "mousedown", down);
// throttle the scroll callback for performance
main.addEventListener("scroll", throttle(scrollCheck, 10));
// debounce the resize callback for performance
window.addEventListener("resize", debounce(recache, 50));
};
const move = (e) => {
const x = (touch ? e.touches[0].pageY : e.pageY) - offsetY;
const offset = getScrollOffset();
const height = cache.document.height - cache.viewport.height;
const width = cache.viewport.height - cache.node.height;
const progress = (x / width) * height;
main.scrollTop = progress;
}
const up = (e) => {
main.style.scrollBehavior = "";
if (touch) {
document.removeEventListener("touchmove", move);
document.removeEventListener("touchend", up);
document.removeEventListener("touchcancel", up);
} else {
document.removeEventListener("mousemove", move);
document.removeEventListener("mouseup", up);
}
}
const down = (e) => {
recache();
offsetY = (touch ? e.touches[0].pageY : e.pageY) - cache.node.top;
// setting scrollTop/Left with "scroll-behavior: smooth" causes monster jank
main.style.scrollBehavior = "auto";
if (touch) {
document.addEventListener("touchmove", move);
document.addEventListener("touchend", up);
document.addEventListener("touchcancel", up);
} else {
document.addEventListener("mousemove", move);
document.addEventListener("mouseup", up);
}
e.preventDefault();
}
// update the cache and check scroll position
function recache() {
// cache the viewport dimensions
cache.viewport = {
width: window.innerWidth,
height: window.innerHeight
};
cache.document = {
height: main.scrollHeight,
width: main.scrollWidth,
};
cache.node = tapeBody.getBoundingClientRect();
scrollCheck();
}
// check whether a node is at or above the horizontal halfway mark
function scrollCheck() {
// instead of relying on calling getBoundingClientRect() everytime,
// let's just take the cached value and subtract the pageYOffset value
// and see if the result is at or above the horizontal midline of the window
const offset = getScrollOffset();
const height = cache.document.height - cache.viewport.height;
const width = cache.viewport.height - cache.node.height;
const progress = (offset.y / height) * width;
tapeBody.style.transform = `translate3d(0, ${progress}px,0)`;
tapeMeasure.style.height = `${progress}px`;
};
// get the scroll offsets
function getScrollOffset() {
return {
x: main.scrollLeft,
y: main.scrollTop
};
};
// throttler
function throttle(fn, limit, context) {
let wait;
return function() {
context = context || this;
if (!wait) {
fn.apply(context, arguments);
wait = true;
return setTimeout(function() {
wait = false;
}, limit);
}
};
};
// debouncer
function debounce(fn, limit, u) {
let e;
return function() {
const i = this;
const o = arguments;
const a = u && !e;
clearTimeout(e),
(e = setTimeout(function() {
(e = null), u || fn.apply(i, o);
}, limit)),
a && fn.apply(i, o);
};
}
/**
* Get native scrollbar width
* @return {Number} Scrollbar width
*/
function getScrollBarWidth() {
const db = document.body;
const div = document.createElement("div");
let = 0;
return div.style.cssText = "width: 100; height: 100; overflow: scroll; position: absolute; top: -9999;", document.body.appendChild(div), t = div.offsetWidth - div.clientWidth, document.body.removeChild(div), t
};
$url: "https://s3-us-west-2.amazonaws.com/s.cdpn.io/86186/";
body {
overflow: hidden;
width: 100vw;
height: 100vh;
margin: 0;
}
main {
width: 100vw;
height: 100vh;
overflow-x: hidden;
overflow-y: scroll;
box-sizing: content-box;
scroll-behavior: smooth;
section {
position: relative;
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background-size: cover;
background-position: center center;
&:nth-child(1) {
background-image: url("#{$url}wildtextures-dark-wood-board.jpg");
.nav-anchor {
animation: 1000ms ease 0ms forwards infinite notice-me;
}
}
&:nth-child(2) {
background-image: url("#{$url}DSC04170.jpg");
.main-title {
color: rgba(255, 255, 255, .85);
}
}
&:nth-child(3) {
background-image: url("#{$url}wildtextures-scrap-wooden-table-top.jpg");
}
&:nth-child(4) {
background-image: url("#{$url}Wood-Texture-6.jpg");
.main-title {
color: rgba(255, 255, 255, .85);
}
}
}
}
.main-title {
font-size: 3vw;
font-family: "Alfa Slab One";
color: rgba(26, 26, 26, .85);
text-align: center;
}
h1 small {
font-size: 22px;
}
.nav-anchor {
position: absolute;
color: #fff;
font-size: 50px;
&.top {
top: 10px;
}
&.bottom {
bottom: 10px;
}
}
#progress {
position: fixed;
bottom: 0;
right: 0;
width: 50px;
height: 100%;
}
#tape-body {
background-image: url("#{$url}tape2.png");
position: absolute;
right: 0;
top: 0;
width: 50px;
height: 76px;
background-size: cover;
z-index: 10;
cursor: grab;
}
#tape-measure {
position: absolute;
right: 12px;
top: 0;
height: 0;
width: 24px;
background-color: #fada34;
z-index: 9;
background-image: url("#{$url}tape2.jpg");
background-size: 100% auto;
}
@keyframes notice-me {
0%, 100% {
transform: translate3d(0,-5px,0);
}
50% {
transform: translate3d(0,5px,0);
}
}
<link href="https://cdn.materialdesignicons.com/2.3.54/css/materialdesignicons.min.css" rel="stylesheet" />

Tape Measure Scrollbar

Simple tape measure scrollbar. Responsive (resize the page).

Might be janky on mobile at the moment.

A Pen by JFarrow on CodePen.

License.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment