How to do an infinite scroll in plain Javascript - javascript

I want to avoid using jQuery or another library for the sake of keeping my code minimal, I need very little in the way of features, I just want to append to a list when the user scrolls to the bottom. How would I do this in plain Javascript?

Basicaly you just need to hook the event scroll, check if the user scrolled down enough and add some content if so:
<html><body>
<div id="test">scroll to understand</div>
<div id="wrapper" style="height: 400px; overflow: auto;">
<div id="content"> </div>
</div>
<script language="JavaScript">
// we will add this content, replace for anything you want to add
var more = '<div style="height: 1000px; background: #EEE;"></div>';
var wrapper = document.getElementById("wrapper");
var content = document.getElementById("content");
var test = document.getElementById("test");
content.innerHTML = more;
// cross browser addEvent, today you can safely use just addEventListener
function addEvent(obj,ev,fn) {
if(obj.addEventListener) obj.addEventListener(ev,fn,false);
else if(obj.attachEvent) obj.attachEvent("on"+ev,fn);
}
// this is the scroll event handler
function scroller() {
// print relevant scroll info
test.innerHTML = wrapper.scrollTop+"+"+wrapper.offsetHeight+"+100>"+content.offsetHeight;
// add more contents if user scrolled down enough
if(wrapper.scrollTop+wrapper.offsetHeight+100>content.offsetHeight) {
content.innerHTML+= more;
}
}
// hook the scroll handler to scroll event
addEvent(wrapper,"scroll",scroller);
</script>
</body></html>

For achieving this behaviour you don't need jQuery or a jQuery plugin. You can use only CSS or JavaScript (if you want to cover all browsers).
But don't use onScroll: you can do all of this with just vanilla JS and the Intersection Observer API.
All you need to do is place elements and listen for when they become available in the screen. The Intersection Observer API is very customisable to fit all your needs.
In summary: you accomplish that with a few JavaScript & HTML lines and it's much more performant than listening for scroll events in the browser.

Excellent demo code for infinite scroll. Goes to show that you don't need jQuery and Angular for any browser independent work. But new boys today are out of touch with pure Javascript that we old guys still trust and use. Here I have simplified the code further:
// we will add this content, replace for anything you want to add
var wrapper, content, test;
var more = '<div style="height:1000px; background:#EEE;"></div>';
// this is the scroll event handler
function scroller() {
// print relevant scroll info
test.innerHTML = wrapper.scrollTop + " + " + wrapper.offsetHeight + " + 100 > " + content.offsetHeight;
// add more contents if user scrolled down enough
if (wrapper.scrollTop + wrapper.offsetHeight + 100 > content.offsetHeight) {
content.innerHTML += more; // NK: Here you can make an Ajax call and fetch content to append to content.innerHTML
}
}
wrapper = document.getElementById("wrapper");
content = document.getElementById("content");
test = document.getElementById("test");
content.innerHTML = more;
// hook the scroll handler to scroll event
if (wrapper.addEventListener) // NK: Works on all new browsers
wrapper.addEventListener("scroll", scroller, false);
else if (wrapper.attachEvent) // NK: Works on old IE
wrapper.attachEvent("onscroll", scroller);
<div id="test">scroll to understand</div>
<div id="wrapper" style="height: 400px; overflow: auto;">
<div id="content"> </div>
</div>

domElem.addEventListener(
'scroll',
function(evt) { ... },
false
);
and handle evt/scroll position appropriately.

I see great answers to your question, but for a scrolling that is not associated with any HTML element or content (just an empty HTML page), here is how I did it:
document.querySelector("body").style.height = "1000px";
window.addEventListener("scroll", function() {
var body = document.querySelector("body");
var height = body.style.height;
height = height.slice(0, -2);
height = Number(height);
return function() {
if(height - window.scrollY < 700) {
height += height / 2;
}
body.style.height = height + "px";
};
}());
<!DOCTYPE html>
<html>
<head>
</head>
<body>
</body>
</html>
I hope this helps someone out there :)

Assume HTML:
<div class="container"></div>
let container = document.querySelector('.container');
// when user scrolls to last list item in view, wait for 1.5seconds and load next 10 list items, and continue thus.
window.addEventListener('scroll', function(){
setTimeout(() => {
loadMoreList(5);
}, 1500);
});
loadMoreList(20); // load first 20 list items.
function loadMoreList(num) {
let scrollY = window.scrollY;
let innerHeight = window.innerHeight;
let offsetHeight = document.body.offsetHeight;
if (scrollY + innerHeight > offsetHeight - 100) {
let item = 1;
let ul = document.createElement('ul');
for (var i = 0; i < num; i++) {
let li = document.createElement('li');
li.innerText = 'List Item ' + item++;
ul.appendChild(li);
container.appendChild(ul);
}
}
}
Note: The more important parts are: the scroll event listener and the loadMoreList function. You don't necessarily need the argument num.
The for loop just ensures that on load, the function is called and 20 items are uniquely created and displayed.

Related

How to incorporate JS media Queries and Scroll listening?

I'm currently making an element visible when my nav is at the top of the page. I'd like the element to be hidden if the page gets to max-width: 900px;. I've tried using modernizer for JS media queries but I ca't seem to get it to work.
Code:
var a = $(".menu").offset().top;
function scrollListener(){
if($(document).scrollTop() > a)
{$('.hidden-logo').css({"opacity": "1","display": "block"});
$('.menu').css({"margin-left": "-130px"})
} else {
$('.hidden-logo').css({"opacity": "0","display": "none"});
$('.menu').css({"margin-left": "0px"})
}
};
$(document).scroll(scrollListener);
You were checking the scroll position the wrong way - I think you want the logo to disappear when the current scroll is greater than the top of the logo, not less.
I added a msgS div (for demo purposes only) that will show you the current scroll value against the top-of-menu static value. I also added a 100px fudge factor to the menu location to make it more clear in the demo when the current scroll reaches that position. I use these temporary msg divs myself when working out my code, and then remove them when I've got it all sorted and ready for production.
And this is all you need to check the media query in javascript:
var winmed = window.matchMedia("(max-width: 700px)");
if (winmed.matches){ //do something }
And that can go into a listener function exactly like your scroll listener.
var gloShowLogo = true;
var a = $(".menu").offset().top;
var fudge = 100; //100px fudge factor so can SEE div disappear
function scrollListener(){
updateScrollMsg();
var currScroll = $(document).scrollTop();
var topOfMenu = a+fudge;
if( gloShowLogo && currScroll < topOfMenu ){
$('.hidden-logo').css({"opacity": "1","display": "block"});
$('.menu').css({"margin-left": "-130px"})
} else {
$('.hidden-logo').css({"opacity": "0","display": "none"});
$('.menu').css({"margin-left": "0px"})
}
};
function resizeListener(){
updateMediaMsg();
var winmed = window.matchMedia("(max-width: 500px)");
if (winmed.matches){
$('.hidden-logo').css({"opacity": "1","display": "block"});
gloShowLogo = true;
} else {
$('.hidden-logo').css({"opacity": "0","display": "none"});
gloShowLogo = false;
}
}
$(window).scroll(scrollListener);
$(window).resize(resizeListener);
function updateScrollMsg(){
$('#msgS').html( $(document).scrollTop() +' // ' + $(".menu").offset().top );
}
function updateMediaMsg(){
var winmed = window.matchMedia("(max-width: 500px)");
var medmsg = (winmed.matches) ? '< 500' : '> 500';
console.log(medmsg);
$('#msgM').html(medmsg);
}
.menu{background:green;text-align:center;}
.content{height:200vh;background:palegreen;text-align:center;}
.hidden-logo{position:fixed;top:1vh;right:1vw;padding:15px; background:pink;z-index:2;}
#msgS{position:fixed;top:0;left:0;padding:10px;background:wheat;z-index:2;}
#msgM{position:fixed;top:40px;left:0;padding:10px;background:lightblue;z-index:2;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<div class="menu">Menu Div</div>
<div class="content">Lengthy content Div..<br><br><br><br>100<br></div>
<div class="hidden-logo">LOGO</div>
<div id="msgS"></div>
<div id="msgM"></div>
Update:
Sorry, I had the media query a bit backwards myself - I think you want the logo to display when the screen-size is < 900px and to be hidden if wider than 900px, yes?
I added a msgM div so you can watch the media query kick-in -- but getting the best width for the demo was a bit of a challenge. I finally settled at 500px as a width that can be demoed (StackOverflow resizes its StackSnippets container as the browser window resizes, which throws things into confusion at each of their resize breakpoints)

Can't change scrollbar values

I've made a simple code to change the value of a scrollbar along with document.scrollTop but i can't get it to work.
var scroll = document.scrollTop;
var container = document.getElementById('test');
container.innerHTML = scroll;
var pb = document.getElementById('progress-bar');
pb.value = 2;
var change = function()
{ pb.value = 2;
pb.value++;
};
if (scroll > 0){
change();
};
Can someone tell me where i've made a mistake(s) ?
Problem:
You haven't shared many details in your post but from what you have posted we can notice that you have some problems in your current code:
As stated in comments you need to use document.body.scrollTop
because document.scrollTop is undefined as the scroll is attached
to document body and not to the document itself.
And with your actual code you are not changing the progress bar value, because the change() function isn't attached to any event but it's just called on document load.
Solution:
If you want to change the progress bar value when the page is scrolled, you need to do it in the onscroll event of the body.
Demo:
I made a sample Demo to show you how it should work, and how the progress bar is changing when we scroll in the view:
document.body.onscroll = function() {
var pb = document.getElementById('progress-bar');
var fullHeight = document.body.scrollHeight;
var value = 100 - (document.body.scrollTop / fullHeight) * 100;
pb.value = value;
};
#progress-bar {
margin-top: 200px;
}
<div style="width:250px; height:1400px">
<progress id="progress-bar" value="100" max="100"></progress>
</div>
The above function is attached to the onscroll event of the document.body, and whenever the body is scrolled it calculates the scrolled height (document.body.scrollTop) compared with the full scrollable height (document.body.scrollHeight) and put this value in the progress bar.

