<html>
<head>
<meta charset="UTF-8">
<title>test</title>
<style type="text/css">
.viewpoint {
margin: 20px auto;
width: 380px;
height: 200px;
border: 1px solid #333;
overflow-y:scroll;
}
.canvas {
position: relative;
height: 20000px;
}
.row {
position: absolute;
height: 10px;
line-height: 10px;
}
</style>
<script src="jquery.js"></script>
</head>
<body>
<div class="viewpoint">
<div class="canvas"></div>
</div>
<div class="log"></div>
<script>
var lineHeight = 10, viewpointHeight = 200, limit = Math.ceil(viewpointHeight/ lineHeight);
var data = new Array(2000).fill(0).map((d, i) => ({ v: i }));
var buffer = {
start: 0,
end: limit,
limit: limit,
total: data.length,
domain:[0, limit],
isAmong(index) {
return buffer.start <= index && index <= buffer.end;
},
preLoad(dir, current) {
if (dir === 0) return false;
var start = buffer.start;
var end = buffer.end;
// scroll up
if (dir < 0 && start === 0) return false;
if (dir < 0 && current < Math.max(0, start + buffer.limit)) {
end = Math.max(buffer.isAmong(current) ? start : current, buffer.limit);
start = Math.max(0, end - buffer.limit);
buffer.domain = [start, end];
buffer.start = start;
buffer.end = Math.min(start + 3 * buffer.limit, buffer.end);
return true;
}
// scroll down
if (dir > 0 && end === buffer.total) return false;
if (dir > 0 && current > end - buffer.limit) {
start = buffer.isAmong(current) ? end : current;
end = Math.min(buffer.total, start + buffer.limit);
buffer.domain = [start, end];
buffer.end = end;
buffer.start = Math.max(buffer.start, end - 3 * buffer.limit);
return true;
}
return false;
}
};
var scroller = {
dir: 0, // -1,0,-1
preIndex: 0,
index: 0,
on(fn) {
this.fn = function(offsetTop) {
log('current', offsetTop);
scroller.dir = offsetTop - scroller.preIndex;
scroller.index = ~~((scroller.preIndex = offsetTop)/ lineHeight);
if (buffer.preLoad(scroller.dir, scroller.index)) {
fn(
scroller.dir > 0 ? 1 : -1,
buffer.domain,
buffer.start,
buffer.end,
scroller.index,
buffer.total
);
}
};
},
fire() {this.fn.apply(this, arguments);}
};
var dataView = {
nodeList: [],
fullNodes() {
return dataView.nodeList.length >= 3 * limit;
},
getNodes(dir, first, last) {
if (dataView.fullNodes()) {
let selected;
if (dir > 0) {
selected = dataView.nodeList.slice(0, last - first);
dataView.nodeList = dataView.nodeList.slice(last - first + 1).concat(selected);
} else {
selected = dataView.nodeList.slice(first - last);
dataView.nodeList = selected.concat(dataView.nodeList.slice(0, first - last - 1));
}
return selected;
}
var nodes = [];
for (let i = first; i <= last; i++) {
nodes.push(function() {
var $node = $('<div/>').addClass('row');
return function(row, top) {
return $node.css('top', top * lineHeight).text(row.v);
}
}());
}
if (dir > 0) {
dataView.nodeList = dataView.nodeList.concat(nodes);
} else {
dataView.nodeList = nodes.concat(dataView.nodeList);
}
return nodes;
},
render(dir, [first, last], start, end) {
var nodes = dataView.getNodes(dir, first, last);
if (dataView.isFullNodes) {
data.slice(first, last).forEach((row, i) => {
nodes[i](row, first + i);
});
return;
}
var parent = $('<div/>');
data.slice(first, last).forEach((row, i) => {
parent.append(nodes[i](row, first + i));
});
$('.canvas').append(parent.children());
if (dataView.fullNodes()) {
dataView.isFullNodes = true;
}
}
};
document.querySelector('.viewpoint').addEventListener('scroll', function(evt) {
// console.log(this.scrollTop);
scroller.fire(this.scrollTop);
});
scroller.on(function(dir, domain, start, end, offsetTop, total) {
console.log(`${dir}, add:[${domain}] [${start}-${end}], ${offsetTop}, nodes:${dataView.nodeList.length}`);
dataView.render(dir, domain, start, end);
});
var $log = document.querySelector('.log');
function log(...args) {
$log.innerHTML = args;
}
</script>
</body>
</html>