Consistent scrolling behavior across browsers
I made this pen to simulate the problem.
function createBox() {
var box = document.createElement("div");
box.className = "box";
box.style.backgroundColor = "#"+((1<<24)*Math.random()|0).toString(16);
return box;
}
function prependInnerHTML() {
console.log('prependInnerHTML');
var element = document.getElementById('scroll');
var box = createBox();
element.innerHTML = box.outerHTML + element.innerHTML;
element.prepend(box);
}
function prependPrepend() {
console.log('prependPrepend');
var element = document.getElementById('scroll');
var box = createBox();
element.prepend(box);
}
function prependPrepend() {
console.log('prependPrepend');
var element = document.getElementById('scroll');
var box = createBox();
element.prepend(box);
}
function prependInsertBefore() {
console.log('prependInsertBefore');
var element = document.getElementById('scroll');
var box = createBox();
element.insertBefore(box, element.firstChild);
}
function scroll() {
console.log('scroll');
var detailElement = document.getElementById('details');
var scrollElement = document.getElementById('scroll');
detailElement.innerHTML = "ScrollTop: " + scrollElement.scrollTop;
}
function clearScrollElement() {
console.log('clear');
var element = document.getElementById('scroll');
while(element.firstChild){
element.removeChild(element.firstChild);
}
}
html, body {
margin: 0;
padding: 0;
height: 100%;
width: 100%
}
#details {
position: fixed;
top: 10px;
left: 20px;
background-color: white;
}
#options {
position: fixed;
top: 10px;
right: 20px;
background-color: white;
}
#scroll {
overflow-x: hidden;
overflow-y: scroll;
height: 100%;
width: 100%;
}
.box {
height: 100vh;
width: 100%
}
<div id="options">
<span onclick="prependInnerHTML()">InnerHTML</span>
<span onclick="prependInsertBefore()">InsertBefore</span>
<span onclick="prependPrepend()">Prepend</span>
<span onclick="clearScrollElement()">Clear</span>
</div>
<div id="details">
ScrollTop: 0
</div>
<div id="scroll" onscroll="scroll()"></div>
The problem is that when an element is added to a scrollable element, it will have different types of behavior in different browsers.
innerHTML:
The first prepend method changes the innerHTML of the scrollable element. This seems to be cross-browser compatible.
The problem is that frameworks like Vue don't use this method internally, it might be using one of the other methods.
PasteBefore with Chrome:
If scrollTop is 0 and we add scrollTop, it will remain zero. If scrollTop is greater than zero, it will adjust the scrollTop to include the height of the added element.
PasteBefore with IE / Edge / Firefox:
Same behavior as innerHTML.
Prepend:
Prepend doesn't seem to be supported in IE / Edge, so I'll skip this one.
Question:
How do I get InsertBefore to behave the same across all browsers without introducing all browser checks?
source to share
Try to do this
/**
* Adds a new element to the top of the container, If after is a valid element then the new element is added after that element
* @param {HTMLElement} newItem - newItem to be added
* @param {HTMLElement} container - container
* @param {HTMLElement} [after=] - optional parameter, states that the new item will be added after this element
* @returns {HTMLElement}
*/
function addElementToTop(newItem, container, after) {
if (after instanceof HTMLElement === false || after.parentElement !== container) {
var first = container.firstElementChild;
} else {
first = after.nextElementSibling;
}
if (first !== null) {
container.insertBefore(newItem, first);
} else {
container.appendChild(newItem);
}
return newItem;
}
Here is a simple version
function addElementToTop(newItem, container) {
var first = container.firstElementChild;
if (first) container.insertBefore(newItem, first);
else container.appendChild(newItem);
return newItem;
}
The version below maintains the same viewport but scroll changes
function addElementToTopSteadyScroll(newItem, container) {
var bot = container.scrollHeight - container.scrollTop;
var first = container.firstElementChild;
if (first) container.insertBefore(newItem, first);
else container.appendChild(newItem);
container.scrollTop = container.scrollHeight - bot;
return newItem;
}
Whereas the below version supports the same scrolling but change view-port
function addElementToTopFlowScroll(newItem, container) {
var top = container.scrollTop;
var first = container.firstElementChild;
if (first) container.insertBefore(newItem, first);
else container.appendChild(newItem);
container.scrollTop = top;
return newItem;
}
source to share