Create a custom scrollbar
Written byPhuoc Nguyen
Category
Level 3 — Advanced
Created
03 May, 2020
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;
/* The color of thumb and track areas */
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.
To do so, we will use the third approach mentioned in the Position an element absolutely to another element post:
<div id="wrapper">...</div>
<!-- Use an anchor -->
<div id="anchor" style="left: 0; position: absolute; top: 0"></div>
<!-- Fake scrollbar -->
<div id="scrollbar" style="position: absolute; width: .75rem;"></div>
The scrollbar is shown at the desired position by setting the
`top`
and `left`
styles:// Query elements
const wrapper = document.getElementById('wrapper');
const content = document.getElementById('content');
const anchor = document.getElementById('anchor');
const scrollbar = document.getElementById('scrollbar');
// Get the bounding rectangles
const wrapperRect = wrapper.getBoundingClientRect();
const anchorRect = anchor.getBoundingClientRect();
// Set the scrollbar position
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;
/* Take full size */
height: 100%;
width: 100%;
}
.thumb {
left: 0;
position: absolute;
/* Take full width */
width: 100%;
}
Initially, the thumb's height is calculated based on the ratio between normal and scroll heights of the content element:
// Query 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 = {
// The current scroll
top: content.scrollTop,
// Get the current mouse position
y: e.clientY,
};
document.addEventListener('mousemove', mouseMoveHandler);
document.addEventListener('mouseup', mouseUpHandler);
};
const mouseMoveHandler = function (e) {
// How far the mouse has been moved
const dy = e.clientY - pos.y;
// Scroll the content
content.scrollTop = pos.top + dy / scrollRatio;
};
// Attach the `mousedown` event handler
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
#See also
Questions? 🙋
Do you have any questions? Not just about this specific post, but about any topic in front-end development that you'd like to learn more about? If so, feel free to send me a message on Twitter or send me an email. You can find them at the bottom of this page.
I have a long list of upcoming posts, but your questions or ideas for the next one will be my top priority. Let's learn together! Sharing knowledge is the best way to grow 🥷.
Recent posts ⚡
Copy the content of an element to your clipboard
01 Oct, 2023
Make a text area fit its content automatically
30 Sep, 2023
Quickly insert alternate characters while typing
30 Sep, 2023
Zebra-like background
30 Sep, 2023
Add autocomplete to your text area
28 Sep, 2023
Linear scale of a number between two ranges
28 Sep, 2023
Highlight the current line in a text area
27 Sep, 2023
Create your own custom cursor in a text area
27 Sep, 2023
Mirror a text area for improving user experience
26 Sep, 2023
Display the line numbers in a text area
24 Sep, 2023
Select a given line in a text area
24 Sep, 2023
Newsletter 🔔
If you're into front-end technologies and you want to see more of the content I'm creating, then you might want to consider subscribing to my newsletter.
By subscribing, you'll be the first to know about new articles, products, and exclusive promotions.
Don't worry, I won't spam you. And if you ever change your mind, you can unsubscribe at any time.
Phước Nguyễn