JSFiddle - React, Tailwind, and code Playground
by Константин Дралюк
HTML
<div class="app">
<div class="scroll-area-scroll-up" data-apply-js="scrollbar-scroll-up">up</div>
<div class="scroll-area">
<div class="scroll-area__inner" data-apply-js="scrollbar">
<div class="scroll-area__body"></div>
</div>
</div>
<div class="scroll-area-scroll-up" data-apply-js="scrollbar-scroll-down">down</div>
</div>
CSS
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
body {
margin: 0;
}
.scroll-area-scroll-up,
.scroll-area-scroll-down {
display: table;
margin: 0 auto;
width: 80px;
text-align: center;
background: #fff;
padding: 5px 10px;
cursor: pointer;
}
.scroll-area-scroll-up:hover,
.scroll-area-scroll-down:hover {
font-weight: bold;
}
.scroll-area-scroll-up.disabled,
.scroll-area-scroll-down.disabled {
visibility: hidden;
}
.scroll-area {
height: 600px;
width: 100%;
position: relative;
overflow: hidden;
background: #fff;
text-align: center;
}
.scroll-area__inner {
height: 100%;
margin-right: -30px;
overflow: auto;
}
.scroll-area__inner:after,
.scroll-area__inner:before {
content: '';
position: absolute;
left: 0;
right: 0;
height: 50px;
transition: .15s ease;
}
.scroll-area__inner:before {
top: 0;
box-shadow: inset 0 100px 50px -62px #fff;
}
.scroll-area__inner:after {
bottom: 0;
box-shadow: inset 0 -100px 50px -62px #fff;
}
.scroll-area__inner.is-up:before {
top: -50px;
}
.scroll-area__inner.is-down:after {
bottom: -50px;
}
.scroll-area__body {
padding-right: 30px;
overflow: hidden;
}
span {
height: 50px;
line-height: 50px;
font-size: 30px;
display: block;
transition: .1s ease;
cursor: pointer;
}
span:hover {
font-weight: bold;
font-size: 32px;
}
JavaScript
(function(){
var $el = $('[data-apply-js="scrollbar"]'),
$up = $('[data-apply-js="scrollbar-scroll-up"]'),
$down = $('[data-apply-js="scrollbar-scroll-down"]'),
initialStep = 50,
lastScrollTop = 0,
scrollDelay = 25,
mouseoverDelay = 150,
round = function(number, to){
return Math.round(number / to) * to
};
(function justifyScrollPosition(){
$el.one('scroll', function(){
var currentScrollTop = $el.scrollTop(),
step = initialStep;
if (currentScrollTop < lastScrollTop) {
step = -initialStep;
} else {
step = initialStep;
}
$el.scrollTop(round(currentScrollTop, initialStep) + step);
setTimeout(function(){
justifyScrollPosition();
}, scrollDelay);
lastScrollTop = $el.scrollTop();
if (round(lastScrollTop, initialStep) === 0) {
$el.addClass('is-up').removeClass('is-down');
$up.addClass('disabled');
} else if (lastScrollTop === $el[0].scrollHeight - $el.height()) {
$el.addClass('is-down').removeClass('is-up');
$down.addClass('disabled');
} else {
$el.removeClass('is-up is-down');
$up.removeClass('disabled');
$down.removeClass('disabled');
}
});
})();
$up.on('mouseover touch', function(){
$el.scrollTop($el.scrollTop() - 20);
if ($up.filter(':hover').length) {
setTimeout(function(){
$up.trigger('mouseover');
}, mouseoverDelay);
}
});
$down.on('mouseover click', function(){
$el.scrollTop($el.scrollTop() + 20);
if ($down.filter(':hover').length) {
setTimeout(function(){
$down.click();
}, mouseoverDelay);
}
});
$el.trigger('scroll');
})();
// Demo data
for(var i = 0; i<20; i++) {
$('.scroll-area__body').append('<span>SMZU396V1AI' + i + '</span>');
}