Trigger event when user scroll to specific element - with jQuery - javascript

I have an h1 that is far down a page..
<h1 id="scroll-to">TRIGGER EVENT WHEN SCROLLED TO.</h1>
and I want to trigger an alert when the user scrolls to the h1, or has it in it's browser's view.
$('#scroll-to').scroll(function() {
alert('you have scrolled to the h1!');
});
how do I do this?

You can calculate the offset of the element and then compare that with the scroll value like:
$(window).scroll(function() {
var hT = $('#scroll-to').offset().top,
hH = $('#scroll-to').outerHeight(),
wH = $(window).height(),
wS = $(this).scrollTop();
if (wS > (hT+hH-wH)){
console.log('H1 on the view!');
}
});
Check this Demo Fiddle
Updated Demo Fiddle no alert -- instead FadeIn() the element
Updated code to check if the element is inside the viewport or not. Thus this works whether you are scrolling up or down adding some rules to the if statement:
if (wS > (hT+hH-wH) && (hT > wS) && (wS+wH > hT+hH)){
//Do something
}
Demo Fiddle

Combining this question with the best answer from jQuery trigger action when a user scrolls past a certain part of the page
var element_position = $('#scroll-to').offset().top;
$(window).on('scroll', function() {
var y_scroll_pos = window.pageYOffset;
var scroll_pos_test = element_position;
if(y_scroll_pos > scroll_pos_test) {
//do stuff
}
});
UPDATE
I've improved the code so that it will trigger when the element is half way up the screen rather than at the very top. It will also trigger the code if the user hits the bottom of the screen and the function hasn't fired yet.
var element_position = $('#scroll-to').offset().top;
var screen_height = $(window).height();
var activation_offset = 0.5;//determines how far up the the page the element needs to be before triggering the function
var activation_point = element_position - (screen_height * activation_offset);
var max_scroll_height = $('body').height() - screen_height - 5;//-5 for a little bit of buffer
//Does something when user scrolls to it OR
//Does it when user has reached the bottom of the page and hasn't triggered the function yet
$(window).on('scroll', function() {
var y_scroll_pos = window.pageYOffset;
var element_in_view = y_scroll_pos > activation_point;
var has_reached_bottom_of_page = max_scroll_height <= y_scroll_pos && !element_in_view;
if(element_in_view || has_reached_bottom_of_page) {
//Do something
}
});

I think your best bet would be to leverage an existing library that does that very thing:
http://imakewebthings.com/waypoints/
You can add listeners to your elements that will fire off when your element hits the top of the viewport:
$('#scroll-to').waypoint(function() {
alert('you have scrolled to the h1!');
});
For an amazing demo of it in use:
http://tympanus.net/codrops/2013/07/16/on-scroll-header-effects/

Inview library triggered event and works well with jquery 1.8 and higher!
https://github.com/protonet/jquery.inview
$('div').on('inview', function (event, visible) {
if (visible == true) {
// element is now visible in the viewport
} else {
// element has gone out of viewport
}
});
Read this https://remysharp.com/2009/01/26/element-in-view-event-plugin

Fire scroll only once after a successful scroll
Note: By successful scroll I mean when the user has scrolled to the desired
element or in other words when the desired element is in view
The accepted answer worked 90% for me so I had to tweak it a little to actually fire only once.
$(window).on('scroll',function() {
var hT = $('#comment-box-section').offset().top,
hH = $('#comment-box-section').outerHeight(),
wH = $(window).height(),
wS = $(this).scrollTop();
if (wS > ((hT+hH-wH)-500)){
console.log('comment box section arrived! eh');
// This detaches the scroll so doStuff() won't run more than once
$(window).off('scroll');
doStuff();
}
});

You could use this for all devices,
$(document).on('scroll', function() {
if( $(this).scrollTop() >= $('#target_element').position().top ){
do_something();
}
});

