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 is-up" 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 click', function(){
    $el.scrollTop($el.scrollTop() - 1);
    if ($up.filter(':hover').length) {
      setTimeout(function(){
        $up.click();
      }, mouseoverDelay);
    }
  });
  
  $down.on('mouseover click', function(){
    $el.scrollTop($el.scrollTop() + 1);
    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>');
}