SOURCE

console 命令行工具 X clear

                    
>
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;
}

本项目引用的自定义外部资源