Intersection Observer can be the best thing IMO, without any external library it does a really good job.
const options = {
root: null,
threshold: 0.25, // 0 - 1 this work as a trigger.
rootMargin: '150px'
};
const target = document.querySelector('h1#scroll-to');
const observer = new IntersectionObserver(
entries => { // each entry checks if the element is the view or not and if yes trigger the function accordingly
entries.forEach(() => {
alert('you have scrolled to the h1!')
});
}, options);
observer.observe(target);

You can use jQuery plugin with the inview event like this :
jQuery('.your-class-here').one('inview', function (event, visible) {
if (visible == true) {
//Enjoy !
}
});
Link : https://remysharp.com/2009/01/26/element-in-view-event-plugin

This should be what you need.
Javascript:
$(window).scroll(function() {
var hT = $('#circle').offset().top,
hH = $('#circle').outerHeight(),
wH = $(window).height(),
wS = $(this).scrollTop();
console.log((hT - wH), wS);
if (wS > (hT + hH - wH)) {
$('.count').each(function() {
$(this).prop('Counter', 0).animate({
Counter: $(this).text()
}, {
duration: 900,
easing: 'swing',
step: function(now) {
$(this).text(Math.ceil(now));
}
});
}); {
$('.count').removeClass('count').addClass('counted');
};
}
});
CSS:
#circle
{
width: 100px;
height: 100px;
background: blue;
-moz-border-radius: 50px;
-webkit-border-radius: 50px;
border-radius: 50px;
float:left;
margin:5px;
}
.count, .counted
{
line-height: 100px;
color:white;
margin-left:30px;
font-size:25px;
}
#talkbubble {
width: 120px;
height: 80px;
background: green;
position: relative;
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
border-radius: 10px;
float:left;
margin:20px;
}
#talkbubble:before {
content:"";
position: absolute;
right: 100%;
top: 15px;
width: 0;
height: 0;
border-top: 13px solid transparent;
border-right: 20px solid green;
border-bottom: 13px solid transparent;
}
HTML:
<div id="talkbubble"><span class="count">145</span></div>
<div style="clear:both"></div>
<div id="talkbubble"><span class="count">145</span></div>
<div style="clear:both"></div>
<div id="circle"><span class="count">1234</span></div>
Check this bootply:
http://www.bootply.com/atin_agarwal2/cJBywxX5Qp

If you are looking for a javascript version. You can call this method on scroll event listener.
showScrollTop = () =>{
const currentScrollPosition = window.pageYOffset;
let elementID = 'service-selector'
const elementOffsetTop = document.getElementById(elementID).offsetTop
if ( currentScrollPosition > elementOffsetTop){
// place your logic here
} else {
// place your logic here
}
}
window.addEventListener('scroll', showScrollTop)