Make two elements have the same size

I want two elements in different locations and different "parents" in the DOM tree to have the same height and width, even if one changes.
Is there a solution that will support all browsers including IE 8?
EDIT: If there is a solution that will not work on IE 8 I would still like to hear about it, but it will not be accepted as the solution I'm looking for.
Clarification: I want to solution to handle any cause for the size change: Window size change, content size change, etc.
You can use setInterval to do what you want.
var changeIndex = -1; // record element width or height is change or not
function setToSame() {
if(changeIndex!=-1) {
console.log("test");
$('.same').height($('.same').eq(changeIndex).height());
$('.same').width($('.same').eq(changeIndex).width());
changeIndex = -1;
}
}
// set your own function to change size, but reserve changeIndex setting
$('input').change(function() {
$(this).parent().children('.same').css($(this).attr('id'), $(this).val() +'px');
// set the changeIndex to the current change div
changeIndex = $('.same').index($(this).parent().children('.same'));
console.log(changeIndex);
});
setInterval(setToSame, 4);
See jsfiddle here.
You can use jQuery to get a solution that works for IE8.
Suppose the two element that you want to have same height and width are,
<div id="fir">
</div>
<div id="sec">
</div>
Now specify height and width of just one element as,
#fir{
height: 50px;
width: 100px;
}
There is no predefined method in CSS to detect height or width change but you can achieve the results using jQuery as,
$(document).ready(function(){
$('#fir').bind('heightChange', function(){
var h = $("#fir").height();
$("#sec").height(h);
});
$('#fir').bind('widthChange', function(){
var w = $("#fir").width();
$("#sec").width(w);
});
$('#sec').bind('heightChange', function(){
var h = $("#sec").height();
$("#fir").height(h);
});
$('#sec').bind('widthChange', function(){
var w = $("#sec").width();
$("#fir").width(w);
});
});
This will detect the height and width change for both element and set the height and width of other element likewise.
To check if the above code works properly you can create a test script that changes width of element with id="fir" by creating a button,
<button id="btn">Change width</button>
Now include the below function,
$("#btn").click(function() {
$("#fir").css('width', '400px');
$("#fir").trigger('widthChange');
});
Here is the fiddle for it
<html>
<head>
<style>
div{}
#A{background: red}
#B{background: blue}
</style>
<script>
mBlockChange = false; //Required for IE9-
function equalSize(f, t){
mBlockChange = true;
f = (f || document.getElementById('A'));
t = (t || document.getElementById('B'));
//We take the larger dimension of both since it is better than clipping.
//Change on your demands.
t.style.height = '';
t.style.width = '';
f.style.height = '';
f.style.width = '';
t.style.height = Math.max(f.offsetHeight, t.offsetHeight).toString() + 'px';
t.style.width = Math.max(f.offsetWidth, t.offsetWidth).toString() + 'px';
f.style.height = Math.max(f.offsetHeight, t.offsetHeight).toString() + 'px';
f.style.width = Math.max(f.offsetWidth, t.offsetWidth).toString() + 'px';
setTimeout(function(){mBlockChange = false}, 100);
}
//This one for IE9+, FFox, Chrome and Safari
//http://help.dottoro.com/ljrmcldi.php
function bindEvents(){
var tA = document.getElementById('A');
var tB = document.getElementById('B');
//The addEventListener() method is not supported in Internet Explorer 8 and earlier versions with resize.
//Resizing the body
document.body.onresize = function(){
//We only do this once the resizing is actually finished.
if (this.Timer) clearTimeout(this.Timer);
this.Timer = setTimeout(function(){
//console.log('Resize', this);
equalSize()
}, 300)
};
//If supported, we listen on dom changes.
if ('MutationEvent' in window){
document.addEventListener('DOMSubtreeModified', function(){
if (document.Timer) clearInterval(document.Timer);
//console.log('DOMSubtreeModified', this);
if (!mBlockChange) equalSize()
}, false);
}
//We set an interval for browsers which do not support DOMSubtreeModified
//If you do not want to rely on ('MutationEvent' in window) put it out of else and cancel the timer (scenario B)
//Can not bind parameters to setInterval in IE8- :s
else{
document.Timer = setInterval(function(){
//console.log('Interval', 'Document');
equalSize()
}, 1000);
}
}
</script>
</head>
<body onload = 'bindEvents()'>
<div id = 'A'><p contenteditable = 'true'>A</p></div>
<div id = 'B'><p contenteditable = 'true'>B</p></div>
</body>
</html>
https://jsfiddle.net/5cn7maqe/
Yet your elements height and width should not magically change, it always requires some interactions, like changing dom by ajax, oninput with contenteditable or resizing the window. You would be better off to just adjust it after those actions manually.
Edit: Made some minor changes.
https://jsfiddle.net/5cn7maqe/1/

Finding position of element within scrollable div

I have these "pages" aka div's inside a scrollable container. On command, I am trying to find out what part of the div in question, is touching the top of .pageContent.
So for example, right when the page loads, no part of #page_1 is touching the top of pageContent, but as I scroll down. #page_1 hits the top of .pageContent and I now want to figure out where that is.
I know I can get the position of .pageContent using $("#pageContent").scrollTop() but these page's could be different sizes and I am not sure how to go about figuring it out.
Could anyone put me in the right direction?
jsfiddle
HTML
<div id="pageContent">
<div id="page_1" class="content"></div>
<div id="page_2" class="content"></div>
<div id="page_3" class="content"></div>
</div>
CSS
#pageContent {
overflow: auto;
width:500px;
height:300px;
padding:10px;
border:1px solid black;
background-color:grey;
}
.content {
height:400px;
width:300px;
margin:0 auto;
background-color:red;
margin-bottom:10px;
}
You can use the jQuery .position() function to compute where each page is in relation to the top of the container. See this Fiddle.
For example, for #page_1,
var page1 = $('#page_1');
$('#pageContent').scroll(function() {
// page1.position().top gives the position of page_1 relative to the
// top of #pageContent
});
ScrollTop can be used, be I wouldn't recommend it.
Attach a scroll event to your main div and listener for all the objects inside:
$('#pageContent').scroll(function(){
var pages = $("#pageContent > .content");
for (var i = 0; i < pages.length; i++)
{
if ($(pages[i]).position().top < 0 && ( $(pages[i]).position().top + $(pages[i]).outerHeight() ) > 0)
{
var outerHeight = $(pages[i]).outerHeight();
var pixels = (outerHeight - (outerHeight + $(pages[i]).position().top));
console.log("These pixels are in view between: " + pixels + " and " + outerHeight );
}
}
})
Every time the div scroll a loop is performed checking the position of all elements. If the elements scroll out of view a the top the if is triggered, calculating the remaining visible pixels of the page currently visible.
This uses jQuery's: position() and outerHeight() and JavaScript's native offsetTop.
http://jsfiddle.net/q5aaLo9L/4/
I tried something like this
$(document).ready(function () {
var divs = $('.content').map(function (i, el) {
return $(el).offset().top - $(el).parent().offset().top;
});
$('#pageContent').scroll(function () {
var index = findIndex($(this).scrollTop(), divs) - 1;
if (index > -1) {
console.log($(this).children().eq(index).attr('id'));
} else {
console.log('outside');
}
});
});
function findIndex(pos, divs) {
return (divs.filter(function (el, et) {
return et <= pos
}).length);
}
It's not super clean code because I had to do it quickly.
DEMO
I hope this helps
I mocked this up, it uses JQuery's each() function to iterate through the pages and return the information of the page that has breached the top of the box.
I wasn't sure from your question exactly what you wanted returned, so I got it to return either the percentage of the page that has cleared the top border, the position (as negative value of pixels) of the top of the "page " in relation to the content container, and also just the ID of that div.
var getCurrentPage = function(){
var page;
var position;
var percentageRead;
$('.content').each(function(){
if($(this).position().top <= 0){
page = $(this);
position = $(this).position().top;
}
});
percentageRead = ((position *-1)/ $(page).height()* 100);
console.log(page.attr('id'));
console.log(position);
console.log(percentageRead + '%');
}
$('#pageContent').on('scroll', getCurrentPage);
You could fire this on any event but I used scroll to build it.

