console
let cvs = document.querySelector('canvas');
let ctx = cvs.getContext('2d');
let root = document.querySelector("#root");
let vscrollbar = document.querySelector('.vscrollbar');
let texts = [];
let lineHeight = 30;
let begin = 0;
let gutter = {}
let gutterWidth = 0;
cvs.width = root.clientWidth;
cvs.height = root.clientHeight;
function appendLine(line) {
texts.push(line);
vscrollbar.querySelector('.innerHeight').style.height = texts.length * lineHeight + 'px';
}
for (let i = 0; i < 100000; i++) {
appendLine(`我是第${i + 1}个句子`);
}
function onMouseMove(e) {
let rect = cvs.getBoundingClientRect();
let x = e.clientX - rect.left;
let gutterWidth = getGutterWidth();
if(x <= gutterWidth) {
cvs.classList.add('c-default');
cvs.classList.remove('c-text');
} else {
cvs.classList.remove('c-default');
cvs.classList.add('c-text');
}
}
function getVisiblePart() {
let a = parseInt(root.clientHeight / lineHeight) + 1;
let b = begin;
let e = begin + a;
let range = texts.slice(b, e);
return range;
}
function getGutterWidth() {
return gutterWidth;
}
function drawVisibleLines() {
ctx.font = '14px 微软雅黑';
ctx.textBaseline = 'middle';
ctx.clearRect(0, 0, cvs.width, cvs.height);
let range = getVisiblePart();
let width = ctx.measureText((range.length + begin).toString()).width;
range.forEach((item, index) => {
let emptyHeight = lineHeight - 14;
let baseTop = index * lineHeight;
let scrollOffset = (vscrollbar.scrollTop % lineHeight);
let top = baseTop + emptyHeight - scrollOffset;
let lineNumber = index + begin + 1;
let lineNumberOffset = 4;
let textOffset = 10;
gutterWidth = width + lineNumberOffset * 2;
ctx.fillStyle = '#ddd';
ctx.fillRect(0, baseTop, gutterWidth, 30);
ctx.fillStyle = '#000';
ctx.fillText(lineNumber, lineNumberOffset, top);
ctx.fillText(item, width + textOffset, top);
});
}
drawVisibleLines();
vscrollbar.onscroll = _.throttle(function () {
let top = this.scrollTop;
begin = parseInt(top / lineHeight);
drawVisibleLines();
}, 10)
cvs.onmousemove = onMouseMove;
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=, initial-scale=">
<meta http-equiv="X-UA-Compatible" content="">
<title></title>
</head>
<body>
<div id="root">
<canvas></canvas>
<div class="vscrollbar">
<div class="innerHeight"></div>
</div>
</div>
</body>
</html>
* {
margin: 0;
padding: 0;
}
#root {
position: relative;
width: 300px;
height: 300px;
border: 1px solid #666;
}
#root canvas {
position: absolute;
left: 0;
top: 0;
}
#root .vscrollbar {
position: absolute;
right: 0;
width: 18px;
height: 100%;
overflow: auto;
z-index: 10;
}
.vscrollbar .innerHeight {
position: absolute;
left: 0;
top: 0;
width: 1px;
opacity: 0;
}
#root canvas.c-default {
cursor: default;
}
#root canvas.c-text {
cursor: text;
}