If you are doing a lot of functionality based on scroll position, Scroll magic (http://scrollmagic.io/) is built entirely for this purpose.
It makes it easy to trigger JS based on when the user reaches certain elements when scrolling. It also integrates with the GSAP animation engine (https://greensock.com/) which is great for parallax scrolling websites

Just a quick modification to DaniP's answer, for anyone dealing with elements that can sometimes extend beyond the bounds of the device's viewport.
Added just a slight conditional - In the case of elements that are bigger than the viewport, the element will be revealed once it's top half has completely filled the viewport.
function elementInView(el) {
// The vertical distance between the top of the page and the top of the element.
var elementOffset = $(el).offset().top;
// The height of the element, including padding and borders.
var elementOuterHeight = $(el).outerHeight();
// Height of the window without margins, padding, borders.
var windowHeight = $(window).height();
// The vertical distance between the top of the page and the top of the viewport.
var scrollOffset = $(this).scrollTop();
if (elementOuterHeight < windowHeight) {
// Element is smaller than viewport.
if (scrollOffset > (elementOffset + elementOuterHeight - windowHeight)) {
// Element is completely inside viewport, reveal the element!
return true;
}
} else {
// Element is larger than the viewport, handle visibility differently.
// Consider it visible as soon as it's top half has filled the viewport.
if (scrollOffset > elementOffset) {
// The top of the viewport has touched the top of the element, reveal the element!
return true;
}
}
return false;
}

I use the same code doing that all the time, so added a simple jquery plugin doing it.
480 bytes long, and fast. Only bound elements analyzed in runtime.
https://www.npmjs.com/package/jquery-on-scrolled-to
It will be
$('#scroll-to').onScrolledTo(0, function() {
alert('you have scrolled to the h1!');
});
or use 0.5 instead of 0 if need to alert when half of the h1 shown.

Quick and fast implementation,
let triggered = false;
$(window).on('scroll',function() {
if (window.scrollY > ($('#scrollTo').offset().top+$('#scrollTo').outerHeight()-window.innerHeight) & !triggered){
console.log('triggered here on scroll..');
triggered = true;
}
});
using global variable triggered = false makes it just to happen once, otherwise, every time crossing past the element, this action is triggered.

Related

How to detect if the user has scroll 100px from current position

I have a one-page website where I am adding a class while the user clicks on nav. However, if the user has scroll 100px from the current location the class need to remove.
DEMO gh pages link
//working fine
var scrollvalue = 0;
$('a.js-scroll-trigger[href*="#"]:not([href="#"])').click(function() {
scrollvalue = $(window).scrollTop();
$(".copyright").addClass("activecopy");
});
//not working fine
$(window).scroll(function() {
if($(window).scrollTop() > 100) {
$('.copyright').removeClass('activecopy');
}
});
Note: I have already read stackoverflow post such as post1 and post2
It's a little hard to figure out what exactly the problem is as you have no shared the corresponding HTML markup. Try the following and let me know if it helps.
var scrollvalue = 0;
$('a.js-scroll-trigger[href*="#"]:not([href="#"])').click(function () {
scrollvalue = $(window).scrollTop();
$(".copyright").addClass("activecopy");
});
$(window).scroll(function () {
if (($(window).scrollTop() - scrollvalue) > 100) {
$('.copyright').removeClass('activecopy');
}
});
EDIT:
As I said, it's hard to see what's happening because you haven't shared markup. Here is a sample. Hope it helps.
EDIT 2:
To make this generic, you can wrap your code which registers for click listeners and scroll listeners in a function which accepts which elements to operate on as arguments. Sample Below.
function registerScrollTrigger(anchor, target) {
var $a = $(anchor);
var $t = $(target);
$a.click(function() {
//Get scroll position at the time of the click
var currentScroll = $(window).scrollTop();
function handleScroll() {
// Demo code to show current scroll on the screen
$t.html('Current Scroll: ' + ($(window).scrollTop() - currentScroll));
// Check if the user has scrolled 100px since clicking the tag
if (($(window).scrollTop() - currentScroll) > 100) {
// Remove active class from element
$t.removeClass('active');
// Demo code ti indicate that the scroll to 100px is complete
$t.html('Complete');
// Stop listening for scroll events [Optional but recommmended]
$(window).off('scroll', handleScroll);
}
}
// Add active class to element [Make it blue]
$t.addClass('active');
// Listen for scroll event and check if 100px has passed
$(window).scroll(handleScroll);
});
}
registerScrollTrigger('#a1', '#scroll1');
registerScrollTrigger('#a2', '#scroll2');
div.scroll {
margin-top: 50px;
}
div.scroll.active {
background: blue;
color: white;
}
div#pad {
height: 1000px;
}
h4 {
margin-bottom: 500px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<h4>Scroll Down For the Button</h4>
<a id="a1" class="js-scroll">Click Me </a>
<div id="scroll1" class="scroll">
Start scrolling after clicking the above button
</div>
<h4>Scroll Down For Another Button</h4>
<a id="a2" class="js-scroll">Click Me Too</a>
<div id="scroll2" class="scroll">
Start scrolling after clicking the above button
</div>
<div id="pad"></div>
Note:
You can also do something similar by setting a data-target attribute on the anchor which can be used to determine which item to add the class to and remove the class from instead of passing both items as a parameter
$(window).scroll(function() {
var height = $(window).scrollTop();
if (height > 100) {
$(".copyright").addClass("activecopy");
} else {
$('.copyright').removeClass('activecopy');
}
});
I am using this for showing my gototop button in bottom. Hope this will works for you.....

jQuery doesn't show and hide the header

I'm trying to make a header that appears at a certain place of the page.
So what I'm doing is checking the scroll to top of the page and the top offset of the element after which the header should appear. If the scrollTop is greater than offset the header is shown, otherwise it disappears.
But! When I scroll to the place, the header position is constantly switching between top: -13% and top: -12.999998%. After some time it finally shows the header but it never disappears.
What am I doing wrong?!
JSFiddle: https://jsfiddle.net/5k5s016f/
Well, i think the problem is that the .animate() functions are running constantly, causing the animations to "restart" before its ends.
It is not the most beautiful solution, but just adding a flag that controls the execution of the functions and a timeout to run the handler less frequently solves the problem.
https://jsfiddle.net/5k5s016f/2/
var visible = false;
$(window).scroll(function() {
setTimeout(function(){
var height = $(window).scrollTop();
var $page2 = $("#page2");
var offset = $page2.offset().top;
if (height > offset) {
if (visible) {
return;
}
visible = true;
$(".floating-header").show().animate({
top: 0
});
} else {
if (!visible) {
return;
}
visible = false;
$(".floating-header").animate({
top: "-13%"
});
}
}, 200)
});
The issue you are seeing is because each time a scroll event gets called animation queues up. If you wait long enough, you can see that the animation to set top to 0 actually works.
You can use the stop() function to stop all animation before attempting to run another one.
Something like this
if (height > offset) {
$(".floating-header").stop().show().animate({
top: "0"
}, 700);
} else {
$(".floating-header").stop().animate({
top: "-13%"
}, 700);
}
A couple of improvements I can suggest are
Debounce the scroll event handler
Check the current state of the header before queuing animation. i.e. do not try to hide it if it is already hidden and vice versa
Your logic is all messed up. Basically, you want to make sure that you are only animating when you absolutely need to - no more, no less. And since scroll events happen hundreds of times... constantly rapid firing as the user scrolls... you want to make sure you are doing the least amount of work possible during each scroll event. This especially means that you don't want to be querying the DOM on every scroll event if you don't have to (ps. $('selector') is a dom query). Take a look at this fiddle:
https://jsfiddle.net/5k5s016f/6/
Looks like I'm last to the party due to interruptions, but since I wrote it up I'll post the answer FWIW.
jsFiddle Demo
You need to debounce your code. Here is a simple system, but studing Ben Alman's explanation/examples is also recommended.
var $m1 = $('#m1'), $m2 = $('#m2'); //TESTING ONLY
var $win = $(window), $page2 = $("#page2"), $hdr=$(".floating-header");
var $offset = $page2.offset().top;
var hvis = false, curpos;
$win.scroll(function() {
curpos = $win.scrollTop();
$m1.html(curpos); //TESTING ONLY
$m2.html($offset);//TESTING ONLY
if ( curpos > $offset ) {
if ( !hvis ){
hvis = true;
//$m1.html(curpos);
$hdr.finish().animate({
top: "0"
}, 700);
}
} else {
if ( hvis ){
$hdr.finish().animate({
top: "-60px"
}, 700);
hvis = false;
}
}
});
html,
body {
height: 100vh;
width: 100vw;
}
#page1,
#page2,
#page3 {
height: 100%;
width: 100%;
background-color: #fff;
}
.floating-header {
position: fixed;
top: -60px;
background-color: #000;
width: 100%;
height: 60px;
}
.msg{position:fixed;bottom:10px;height:30px;width:80px;text-align:center;}
.msg{padding-top:10px;}
#m1 {left:3px; border:1px solid orange;background:wheat;}
#m2 {right:3px;border:1px solid green; background:palegreen;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<header class="floating-header">Header</header>
<div id="page1">
<p>Page1</p>
</div>
<div id="page2">
<p>Page2</p>
</div>
<div id="page3">
<p>Page3</p>
</div>
<div id="m1" class="msg"></div>
<div id="m2" class="msg"></div>

A cleaner way for a fixed div at bottom of the window but stays above the footer and triggers on page width

I've created a sticky bar to stay at the bottom of the window. As the user scrolls down to the bottom of the page the same bar will stay fixed until the footer shows, then removes its fixed position, temporarily, to stay above the footer until the user scrolls back up and it remains fixed again.
I only want to happen when the page is wider than 680px. Anything under that will keep the sticky bar in a default position (CSS: position:inherit).
This is the website: http://ttd.firefly-digital.co.uk
It works as expected. However, when I test on Chrome in Mac it triggers my CPU fan which suggests this not very efficient and with my limited JavaScript skills, wondered if there is a cleaner way to achieve this is?
This is the current js code:
$(window).scroll(function(event) {
var scroll = $(this).scrollTop();
var docHeight = $(document).height();
var windowHeight = $(window).height();
var footerHeight = $('.footer').height();
if(docHeight - (windowHeight + scroll) < footerHeight) {
$('.contact-bar').css({
bottom: footerHeight - (docHeight - (windowHeight + scroll))
});
} else {
$('.contact-bar').css({
bottom: 0
});
}
});
var windowWidth = $(window).width();
$(window).resize(function() {
windowWidth = $(window).width();
if(windowWidth > 680) {
$('.contact-bar').css({
position: "fixed"
});
} else {
$('.contact-bar').css({
position: "inherit"
});
}
});
CSS code
.contact-bar {
background: $contact-bar;
width: 100%;
height: 40px;
text-align: center;
position: fixed;
bottom: 0;
z-index: 10;
}
You can do it in reverse. Make it so that the bar, without position fixed, is above the footer without any JavaScript (incl. media queries). Than add a fixed class with position:fixed and bottom:0 that will be added accordingly. Like so:
.contact-bar.fixed { position:fixed; bottom:0; }
The jquery code that will trigger this, is as follows:
$(window).scroll(function (event) {
var windowTop = $(this).scrollTop();
if (windowTop >= $(".footer").offset().top) {
$(".contact-bar").addClass("fixed");
} else {
$(".contact-bar").removeClass("fixed");
}
});
Then add a few lines that the above code will only fire if the window width is > 680, either with jquery or pure javascript. For example with:
if ($(window).width() < 960) { // above function }
Do note I have not tested this, so please comment if it doesn't work. Credit: Preventing element from displaying on top of footer when using position:fixed
You better use classes to target your elements, at least to prevent jQuery from traversing the whole DOM using selectors appropriately which is good in performance.

jQuery scroll event: how to determine amount scrolled (scroll delta) in pixels?

I have this event:
$(window).scroll(function(e){
console.log(e);
})
I want to know, how much I have scroll value in pixels, because I think, scroll value depends from window size and screen resolution.
Function parameter e does not contains this information.
I can store $(window).scrollTop() after every scroll and calculate difference, but can I do it differently?
The "scroll value" does not depend on the window size or screen resolution. The "scroll value" is simply the number of pixels scrolled.
However, whether you are able to scroll at all, and the amount you can scroll is based on available real estate for the container and the dimensions of the content within the container (in this case the container is document.documentElement, or document.body for older browsers).
You are correct that the scroll event does not contain this information. It does not provide a delta property to indicate the number of pixels scrolled. This is true for the native scroll event and the jQuery scroll event. This seems like it would be a useful feature to have, similar to how mousewheel events provide properties for X and Y delta.
I do not know, and will not speculate upon, why the powers-that-be did not provide a delta property for scroll, but that is out of scope for this question (feel free to post a separate question about this).
The method you are using of storing scrollTop in a variable and comparing it to the current scrollTop is the best (and only) method I have found. However, you can simplify this a bit by extending jQuery to provide a new custom event, per this article: http://learn.jquery.com/events/event-extensions/
Here is an example extension I created that works with window / document scrolling. It is a custom event called scrolldelta that automatically tracks the X and Y delta (as scrollLeftDelta and scrollTopDelta, respectively). I have not tried it with other elements; leaving this as exercise for the reader. This works in currrent versions of Chrome and Firefox. It uses the trick for getting the sum of document.documentElement.scrollTop and document.body.scrollTop to handle the bug where Chrome updates body.scrollTop instead of documentElement.scrollTop (IE and FF update documentElement.scrollTop; see https://code.google.com/p/chromium/issues/detail?id=2891).
JSFiddle demo: http://jsfiddle.net/tew9zxc1/
Runnable Snippet (scroll down and click Run code snippet):
// custom 'scrolldelta' event extends 'scroll' event
jQuery.event.special.scrolldelta = {
delegateType: "scroll",
bindType: "scroll",
handle: function (event) {
var handleObj = event.handleObj;
var targetData = jQuery.data(event.target);
var ret = null;
var elem = event.target;
var isDoc = elem === document;
var oldTop = targetData.top || 0;
var oldLeft = targetData.left || 0;
targetData.top = isDoc ? elem.documentElement.scrollTop + elem.body.scrollTop : elem.scrollTop;
targetData.left = isDoc ? elem.documentElement.scrollLeft + elem.body.scrollLeft : elem.scrollLeft;
event.scrollTopDelta = targetData.top - oldTop;
event.scrollTop = targetData.top;
event.scrollLeftDelta = targetData.left - oldLeft;
event.scrollLeft = targetData.left;
event.type = handleObj.origType;
ret = handleObj.handler.apply(this, arguments);
event.type = handleObj.type;
return ret;
}
};
// bind to custom 'scrolldelta' event
$(window).on('scrolldelta', function (e) {
var top = e.scrollTop;
var topDelta = e.scrollTopDelta;
var left = e.scrollLeft;
var leftDelta = e.scrollLeftDelta;
// do stuff with the above info; for now just display it to user
var feedbackText = 'scrollTop: ' + top.toString() + 'px (' + (topDelta >= 0 ? '+' : '') + topDelta.toString() + 'px), scrollLeft: ' + left.toString() + 'px (' + (leftDelta >= 0 ? '+' : '') + leftDelta.toString() + 'px)';
document.getElementById('feedback').innerHTML = feedbackText;
});
#content {
/* make window tall enough for vertical scroll */
height: 2000px;
/* make window wide enough for horizontal scroll */
width: 2000px;
/* visualization of scrollable content */
background-color: blue;
}
#feedback {
border:2px solid red;
padding: 4px;
color: black;
position: fixed;
top: 0;
height: 20px;
background-color: #fff;
font-family:'Segoe UI', 'Arial';
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='feedback'>scrollTop: 0px, scrollLeft: 0px</div>
<div id='content'></div>
Note that you may want debounce the event depending on what you are doing. You didn't provide very much context in your question, but if you give a better example of what you are actually using this info for we can provide a better answer. (Please show more of your code, and how you are using the "scroll value").
To detemine how many pixels were scrolled you have to keep in mind that the scroll event gets fired almost every pixel that you move. The way to accomplish it is to save the previous scrolled value and compare that in a timeout. Like this:
var scrollValue = 0;
var scrollTimeout = false
$(window).scroll(function(event){
/* Clear it so the function only triggers when scroll events have stopped firing*/
clearTimeout(scrollTimeout);
/* Set it so it fires after a second, but gets cleared after a new triggered event*/
scrollTimeout = setTimeout(function(){
var scrolled = $(document).scrollTop() - scrollValue;
scrollValue = $(document).scrollTop();
alert("The value scrolled was " + scrolled);
}, 1000);
});
This way you will get the amount of scrolled a second after scrolling (this is adjustable but you have to keep in mind that the smooth scrolling that is so prevalent today has some run-out time and you dont want to trigger before a full stop).
The other way to do this? Yes, possible, with jQuery Mobile
I do not appreciate this solution, because it is necessary to include heavy jQuery mobile. Solution:
var diff, top = 0;
$(document).on("scrollstart",function () {
// event fired when scrolling is started
top = $(window).scrollTop();
});
$(document).on("scrollstop",function () {
// event fired when scrolling is stopped
diff = Math.abs($(window).scrollTop() - top);
});
To reduce the used processing power by adding a timer to a Jquery scroll method is probably not a great idea. The visual effect is indeed quite bad.
The whole web browsing experience could be made much better by hiding the scrolling element just when the scroll begins and making it slide in (at the right position) some time after. The scrolling even can be checked with a delay too.
This solution works great.
$(document).ready(function() {
var element = $('.movable_div'),
originalY = element.offset().top;
element.css('position', 'relative');
$(window).on('scroll', function(event) {
var scrollTop = $(window).scrollTop();
element.hide();
element.stop(false, false).animate({
top: scrollTop < originalY
? 0
: scrollTop - originalY + 35
}, 2000,function(){element.slideDown(500,"swing");});
});
});
Live demo here

Infinite scrolling in both directions - Up and down

I am trying to create a page that is an endless scrolling loop both up and down.
At the moment I am using jquery to relocate content from the top of the page to the bottom. This creates a nice seemless loop when you scroll down but I would like it to work when the user scrolls up too.
The problem seems to be that even if content is positioned in negative vertical space on the page the scroll will not extend to that space. As far as I am aware there is no way to override this so I am looking for some type of work around.
I have thoughts of using javascript to disable the scrolling and using the scroll event to reposition the elements but there are already lots of absolute positioned elements and animation happening on the page so I'm concerned about performance taking that route.
Any other leads?
OK... I worked it out.
I adapted this script which instantly relocates the scroll location to the top of the page when you get to the bottom and to the bottom when you reach the top.
$(window).scroll(function() {
if ( $(window).scrollTop() >= 18830 ) {
$(window).scrollTop(201);
}
else if ( $(window).scrollTop() == 0 ) {
$(window).scrollTop(18629);
}
});
And then I made sure that the content at the bottom and the top of the page was identical. I thought that there would be a flash or something when this relocation happened but it's smooth!
The solution I like the best is this one (code), because it adds elements at the bottom before the bottom is reached, making sure that scrolling remains continuous (even with smooth scrolling on). However, it doesn't work that well on mobile phones where scrolling can happen pretty quickly. I recommend Marijn Haverbeke's wonderful article on fake scrollbars in CodeMirror where he deals with similar issues.
I leave you with some snippets.
First, some background. Why would we want to fake a scrollbar to begin with?
In order to remain responsive when huge documents are loaded in, CodeMirror does not render the whole document, but only the part of it that is currently scrolled into view. This means that the amount of DOM nodes it creates is limited by the size of the viewport, and the browser relayouts triggered by changes to the text are relatively cheap.
And further down...
Then, it listens to wheel events, but never calls preventDefault on them or does scrolling in response to them. Instead, it responds by setting a timeout to observe the amount of pixels that the wheel event did scroll the content, and uses that to tweak its delta-to-pixel rate at run-time.
Clone your HTML body two (or three) times (in javascript or otherwise). Start the page in the middle copy instead of the top, and then you can handle scrolling however you like.
Any other leads?
Seen these?
5 jQuery infinite Scrolling Demos
jsfiddle that I cannot find origin of. (I didn't write and don't know who did)
As many have suggested, if your page doesn't look exactly the same at the top and at the bottom you’ll need to clone your content to make it look like it does. I’ve made an example using this technique that works pretty smooth:
/*
Ininite looping scroll.
Tested and works well in latest Chrome, Safari and Firefox.
*/
(function (window) {
'use strict';
var doc = document,
body = doc.body,
html = doc.documentElement,
startElement = doc.getElementsByClassName('is-start')[0],
clones = doc.getElementsByClassName('is-clone'),
disableScroll = false,
docHeight,
scrollPos,
clonesHeight,
i;
function getScrollPos() {
return (window.pageYOffset || html.scrollTop) - (html.clientTop || 0);
}
function getDocHeight() {
return Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
}
function getClonesHeight() {
i = 0;
clonesHeight = 0;
for (i; i < clones.length; i += 1) {
clonesHeight = clonesHeight + clones[i].offsetHeight;
}
return clonesHeight;
}
docHeight = getDocHeight();
clonesHeight = getClonesHeight();
window.addEventListener('resize', function () {
scrollPos = getScrollPos();
docHeight = getDocHeight();
clonesHeight = getClonesHeight();
if (scrollPos <= 0) {
window.scroll(0, 1); // Scroll 1 pixel to allow upwards scrolling.
}
}, false);
window.addEventListener('scroll', function () {
if (disableScroll === false) {
scrollPos = getScrollPos();
if (clonesHeight + scrollPos >= docHeight) {
// Scroll to the top when you’ve reached the bottom
window.scroll(0, 1); // Scroll 1 pixel to allow upwards scrolling.
disableScroll = true;
} else if (scrollPos <= 0) {
// Scroll to the top of the clones when you reach the top.
window.scroll(0, docHeight - clonesHeight);
disableScroll = true;
}
if (disableScroll) {
// Disable scroll-repositioning for a while to avoid flickering.
window.setTimeout(function () {
disableScroll = false;
}, 100);
}
}
}, false);
// Needs a small delay in some browsers.
window.setTimeout(function () {
if (startElement) {
// Start at the middle of the starting block.
window.scroll(0, Math.round(startElement.getBoundingClientRect().top + document.body.scrollTop - (window.innerHeight - startElement.offsetHeight) / 2));
} else {
// Scroll 1 pixel to allow upwards scrolling.
window.scroll(0, 1);
}
});
}(this));
section {
position: relative;
text-align: center;
height: 80vh;
}
.red {
background: #FF4136;
}
.green {
background: #2ECC40;
}
.blue {
background: #0074D9;
}
.orange {
background: rebeccapurple;
}
h1 {
margin: 0;
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 100%;
font-size: 5vw;
color: #fff;
text-transform: uppercase;
}
body {
font-family: "Avenir Next", Montserrat, Helvetica, sans-serif;
font-weight: normal;
font-size: 100%;
}
::scrollbar {
display: none;
}
<section class="green">
<h1>One</h1>
</section>
<section class="red">
<h1>For</h1>
</section>
<section class="blue">
<h1>All</h1>
</section>
<section class="orange">
<h1>And</h1>
</section>
<section class="blue">
<h1>All</h1>
</section>
<section class="red">
<h1>For</h1>
</section>
<!--
These following blocks are the same as the first blocks to get that looping illusion going. You need to add clones to fill out a full viewport height.
-->
<section class="green is-clone is-start">
<h1>One</h1>
</section>
<section class="red is-clone">
<h1>For</h1>
</section>
Building up on Mahmoud's answer, I hacked up this in a few minutes.
It works somewhat (at least on Firefox) when scrolling either with keys or with mouse wheel, but it gets all glitchy when dragging the scrollbar. Depending on how the div heights relate to the viewport height, all kinds of fireworks can happen too.
Still, I hope this can help you get on the right direction.
function onScroll(){
var SAFETY_MARGIN = 50,
scrollPos = $(this).scrollTop(),
docHeight = $(document.body).height(),
winHeight = $(window).height(),
firstDiv = $('body>div:first-child')[0],
lastDiv = $('body>div:last-child')[0],
lowerLimit = SAFETY_MARGIN,
higherLimit = docHeight - SAFETY_MARGIN;
// Scrolling too high
if( scrollPos <= lowerLimit ){
// Move content to top;
$(lastDiv).prependTo(document.body);
// Adjust scroll position to compensate
// for the new content at the top
$(window).scrollTop(scrollPos + $(lastDiv).height());
}
// Scrolling too low
else if( scrollPos + winHeight >= higherLimit ){
// Move content to bottom
$(firstDiv).appendTo(document.body);
// Adjust scroll position to compensate
// for the missing content at the top
$(window).scrollTop(scrollPos - $(firstDiv).height());
}
}
$(window).scroll(onScroll);
$(window).load(function(){
var $body = $(document.body);
$(window).scrollTop($body.height() / 2);
});
</script>
</head>
<body>
<div style="height: 600px; background-color: red"> </div>
<div style="height: 600px; background-color: green"> </div>
<div style="height: 600px; background-color: blue"> </div>
</body>
</html>

Categories

Resources