Last active
December 30, 2021 01:31
-
-
Save mjy9088/8498f996992631a1f75098d41aae30d2 to your computer and use it in GitHub Desktop.
remove invisible elements from DOM tree
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var InfiniteScroll = window.InfiniteScroll = (function () { | |
var instanceKey = window.Symbol ? Symbol('InfiniteScroll') : '__IE_POLYFILL__SYMBOL__INFINITE_SCROLL'; | |
function result(options) { | |
if (!(this instanceof result)) { | |
throw new Error('InfiniteScroll must called with new operator and options object'); | |
} | |
this.init(options); | |
} | |
Object.defineProperties(result.prototype, { | |
init: { | |
value: function (options) { | |
this.stopped = true; | |
this.container = options.container; | |
this.renderer = options.renderer || result.defaultRenderer; | |
this.manager = options.manager; | |
this.spare = options.spare || 0; | |
this.data = options.initialData || []; | |
this.elementPool = []; | |
this.renderedElements = Object.create(null); | |
this.prevStart = 0; | |
this.prevEnd = 0; | |
this.scrollListener = (function () { this.render(); }).bind(this); | |
this.loading = false; | |
this.cursor = options.initialCursor; | |
this.dataLoader = options.dataLoader; | |
this.context = options.initialContext || []; | |
this.end = false; | |
this.updateContainerHeight(); | |
if (!options.noStart) this.start(); | |
} | |
}, | |
setContext: { | |
value: function (context, initialData, initialCursor) { | |
if (this.context.length == context.length && this.context.every(function (_, i, a) { | |
return a[i] === context[i]; | |
})) { | |
return false; | |
} | |
var prevData = this.data; | |
this.context = context; | |
this.data = initialData || []; | |
this.cursor = initialCursor; | |
this.loading = false; | |
this.end = false; | |
this.updateContainerHeight(); | |
this.render(); | |
return prevData; | |
} | |
}, | |
updateContainerHeight: { | |
value: function () { | |
this.container.style.height = (this.data.length && this.manager.getMaxOffsetByIndex(this.data.length - 1) || 0) + 'px'; | |
} | |
}, | |
loadData: { | |
value: function (wantCount) { | |
if (this.loading) return false; | |
this.loading = true; | |
var self = this; | |
var context = self.context; | |
this.dataLoader(function addData(data) { | |
if (self.context != context) { | |
// aborted | |
return; | |
} | |
self.loading = false; | |
if (!data) { | |
return; | |
} | |
if (data.end) { | |
self.end = true; | |
} | |
self.cursor = data.cursor; | |
Array.prototype.push.apply(self.data, data.data); | |
self.updateContainerHeight(); | |
self.render(); | |
}, this.cursor, context, wantCount); | |
} | |
}, | |
acquireElement: { | |
value: function () { | |
return this.elementPool.length ? this.elementPool.pop() : this.renderer.create(); | |
} | |
}, | |
releaseElement: { | |
value: function (element) { | |
if (this.renderer.clean) { | |
this.renderer.clean(element); | |
} | |
this.elementPool.push(element); | |
} | |
}, | |
setManager: { | |
value: function (manager) { | |
this.manager = manager; | |
this.render(true); | |
} | |
}, | |
setRenderer: { | |
value: function (renderer) { | |
this.renderer = renderer; | |
this.render(true); | |
} | |
}, | |
setManagerAndRenderer: { | |
value: function (manager, renderer) { | |
this.manager = manager; | |
this.renderer = renderer; | |
this.render(true); | |
} | |
}, | |
start: { | |
value: function () { | |
if (!this.stopped) return; | |
this.stopped = false; | |
this.render(); | |
window.addEventListener('scroll', this.scrollListener); | |
} | |
}, | |
stop: { | |
value: function () { | |
if (this.stopped) return; | |
this.stopped = true; | |
window.removeEventListener('scroll', this.scrollListener); | |
} | |
}, | |
render: { | |
value: function (force) { | |
var containerRect = this.container.getBoundingClientRect(); | |
var start = this.manager.getMinIndexByOffset(-containerRect.top - this.spare); | |
var screenEnd = this.manager.getMaxIndexByOffset(window.innerHeight - containerRect.top + this.spare); | |
start = isNaN(start) ? 0 : Math.max(0, start); | |
screenEnd = isNaN(screenEnd) ? undefined : screenEnd; | |
var end = screenEnd === undefined ? this.data.length - 1 : Math.min(this.data.length - 1, screenEnd); | |
for (var i = this.prevStart; i < start && this.renderedElements[i]; i++) { | |
this.renderedElements[i].parentElement.removeChild(this.renderedElements[i]); | |
this.releaseElement(this.renderedElements[i]); | |
delete this.renderedElements[i]; | |
} | |
for (var i = this.prevEnd; i > end && this.renderedElements[i]; i--) { | |
this.renderedElements[i].parentElement.removeChild(this.renderedElements[i]); | |
this.releaseElement(this.renderedElements[i]); | |
delete this.renderedElements[i]; | |
} | |
if (force) { | |
for (var i in this.renderedElements) { | |
this.renderer.clean(this.renderedElements[i]); | |
this.renderer.updateOffset(this.renderedElements[i], this.manager.getMinOffsetByIndex(i), this.manager.getHeightByIndex(i), i); | |
this.renderer.render(this.renderedElements[i], this.data[i], i, this.context); | |
} | |
this.updateContainerHeight(); | |
} | |
this.prevStart = start; | |
this.prevEnd = end; | |
for (var i = start; i <= end; i++) { | |
if (!this.renderedElements[i]) { | |
this.renderedElements[i] = this.acquireElement(); | |
this.renderer.updateOffset(this.renderedElements[i], this.manager.getMinOffsetByIndex(i), this.manager.getHeightByIndex(i), i); | |
this.renderer.render(this.renderedElements[i], this.data[i], i, this.context); | |
this.container.insertBefore(this.renderedElements[i], null); | |
} | |
} | |
if (screenEnd >= this.data.length - 1 && !this.end) { | |
this.loadData(screenEnd - this.data.length + 1); | |
} | |
} | |
}, | |
running: { | |
enumerable: true, | |
get: function () { | |
return !this.stopped; | |
}, | |
set: function (value) { | |
if (value) { | |
this.start(); | |
} else { | |
this.stop(); | |
} | |
} | |
} | |
}); | |
Object.defineProperties(result, { | |
instanceKey: { | |
value: instanceKey | |
}, | |
init: { | |
value: function (options) { | |
if (!options.container) { | |
throw new Error('container options is required'); | |
} | |
Object.defineProperty(options.container, instanceKey, { | |
value: new result(options) | |
}); | |
} | |
}, | |
getInstance: { | |
value: function (node) { | |
for (var current = node; current; current = current.parentElement) { | |
if (current[instanceKey]) { | |
return current[instanceKey]; | |
} | |
} | |
} | |
}, | |
defaultRenderer: { | |
value: (function () { | |
var renderer = Object.create(null); | |
Object.defineProperties(renderer, { | |
create: { | |
value: function () { | |
var element = document.createElement('div'); | |
element.style.position = 'absolute'; | |
return element; | |
}, | |
enumerable: true | |
}, | |
clean: { | |
value: function (element) { | |
while (element.firstChild) { | |
element.removeChild(element.firstChild); | |
} | |
}, | |
enumerable: true | |
}, | |
updateOffset: { | |
value: function (element, offset, height, _index) { | |
element.style.top = offset + 'px'; | |
element.style.height = height + 'px'; | |
}, | |
enumerable: true | |
}, | |
render: { | |
value: function (element, item, _index) { | |
element.insertBefore(item, null); | |
}, | |
enumerable: true | |
} | |
}); | |
return renderer; | |
})() | |
}, | |
basicManager: { | |
value: function (height, spareCount) { | |
spareCount = spareCount || 0; | |
return { | |
getHeightByIndex: function (_index) { | |
return height; | |
}, | |
getMinOffsetByIndex: function (index) { | |
return index * height; | |
}, | |
getMaxOffsetByIndex: function (index) { | |
return (index + 1) * height; | |
}, | |
getMinIndexByOffset: function (offset) { | |
return Math.floor(offset / height - spareCount); | |
}, | |
getMaxIndexByOffset: function (offset) { | |
return Math.ceil(offset / height + spareCount); | |
} | |
}; | |
} | |
} | |
}); | |
return result; | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
window.addEventListener('DOMContentLoaded', function () { | |
var container = document.getElementById('infinite-scroll-test-container'); | |
var selectManager = document.getElementById('infinite-scroll-test-manager'); | |
var selectContext = document.getElementById('infinite-scroll-test-context'); | |
var inputMax = document.getElementById('infinite-scroll-test-max'); | |
var managers = { | |
row: InfiniteScroll.basicManager(48, 10), | |
'col-3': { | |
getHeightByIndex: function (_index) { | |
return 72; | |
}, | |
getMinOffsetByIndex: function (index) { | |
return Math.floor(index / 3) * 72; | |
}, | |
getMaxOffsetByIndex: function (index) { | |
return (Math.floor(index / 3) + 1) * 72; | |
}, | |
getMinIndexByOffset: function (offset) { | |
return Math.floor(offset / 72 - 10) * 3; | |
}, | |
getMaxIndexByOffset: function (offset) { | |
return Math.ceil(offset / 72 + 10) * 3 + 3; | |
} | |
} | |
}; | |
function render(element, item, _index) { | |
element.innerHTML = '' + | |
'<div style="box-sizing: border-box; width: 100%; height: 100%; border: 3px solid #BBFF88; border-radius: 24px;">' + | |
'<div>' + | |
'<span>' + (item.index + 1) + '</span>' + | |
item.text + | |
'</div>' + | |
'</div>' + | |
''; | |
} | |
var renderers = { | |
row: Object.assign({}, InfiniteScroll.defaultRenderer, { | |
updateOffset: function (element, offset, height, _index) { | |
element.style.top = offset + 'px'; | |
element.style.height = height + 'px'; | |
element.style.left = ''; | |
element.style.width = '100%'; | |
}, | |
render: render | |
}), | |
'col-3': Object.assign({}, InfiniteScroll.defaultRenderer, { | |
updateOffset: function (element, offset, height, index) { | |
element.style.top = offset + 'px'; | |
element.style.height = height + 'px'; | |
element.style.left = (index % 3) * 33 + '%'; | |
element.style.width = '33%'; | |
}, | |
render: render | |
}) | |
}; | |
function getData(index, context) { | |
return { | |
index: index, | |
text: context[0] | |
}; | |
} | |
function dataLoader(addData, cursor, context, count) { | |
count = count || 10; | |
var END = context[1]; | |
setTimeout(function () { | |
count = Math.min(END - cursor, count); | |
addData({ | |
data: Array.from(new Array(count)).map(function (_, i) { | |
return getData(cursor + i, context); | |
}), | |
cursor: cursor + count, | |
end: cursor + count == END | |
}); | |
}, 500); | |
} | |
function getContext() { | |
var max = parseInt(inputMax.value, 10); | |
if (!max) return undefined; | |
return [selectContext.value, max]; | |
} | |
function updateContext() { | |
var newContext = getContext(); | |
if (newContext) { | |
InfiniteScroll.getInstance(container).setContext(newContext, [], 0); | |
} | |
} | |
InfiniteScroll.init({ | |
container: container, | |
dataLoader: dataLoader, | |
renderer: renderers[selectManager.value], | |
initialCursor: 0, | |
initialContext: getContext(), | |
manager: managers.row | |
}); | |
selectContext.addEventListener('change', updateContext); | |
inputMax.addEventListener('change', updateContext); | |
selectManager.addEventListener('change', function () { | |
InfiniteScroll.getInstance(container).setManagerAndRenderer( | |
managers[selectManager.value], | |
renderers[selectManager.value] | |
); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
html, body { | |
width: 100%; | |
height: 100%; | |
margin: 0; | |
padding: 0; | |
border: none; | |
box-sizing: border-box; | |
} | |
header, footer { | |
height: 720px; | |
background-color: #444444; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Infinite Scroll Test</title> | |
<link rel="stylesheet" href="./style.css" /> | |
<script src="./script.js"></script> | |
<script src="./infinite-scroll.js"></script> | |
</head> | |
<body> | |
<header> | |
HEADER | |
</header> | |
<main> | |
MAIN | |
<select id="infinite-scroll-test-manager"> | |
<option selected value="row">List</option> | |
<option value="col-3">Table</option> | |
</select> | |
<select id="infinite-scroll-test-context"> | |
<option selected value="Hello">Hello</option> | |
<option value="Bye">Bye</option> | |
</select> | |
<input id="infinite-scroll-test-max" type="number" value="123" /> | |
<section id="infinite-scroll-test-container" style="position: relative;"></section> | |
</main> | |
<footer> | |
FOOTER | |
</footer> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment