JSFiddle - React, Tailwind, and code Playground
HTML
<div id="frame" tabIndex="0">
<div id="content">
<div id="scrollable">
...
CSS
body {
overflow: hidden;
}
#frame {
width: 80%;
height: 300px;
margin: 0px auto 0px auto;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: stretch;
flex-wrap: nowrap;
}
#content {
flex-grow: 1;
flex-basis: 1px;
position: relative;
background-color: #e0e0e0;
overflow: hidden;
}
#scrollable {
position: absolute;
top: 0px;
left: 0px;
}
#scrollbar {
flex-grow: 0;
flex-basis: 20px;
position: relative;
width: 20px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex-wrap: nowrap;
}
#rail {
flex-grow: 1;
flex-basis: 1px;
width: 10px;
background-color: #f0f0f0;
border-radius: 4px;
}
#dragger {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
}
#bar {
width: 10px;
height: 30px;
margin: 0px auto 0px auto;
background-color: red;
border-radius: 4px;
}
#hook {
position: absolute;
top: 0px;
left: 0px;
width: 0px;
height: 0px;
}
.drag-on #hook {
position: absolute;
top: -1500px;
left: -1500px;
width: 3000px;
height: 3000px;
z-index: 255;
}
JavaScript
var model = {
dragOn: false,
scrollMaxV: 7.0, // スクロールの最大速度
scrollMinV: 0.25, // スクロールの最小速度
scrollVRatio: 0.03, // スクロール速度係数
scrollUnit: 10, // ホイールイベントやキーダウンイベントでスクロールバーを動かす量
scrollY: 0, // scrollableの現在のY座標
posY: 0, // スクロールバーの現在のY座標
cursorY: 0 // スクロールバーのドラッグ中に、最後に記録したマウスカーソルのY座標
};
function updateUI() {
dragMode();
posDragger();
updateContentPos();
}
function dragMode() {
if (model.dragOn) {
$('#dragger').addClass('drag-on');
} else {
$('#dragger').removeClass('drag-on');
}
}
function posDragger() {
var elem = $('#dragger');
elem.css("top", model.posY + "px");
}
function updateContentPos() {
// model.posYと同じスクロール比率になるまでスクロールする。
// スクロール速度は、posY - scrollYの10%にする。
// 現時点でのスクロール目標座標を算出
var scrollRatio = model.posY / ($('#scrollbar').height() - $('#bar').height());
var destinationPos = -1 * ($('#scrollable').height() - $('#content').height()) * scrollRatio;
var distance = destinationPos - model.scrollY;
if (Math.abs(distance) > 2) { // 2px以内はスクロールを完了させる
// スクロール速度は移動先座標までの距離に比例
var v = distance * model.scrollVRatio;
// 最大・最小速度に制限を掛ける
if (Math.abs(v) < model.scrollMinV)
v = v > 0 ? model.scrollMinV : -1 * model.scrollMinV;
else if (model.scrollMaxV < Math.abs(v))
v = v > 0 ? model.scrollMaxV : -1 * model.scrollMaxV;
console.log(v);
model.scrollY += v;
$('#scrollable').css("top", model.scrollY + "px");
requestAnimationFrame(updateContentPos);
} else {
model.scrollY = destinationPos;
$('#scrollable').css("top", model.scrollY + "px");
}
}
function limitPosY() {
if(model.posY < 0) {
model.posY = 0;
return;
}
var maxY = $('#scrollbar').height() - $('#bar').height();
if (maxY < model.posY) {
model.posY = maxY;
return;
}
}
// vが正の値なら下へ、0又は負の値なら上へスクロール
function scroll(v) {
...