231 lines
7.0 KiB
HTML
231 lines
7.0 KiB
HTML
<div tabindex="1" id="scroll" style="overflow-y: scroll; width: 300px; position: relative; height: 300px; border: 1px solid gray;" onscroll="enqueue(scrollother)">
|
|
<div style="height: 1500px" id="content"></div>
|
|
<div id="realcontent" style="z-index: 1; width: 100%; position: absolute; overflow: hidden"></div>
|
|
</div>
|
|
<style>
|
|
.item { border: 1px solid gray; box-sizing: border-box; text-align: center; }
|
|
</style>
|
|
<script>
|
|
/*<!--*/
|
|
//http://stackoverflow.com/questions/11730596/non-linear-scrolling
|
|
var s = document.getElementById('scroll');
|
|
var c = document.getElementById('content');
|
|
var rc = document.getElementById('realcontent');
|
|
|
|
var minheight = 100;
|
|
|
|
var api = (function() {
|
|
var items = [];
|
|
for (var i = 0; i < 100; i++)
|
|
items.push(30+Math.round(Math.random()*120));
|
|
items = [31,109,107,97,140,133,49,69,123,43,137,128,31,101,120,148,119,30,31,37,43,119,105,106,91,148,36,85,76,97,148,31,53,126,138,51,85,65,53,78,39,49,133,49,103,146,143,148,115,49,55,98,44,84,57,127,98,98,104,116,150,82,123,62,134,109,89,71,36,50,146,146,53,35,86,143,86,138,39,81,72,106,88,117,127,97,127,147,102,42,124,99,32,51,84,45,106,139,140,69];
|
|
// Аналог API получения элементов
|
|
return [ items.length, function(start, count, callback)
|
|
{
|
|
setTimeout(function() { callback(items.slice(start, start+count)); }, 200);
|
|
} ]
|
|
})();
|
|
var totalItems = api[0];
|
|
var getItems = api[1];
|
|
|
|
c.style.height = (totalItems*minheight)+'px';
|
|
|
|
var lastFirst = 0;
|
|
var curFirst = 0, curOffset = 0, curCount = 0, curPos = null;
|
|
var pagesize = Math.floor(s.offsetHeight/30);
|
|
|
|
rc.parentNode.addEventListener('keydown', handlekey);
|
|
c.addEventListener('keydown', handlekey, true);
|
|
rc.addEventListener('keydown', handlekey, true);
|
|
//rc.parentNode.addEventListener('DOMMouseScroll', mouse_wheel);
|
|
//rc.parentNode.onmousewheel = mouse_wheel;
|
|
rc.parentNode.focus();
|
|
|
|
function mouse_wheel(event)
|
|
{
|
|
event = event||window.event;
|
|
var direction = ((event.wheelDelta) ? event.wheelDelta/120 : event.detail/-3) || false;
|
|
if (direction)
|
|
{
|
|
if (event.preventDefault)
|
|
event.preventDefault();
|
|
event.returnValue = false;
|
|
enqueue(scrollbypx, -direction * 30);
|
|
}
|
|
}
|
|
|
|
function handlekey(ev)
|
|
{
|
|
if (ev.keyCode == 38 || ev.keyCode == 40 || ev.keyCode == 33 || ev.keyCode == 34) // up, down, pgup, pgdown
|
|
{
|
|
var px;
|
|
if (ev.keyCode == 38) px = -30;
|
|
else if (ev.keyCode == 40) px = 30;
|
|
else if (ev.keyCode == 33) px = -270;
|
|
else if (ev.keyCode == 34) px = 270;
|
|
enqueue(scrollbypx, px);
|
|
}
|
|
else if (ev.keyCode == 36)
|
|
s.scrollTop = 0;
|
|
else if (ev.keyCode == 35)
|
|
s.scrollTop = s.scrollHeight-s.offsetHeight+1;
|
|
else
|
|
return;
|
|
ev.preventDefault();
|
|
}
|
|
|
|
var rq = [];
|
|
var loadedItems = [];
|
|
|
|
function enqueue(fn, arg)
|
|
{
|
|
if (!rq.length)
|
|
fn(arg);
|
|
else if (arg)
|
|
rq.push(function() { fn(arg) });
|
|
else if (rq[rq.length-1] != fn)
|
|
rq.push(fn);
|
|
}
|
|
|
|
function loadfrom(start, count)
|
|
{
|
|
console.log('load from '+start+': '+count+' items');
|
|
getItems(start, count, function(partItems)
|
|
{
|
|
var h, i;
|
|
for (i = 0; i < partItems.length; i++)
|
|
loadedItems[start+i] = partItems[i];
|
|
rq.length && (rq.shift())();
|
|
});
|
|
}
|
|
|
|
// скроллинг в пикселях (в пределах видимости - можно использовать для up/down, pgup/pgdown, колеса мыши)
|
|
function scrollbypx(px)
|
|
{
|
|
if (px < 0)
|
|
{
|
|
var cpx = px;
|
|
cpx += curOffset*loadedItems[curFirst];
|
|
var nf = curFirst;
|
|
while (nf > 0 && cpx < 0)
|
|
{
|
|
nf--;
|
|
if (!loadedItems[nf])
|
|
{
|
|
rq.unshift(function() { scrollbypx(px) });
|
|
loadfrom(nf+1 > pagesize ? nf+1-pagesize : 0, pagesize);
|
|
return;
|
|
}
|
|
cpx += loadedItems[nf];
|
|
}
|
|
newFirst = nf+(cpx/loadedItems[nf]);
|
|
}
|
|
else
|
|
{
|
|
px -= loadedItems[curFirst]*(1-curOffset);
|
|
var nf = curFirst;
|
|
while (nf < totalItems-1 && px > 0)
|
|
{
|
|
nf++;
|
|
px -= loadedItems[nf];
|
|
}
|
|
nfo = (px >= 0 ? loadedItems[nf] : px+loadedItems[nf]);
|
|
newFirst = nf+(nfo/loadedItems[nf]);
|
|
}
|
|
s.scrollTop = newFirst/lastFirst*(s.scrollHeight-s.offsetHeight+1);
|
|
rq.length && (rq.shift())();
|
|
}
|
|
|
|
function loadnext()
|
|
{
|
|
var h, i;
|
|
for (h = 0, i = curFirst; i < totalItems && h < s.offsetHeight; i++)
|
|
{
|
|
if (!loadedItems[i])
|
|
{
|
|
var start = i;
|
|
while (start < totalItems && !loadedItems[start] && start < i+pagesize)
|
|
start++;
|
|
start = start-pagesize;
|
|
start = start < 0 ? 0 : start;
|
|
rq.unshift(loadnext);
|
|
loadfrom(start, pagesize);
|
|
return;
|
|
}
|
|
h += (i == curFirst ? loadedItems[i]*(1-curOffset) : loadedItems[i]);
|
|
}
|
|
curCount = i-curFirst;
|
|
rq.length && (rq.shift())();
|
|
}
|
|
|
|
function renderitems()
|
|
{
|
|
var ih = '', h = 0;
|
|
for (var i = 0; i < curCount; i++)
|
|
{
|
|
ih += '<div class="item" style="height: '+loadedItems[curFirst+i]+'px">'+(curFirst+i)+'</div>';
|
|
h += loadedItems[curFirst+i];
|
|
}
|
|
// curPos, чтобы при плавном скролле вниз загруженное в середину не было видно
|
|
var t = Math.round(-loadedItems[curFirst]*curOffset+curPos);
|
|
rc.style.height = '1px'; // на случай, если браузеру вздумается где-то посередине перерисовать
|
|
rc.style.top = t+'px';
|
|
rc.style.height = (t+h < s.scrollHeight-1 ? h : s.scrollHeight-1-t)+'px';
|
|
rc.innerHTML = ih;
|
|
// Тут можно убить лишние элементы
|
|
var q0 = curFirst-pagesize;
|
|
q0 = q0 - q0%pagesize;
|
|
var q1 = curFirst+curCount+pagesize;
|
|
q1 = q1 + (pagesize-q1%pagesize);
|
|
var destroyed = 0;
|
|
for (var i in loadedItems)
|
|
{
|
|
if (i < q0 || i >= q1)
|
|
{
|
|
destroyed++;
|
|
delete loadedItems[i];
|
|
}
|
|
}
|
|
if (destroyed > 0)
|
|
console.log('destroy '+destroyed+' items');
|
|
rq.length && (rq.shift())();
|
|
}
|
|
|
|
// нелинейный скроллинг (в % от элементов)
|
|
function scrollother()
|
|
{
|
|
var i, h;
|
|
if (s.scrollTop == curPos)
|
|
return;
|
|
var newFirst = s.scrollTop / (s.scrollHeight-s.offsetHeight+1) * lastFirst;
|
|
if (newFirst > lastFirst)
|
|
newFirst = lastFirst;
|
|
curFirst = Math.floor(newFirst);
|
|
curOffset = newFirst-curFirst;
|
|
curPos = s.scrollTop;
|
|
rq.unshift(renderitems);
|
|
loadnext();
|
|
}
|
|
|
|
function initscroll()
|
|
{
|
|
var h = 0, i;
|
|
for (i = totalItems-1; i >= 0 && h < s.offsetHeight; i--)
|
|
{
|
|
if (!loadedItems[i])
|
|
{
|
|
rq.unshift(initscroll);
|
|
loadfrom(i+1-pagesize, pagesize);
|
|
return;
|
|
}
|
|
h += loadedItems[i];
|
|
}
|
|
lastFirst = i+1+(h-s.offsetHeight)/loadedItems[i+1];
|
|
rq.length && (rq.shift())();
|
|
}
|
|
|
|
rq.push(scrollother);
|
|
initscroll();
|
|
//-->
|
|
</script>
|