Create a custom scrollbar
You can customize the look and feel of the browser's scrollbar by changing some CSS properties.
For example, we can use the :-webkit-scrollbar
styles on the latest version of Chrome, Edge and Safari:
body::-webkit-scrollbar {
width: 0.75rem;
}
*::-webkit-scrollbar-track {
background-color: #edf2f7;
}
*::-webkit-scrollbar-thumb {
background-color: #718096;
border-radius: 9999px;
}
On Firefox, we can use the new scrollbar-width
and scrollbar-color
styles:
body {
scrollbar-width: thin;
scrollbar-color: #718096 #edf2f7;
}
Unfortunately, the
-webkit-scrollbar
styles
aren't standard and isn't recommended to use on production sites.
In this post, you'll see how to hide the default scrollbar and create a fake, customizable scrollbar. Assume that our target is a scrollable element whose height
or max-height
style is set:
<div id="content" class="content" style="overflow: auto; max-height: ...;">...</div>
Hide the default scrollbar
We wrap the content in a container which has the same height
or max-height
as the content. Instead of setting max height for the content, it'll take the full height.
<div id="wrapper" class="wrapper">
<div id="content" class="content">...</div>
</div>
We block the scrolling in the wrapper and still allow user to scroll in the content:
.wrapper {
max-height: 32rem;
overflow: hidden;
}
.content {
height: 100%;
overflow: auto;
}
It's easy to hide the default scrollbar by using a negative margin:
.content {
margin-right: -1rem;
padding-right: 1rem;
}
Position the fake scrollbar
In this step, we'll create an element representing the fake scrollbar. It'll be positioned at the right side of the wrapper, and has the same height as wrapper.
<div id="wrapper">...</div>
<div id="anchor" style="left: 0; position: absolute; top: 0"></div>
<div id="scrollbar" style="position: absolute; width: .75rem;"></div>
The scrollbar is shown at the desired position by setting the top
and left
styles:
const wrapper = document.getElementById('wrapper');
const content = document.getElementById('content');
const anchor = document.getElementById('anchor');
const scrollbar = document.getElementById('scrollbar');
const wrapperRect = wrapper.getBoundingClientRect();
const anchorRect = anchor.getBoundingClientRect();
const top = wrapperRect.top - anchorRect.top;
const left = wrapperRect.width + wrapperRect.left - anchorRect.left;
scrollbar.style.top = `${top}px`;
scrollbar.style.left = `${left}px`;
The scrollbar has the same height as the wrapper:
scrollbar.style.height = `${wrapperRect.height}px`;
Organize the scrollbar
The scrollbar consists of two parts:
- A track element that lets user know that there's a scrollbar. It takes the full size of scrollbar
- A thumb element that user can click on and drag to scroll
<div id="scrollbar">
<div id="track" class="track"></div>
<div id="thumb" class="thumb"></div>
</div>
These parts are positioned absolutely to the scrollbar, therefore they have the following styles:
.track {
left: 0;
position: absolute;
top: 0;
height: 100%;
width: 100%;
}
.thumb {
left: 0;
position: absolute;
width: 100%;
}
Initially, the thumb's height is calculated based on the ratio between normal and scroll
heights of the content element:
const track = document.getElementById('track');
const thumb = document.getElementById('thumb');
const scrollRatio = content.clientHeight / content.scrollHeight;
thumb.style.height = `${scrollRatio * 100}%`;
Drag the thumb to scroll
Please visit the
Drag to scroll post to see the details. Below is the implementation in our use case:
let pos = { top: 0, y: 0 };
const mouseDownThumbHandler = function (e) {
pos = {
top: content.scrollTop,
y: e.clientY,
};
document.addEventListener('mousemove', mouseMoveHandler);
document.addEventListener('mouseup', mouseUpHandler);
};
const mouseMoveHandler = function (e) {
const dy = e.clientY - pos.y;
content.scrollTop = pos.top + dy / scrollRatio;
};
thumb.addEventListener('mousedown', mouseDownThumbHandler);
When user drags the thumb element as well as scroll the content element, we have to update the position of the thumb element.
Here is the scroll
event handler of the content element:
const scrollContentHandler = function () {
window.requestAnimationFrame(function () {
thumb.style.top = `${(content.scrollTop * 100) / content.scrollHeight}%`;
});
};
content.addEventListener('scroll', scrollContentHandler);
Jump when clicking the track
There is another way to scroll. User can jump in the content element by clicking a particular point in the track element.
Again, we have to calculate and update the scrollTop
property for the content element:
const trackClickHandler = function (e) {
const bound = track.getBoundingClientRect();
const percentage = (e.clientY - bound.top) / bound.height;
content.scrollTop = percentage * (content.scrollHeight - content.clientHeight);
};
track.addEventListener('click', trackClickHandler);
I hope this post isn't too long and you can follow until here. Following is the final demo. Enjoy!
Demo
Create a custom scrollbar
See also