How can I stop Scrolling my <div> onmouseover?

Right now I have a div that I am automatically scrolling infinitely:
<script language="javascript">
i = 0
var speed = 1
function scroll() {
i = i + speed
var div = document.getElementById("content")
div.scrollTop = i
if (i > div.scrollHeight - 160) { i = 0 }
t1 = setTimeout("scroll()", 100)
}
</script>
The div that I need to scroll I want to stop scrolling onmouseover, so I have added this code to the div:
<div id="content" value="Pause" onmouseover="clearTimeout(t1)" onmouseout="scroll()">
Here is a link to a jfiddle. The preview there isn't doing what it should, but that's what I have that's working in my project right now.
So far this is working, but the problem is that I want to be able to manually scroll when I hover over this div. Now, the automatic scrolling is stopping, but I can't manually scroll with just the scrollwheel: It reacts the same when it is scrolling as when it is stopped with onmouseover.
Is there a way that I can basically cancel the whole scroll function onmouseover, or write something that just allows using the scrollwheel/scrollbar. as well? It would also be ok to have code to ALWAYS allow using the scrollwheel/scrollbar.
I'm not sure what would be the best way to do it.
Thanks!!
By setting it to scroll the overflow, assuming a fixed height, the scrollbar will pop up and you can scroll manually. Of course you will want to hide the scrollbar again when it resumes autoscrolling so you will need two functions to set the style.
<script language="javascript">
i = 0;
var speed = 1,t1=null;
function startScroll(){
document.getElementById("content").style.overflowY="hidden";
scroll();
}
function stopScroll(){
clearTimeout(t1);
document.getElementById("content").style.overflowY="scroll";
}
function scroll() {
i = i + speed;
var div = document.getElementById("content");
div.scrollTop = i;
if (i > div.scrollHeight - 160) { i = 0; }
t1 = setTimeout("scroll()", 100);
}
</script>
HTML change:
<div id="content" value="Pause" onmouseover="pauseScroll()" onmouseout="startScroll()">

Categories

Resources