I am trying to determine if the user has scrolled up or down and I found some code in a different answer that seems to help me out. My one problem with this code is that I cannot wrap my head around how to capture last_scroll_position. I have a function set up to that returns scrollTop so getting the value for the variable current_position is not a problem, but getting the value for last_scroll_position seems a bit tricky.
Here is the answer I found...
Keep a variable, say, last_scroll_position, and when you have a scroll, if last_scroll_position - current_position > 0, the user scrolled up, and down if it's less than 0.
Differentiate between scroll up/down in jquery?
I recommend that check out this example: stackoverflow.com/a/24815216...
The scroll event behaves oddly in Firefox, it is fired a lot of times because of the smoothness scrolling, but it works, here are an example:
//creates an element to print the scroll position
$("<p id='test'>").appendTo("body").css({
padding: "5px 7px",
background: "#e9e9e9",
position: "fixed",
bottom: "35px",
left: "35px" });
//binds the "scroll" event
$(window).scroll(function (e) {
var target = e.currentTarget,
scrollTop = target.scrollTop || window.pageYOffset,
scrollHeight = target.scrollHeight || document.body.scrollHeight,
lastScrollTop = $(target).data("lastScrollTop") || 0,
scrollText = "";
if (scrollTop > lastScrollTop) {
scrollText = "<b>scroll down</b>";
} else {
scrollText = "<b>scroll up</b>";
}
$("#test").html(scrollText +
"<br>scrollTop: " + scrollTop +
"<br>lastScrollTop: " + lastScrollTop);
if (scrollHeight - scrollTop === $(target).innerHeight()) {
console.log("► End of scroll");
}
//saves the current scrollTop
$(target).data("lastScrollTop", scrollTop);
});
Related
I'm currently making an overlay that covers a sticky top bar when the user has scrolled beyond a certain point (down) and disappears when scrolling back up. However, I'd like to be able to scroll for at least 50px before the code is executed (something like a gap before the overlay is triggered).
$(function() {
var prevScroll = $(document).scrollTop(); //initial position
$(window).scroll(function() {
var newScroll = $(document).scrollTop(); //position from top after scrolling
if(newScroll > prevScroll) { // checks if the user has scrolled up or down
var fromNew = $(document).scrollTop(); // holds value to compare with the position + gap amount
if (fromNew > newScroll + 50) { //checks to see if scrolled for 50px
$("#stick-start").fadeIn("fast");
prevScroll = newScroll + 50; //initial position + scrolled amount
};
} else {
var fromNew = $(document).scrollTop();
if (fromNew > newScroll - 50) {
if ($("#stick-start").hasClass("is-stuck")) {
$("#stick-start").fadeOut("fast");
prevScroll = newScroll - 50;
};
};
};
});
});
The condition that checks whether you're scrolling up or down works. But as it is now, the overlay just keeps fading in and out repeatedly. How do I make it so that you have to scroll at least 50px before anything happens ?
I think this should get you where you're going.
var $document = $(document);
$(window).scroll(function() {
if ($document.scrollTop() >= 50) {
$("#stick-start").fadeIn("fast");
} else {
$("#stick-start").fadeOut("fast");
}
});
EDIT: had an error, should be good now.
$(window).scroll(function() {
if ($(this).scrollTop() >= 50) {
$("#stick-start").fadeIn();
} else {
$("#stick-start").fadeOut();
}
});
I'm trying to make some of my element fixed for vertical and horizontal scrolling and looked up some examples on stackoverflow but I could not figure out why they work, therefore I cannot use it for a different purpose:
This is the code that everybody gives, and it works:
$(window).scroll(function() {
$('#header').css({
'top': $(this).scrollTop() + 15,
'left': $(this).scrollLeft() + 15 // top, left = 15px in css
});
}
So what it is doing is set the position of top and left of the header id tag so everytime window is scrolled, it goes to the position relative to window?
$(this).scrollTop() always print out 0 in my test however, if I do the below instead, it stop working:
function test() { /* Original code example, keeping this unmodified so some answers doesn't seem strange */
$('#header').css({
'top': 15,
'left': 15 // hardcode 15 just for example
});
}
What is the purpose of $(this).scrollTop() here that makes or breaks the functionality?
Lastly, I'm not allowed to use JQuery so I use javascript and none of these variations are working. Could you tell me what I'm doing wrong?
function test() {
var header = getElementById('header');
header.style.top = header.scrollTop + 15;
header.style.left = header.scrollLeft + 15;
}
also tried few others such as: using '15px', 15 + 'px'.
edit: modified first code example to the correct original code from stackoverflow
The getElementById function is not global on its own -- it is under the global document object. So you must use var header = document.getElementById('header');
Next, the browser's native scrollTop and scrollLeft functions used on a DOM element will tell you the scroll inside that element. For instance, for scrollTop is the measurement from the top of the element (which may be hidden because of scroll) to the top visible edge (In other words, it is the distance above the visible part of the element -- the part that's been hidden because of internal scrolling). If the top of the header element is always completely visible (and the header doesn't have it's own scrollbar), then this distance will always be zero.
To position the header relative to the scroll of the browser window, you may want to get the pageYOffset and pageXOffset on the window element instead, and then position the header based on that. For instance, window.pageYOffset + 15.
Here is a safer alternative that checks various possible places for the page's scroll (helps with browser compatibility).
var scrollTop = function(){
return (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
}
var scrollLeft = function(){
return (window.pageXOffset !== undefined) ? window.pageXOffset : (document.documentElement || document.body.parentNode || document.body).scrollLeft;
}
The last thing is that element.style.top should be given a string value that is like "10px", so you will need to do the calculation first and then add + 'px' at the end.
eg. header.style.top = window.scrollTop + 15 + "px";
Putting that all together:
var scrollTop = function(){
return (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
}
var scrollLeft = function(){
return (window.pageXOffset !== undefined) ? window.pageXOffset : (document.documentElement || document.body.parentNode || document.body).scrollLeft;
}
function test() {
var header = document.getElementById('header');
header.style.top = scrollTop() + 15 + 'px';
header.style.left = scrollLeft() + 15 + 'px';
}
Good luck! Hope that helps!
top and left property of css not going to work in this condition if you do not
add position property ..
function test() {
$('#header').css({
'position':'absolute',
'top': $(this).scrollTop() + 215,
'left': $(this).scrollLeft() + 15 // top, left = 15px in css
});
}
LIVE http://jsfiddle.net/mailmerohit5/t45ktx4r/
I need to retrieve the visible height of a div within a scrollable area. I consider myself pretty decent with jQuery, but this is completely throwing me off.
Let's say I've got a red div within a black wrapper:
In the graphic above, the jQuery function would return 248, the visible portion of the div.
Once the user scrolls past the top of the div, as in the above graphic, it would report 296.
Now, once the user has scrolled past the div, it would again report 248.
Obviously my numbers aren't going to be as consistent and clear as they are in this demo, or I'd just hard code for those numbers.
I have a bit of a theory:
Get the height of the window
Get the height of the div
Get the initial offset of the div from the top of the window
Get the offset as the user scrolls.
If the offset is positive, it means the top of the div is still visible.
if it's negative, the top of the div has been eclipsed by the window. At this point, the div could either be taking up the whole height of the window, or the bottom of the div could be showing
If the bottom of the div is showing, figure out the gap between it and the bottom of the window.
It seems pretty simple, but I just can't wrap my head around it. I'll take another crack tomorrow morning; I just figured some of you geniuses might be able to help.
Thanks!
UPDATE: I figured this out on my own, but looks like one of the answers below is more elegant, so I'll be using that instead. For the curious, here's what I came up with:
$(document).ready(function() {
var windowHeight = $(window).height();
var overviewHeight = $("#overview").height();
var overviewStaticTop = $("#overview").offset().top;
var overviewScrollTop = overviewStaticTop - $(window).scrollTop();
var overviewStaticBottom = overviewStaticTop + $("#overview").height();
var overviewScrollBottom = windowHeight - (overviewStaticBottom - $(window).scrollTop());
var visibleArea;
if ((overviewHeight + overviewScrollTop) < windowHeight) {
// alert("bottom is showing!");
visibleArea = windowHeight - overviewScrollBottom;
// alert(visibleArea);
} else {
if (overviewScrollTop < 0) {
// alert("is full height");
visibleArea = windowHeight;
// alert(visibleArea);
} else {
// alert("top is showing");
visibleArea = windowHeight - overviewScrollTop;
// alert(visibleArea);
}
}
});
Calculate the amount of px an element (height) is in viewport
Fiddle demo
This tiny function will return the amount of px an element is visible in the (vertical) Viewport:
function inViewport($el) {
var elH = $el.outerHeight(),
H = $(window).height(),
r = $el[0].getBoundingClientRect(), t=r.top, b=r.bottom;
return Math.max(0, t>0? Math.min(elH, H-t) : Math.min(b, H));
}
Use like:
$(window).on("scroll resize", function(){
console.log( inViewport($('#elementID')) ); // n px in viewport
});
that's it.
jQuery .inViewport() Plugin
jsFiddle demo
from the above you can extract the logic and create a plugin like this one:
/**
* inViewport jQuery plugin by Roko C.B.
* http://stackoverflow.com/a/26831113/383904
* Returns a callback function with an argument holding
* the current amount of px an element is visible in viewport
* (The min returned value is 0 (element outside of viewport)
*/
;(function($, win) {
$.fn.inViewport = function(cb) {
return this.each(function(i,el) {
function visPx(){
var elH = $(el).outerHeight(),
H = $(win).height(),
r = el.getBoundingClientRect(), t=r.top, b=r.bottom;
return cb.call(el, Math.max(0, t>0? Math.min(elH, H-t) : Math.min(b, H)));
}
visPx();
$(win).on("resize scroll", visPx);
});
};
}(jQuery, window));
Use like:
$("selector").inViewport(function(px) {
console.log( px ); // `px` represents the amount of visible height
if(px > 0) {
// do this if element enters the viewport // px > 0
}else{
// do that if element exits the viewport // px = 0
}
}); // Here you can chain other jQuery methods to your selector
your selectors will dynamically listen to window scroll and resize but also return the initial value on DOM ready trough the first callback function argument px.
Here is a quick and dirty concept. It basically compares the offset().top of the element to the top of the window, and the offset().top + height() to the bottom of the window:
function getVisible() {
var $el = $('#foo'),
scrollTop = $(this).scrollTop(),
scrollBot = scrollTop + $(this).height(),
elTop = $el.offset().top,
elBottom = elTop + $el.outerHeight(),
visibleTop = elTop < scrollTop ? scrollTop : elTop,
visibleBottom = elBottom > scrollBot ? scrollBot : elBottom;
$('#notification').text(`Visible height of div: ${visibleBottom - visibleTop}px`);
}
$(window).on('scroll resize', getVisible).trigger('scroll');
html,
body {
margin: 100px 0;
}
#foo {
height: 1000px;
background-color: #C00;
width: 200px;
margin: 0 auto;
}
#notification {
position: fixed;
top: 0;
left: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<div id="foo"></div>
<div id="notification"></div>
The logic can be made more succinct if necessary, I've just declared separate variables for this example to make the calculation as clear as I can.
Here is a version of Rory's approach above, except written to function as a jQuery plugin. It may have more general applicability in that format. Great answer, Rory - thanks!
$.fn.visibleHeight = function() {
var elBottom, elTop, scrollBot, scrollTop, visibleBottom, visibleTop;
scrollTop = $(window).scrollTop();
scrollBot = scrollTop + $(window).height();
elTop = this.offset().top;
elBottom = elTop + this.outerHeight();
visibleTop = elTop < scrollTop ? scrollTop : elTop;
visibleBottom = elBottom > scrollBot ? scrollBot : elBottom;
return visibleBottom - visibleTop
}
Can be called with the following:
$("#myDiv").visibleHeight();
jsFiddle
Here is the improved code for jquery function visibleHeight: $("#myDiv").visibleHeight();
$.fn.visibleHeight = function() {
var elBottom, elTop, scrollBot, scrollTop, visibleBottom, visibleTop, height;
scrollTop = $(window).scrollTop();
scrollBot = scrollTop + $(window).height();
elTop = this.offset().top;
elBottom = elTop + this.outerHeight();
visibleTop = elTop < scrollTop ? scrollTop : elTop;
visibleBottom = elBottom > scrollBot ? scrollBot : elBottom;
height = visibleBottom - visibleTop;
return height > 0 ? height : 0;
}
EDIT:
I figured out the cause of the issue. When you scroll with the scrollbar, and then programmatically set the scroll, the scrollbar moves, which causes the browser to recalculate the scroll position based on the position of the mouse relative to the new scrollbar position. Now I'm looking for a solution. Here is a Youtube video of the problem using code from one of the answers below.
ORIGINAL POST:
Im trying to achieve an effect where the window scrolls in increments equal to the size of elements in a grid, so that the scrolling looks smooth. The elements are 40px square, with 4px of padding, so I'm trying to scroll the page in increments of 44px. I've written some code, and it works great when the page scrolls vertically. I've got the exact same operation (or so I think) set up for horizontal scrolling, but when I scroll horizontally, the page jitters back and forth. I've tried debugging it, and it seems like the window scroll left gets set to some seemingly random value every second or third time the scroll handler is invoked.
Here is the code (and a fiddle):
$(function(){
// Here is the scroll function in question
$(window).scroll(function(){
var lastScrollTop = $(window).scrollTop();
var lastScrollLeft = $(window).scrollLeft();
return function(){
var scrollTop = $(this).scrollTop();
var scrollLeft = $(this).scrollLeft();
var yDir = scrollTop - lastScrollTop > 0 ? 1 : -1;
var xDir = scrollLeft - lastScrollLeft > 0 ? 1 : -1;
$(this).scrollTop(~yDir ? scrollTop + ((44 - ((scrollTop + 44) % 44))) : scrollTop - (scrollTop % 44));
$(this).scrollLeft(~xDir ? scrollLeft + ((44 - ((scrollLeft + 44) % 44))) : scrollLeft - (scrollLeft % 44));
scrollTop = lastScrollTop = $(this).scrollTop();
scrollLeft = lastScrollLeft = $(this).scrollLeft();
}
}());
});
EDIT:
I'm testing this in Chrome 26.0.1410.65 btw.
EDIT:
The problem occurs with vertical scrolling too, but only when using the scrollbar, not the mousewheel.
There are cases where scrollTop - lastScrollTop and scrollLeft - lastScrollLeft are zero and is causing problems in your logic.
See these two lines
var yDir = scrollTop - lastScrollTop > 0 ? 1 : -1;
var xDir = scrollLeft - lastScrollLeft > 0 ? 1 : -1;
Say the difference is > 0, your xDir variable will have 1 and you set scrollLeft to scrollTop + ((44 - ((scrollTop + 44) % 44)).
Next iteration difference is 0, your xDir variable will have -1 and you will now use scrollLeft - (scrollLeft % 44)). These two equations do not evaluate to the same even though the scroll position didn't changed.
Try this.
$(function(){
// Here is the scroll function in question
$(window).scroll(function(){
var lastScrollLeft = $(window).scrollLeft();
var lastScrollTop = $(window).scrollTop();
return function(){
var scrollLeft = $(this).scrollLeft();
var scrollTop = $(this).scrollTop();
if (lastScrollLeft !== scrollLeft) {
lastScrollLeft = scrollLeft - (scrollLeft % 44);
$(this).scrollLeft(lastScrollLeft);
}
if (lastScrollTop !== scrollTop) {
lastScrollTop = scrollTop - (scrollTop % 44);
$(this).scrollTop(lastScrollTop);
}
}
}());
// Generates the grid, ignore this part
for(var i=0;i<50;i++){
var row = $('<div></div>').addClass('row').appendTo('body');
for(var j=0; j<50; j++)
$('<span></span>').appendTo(row).addClass('cell').text(50*i+j);
}
});
So, based on the cause of the issue as stated at the top of the question, I don't think there is a direct solution to this issue using the method in the code. The solution I ended up using was to implement a sort of virtual scrolling. I put the element to be scrolled into a 'viewport' div with a fixed position filling the window area. Then I expanded the window size using a 'scrollTarget' div, and scrolled the viewport in increments of 44 based on the scroll position of the window. See this fiddle.
Javascript
$(function(){
// Here is the scroll function in question
$(this).scroll(function(){
var scrollTop = $(this).scrollTop();
var scrollLeft = $(this).scrollLeft();
$('#viewport').scrollTop(scrollTop - (scrollTop % 44));
$('#viewport').scrollLeft(scrollLeft - (scrollLeft % 44));
});
// Generates the grid, ignore this part
for(var i=0;i<50;i++){
var row = $('<div></div>').addClass('row').appendTo('#viewport');
for(var j=0; j<50; j++)
$('<span></span>').appendTo(row).addClass('cell').text(50*i+j);
}
$('#scrollTarget').height((50 * 44) + 48)
.width((50 * 44) + 48);
});
HTML
<div id="viewport"></div>
<div id="scrollTarget"></div>
CSS
#viewport{
position:fixed;
height:100%;
width:100%;
overflow:hidden;
}
#scrollTarget{
position:absolute;
}
.row{
height:44px;
width:2204px;
}
.row:first-child{
height:40px;
}
.cell{
display:inline-block;
height:40px;
width:40px;
margin-top:4px;
margin-right:4px;
background-color:#859DE1;
color:#0F1219;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
.row:first-child .cell{
margin-top:0px;
}
I have created a parallax scroll, which seem to be working fine in firefox however in the chrome browser there's a slight jump on the body text when scrolling. click here scroll to the about section. I am not sure if t this is a css or JS issue.. below is a snippet i have incorporated into my parallax function
Does anyone know how i an fix this issue?
$(document).ready(function(){
// Cache the Window object
$window = $(window);
// Cache the Y offset and the speed of each sprite
$('[data-type]').each(function() {
$(this).data('offsetY', parseInt($(this).attr('data-offsetY')));
$(this).data('Xposition', $(this).attr('data-Xposition'));
$(this).data('speed', $(this).attr('data-speed'));
});
// For each element that has a data-type attribute
$('[data-type="background"]').each(function(){
// Store some variables based on where we are
var $self = $(this),
offsetCoords = $self.offset(),
topOffset = offsetCoords.top;
// When the window is scrolled...
$(window).scroll(function() {
// If this section is in view
if ( ($window.scrollTop() + $window.height()) > (topOffset) &&
( (topOffset + $self.height()) > $window.scrollTop() ) ) {
// Scroll the background at var speed
// the yPos is a negative value because we're scrolling it UP!
var yPos = -($window.scrollTop() / $self.data('speed'));
// If this element has a Y offset then add it on
if ($self.data('offsetY')) {
yPos += $self.data('offsetY');
}
// Put together our final background position
var coords = '50% '+ yPos + 'px';
// Move the background
$self.css({ backgroundPosition: coords });
$('[data-type="scroll-text"]', $self).each(function() {
var $text= $(this);
var pos = ($window.scrollTop()/10) * $text.data('speed');
var curP = $text.css('margin-top');
var is_chrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
if(is_chrome) {
$text.animate({
paddingTop: pos,
}, 200, 'linear', function() {
// Animation complete.
});
} else {
$text.css('padding-top', pos);
}
});
}; // in view
}); // window scroll
}); // each data-type
}); // document ready
Some suggestions:
1.) Use position: fixed to avoid any jitter, as you'll be taking the element out of the document flow. You can then position it using z-index.
2.) Cache as much as you can to ease processing time.
3.) Math.round may not be necessary, but try adding this CSS to your moving areas: -webkit-transform: translate3d(0,0,0); This will force hardware acceleration in Chrome, which may ease some of the jittering. (It looked smoother on my screen when I added this with Inspector, but it didn't get rid of the jumpiness with the scroll wheel.) Note: Don't do this on your entire document (e.g. body tag), as it might cause some issues with your current layout. (Your navigation bar didn't stick to the top of the window, for instance.)
4.) If you have any animations running as part of your parallax logic (tweening the margin into place or something along those lines), remove it - that would probably cause the jump you see.
Hope this helps. Best of luck.
I see the same jittering in FireFox and Chrome (Mac). Looking at your containers, one thing that's glaring at me is the pixel position that's being calculated/used.
Chrome: <div id="about-title" style="margin-top: 1562.3999999999999px;">
FireFox: <div id="about-title" style="margin-top: 1562.4px;">
Browsers aren't going to allow content to sit at 1/2 pixel, let alone 0.3999999 of a pixel. I think it's moving it, and trying to calculate whether to round up or round down. It jitters because it's calculating with every click of your mouse wheel.
Thus, I'd try adding Math.round() to your positions so that the containers are never being left in limbo.
Take a look at the code here: http://webdesigntutsplus.s3.amazonaws.com/tuts/338_parallax/src/index.html
Firebug some of the elements, and you'll see that their only fraction of a pixel is '0.5'. Most of them (the bulk) go to round number values.
You are going to have to change the way that the scrolling works (i.e. change how the spacing is computed), but this can be fixed by adding the position:fixed CSS element to the page elements that are scrolling. The problem is coming from the time that it takes for the JavaScript to process and then render.
For example, on your page you would set each of the <div> tags containing text to have a fixed position and then use the JavaScript/JQuery function to update the top: CSS element. This should make the page scroll smoothly.
Have you tried adding the preventdefault inside the scroll function?
$(window).scroll(function(e) {
e.preventDefault();
// rest of your code
}
In a previous question I created a fairly good parallax scrolling implementation. Jquery Parallax Scrolling effect - Multi directional You might find it useful.
Here's the JSFiddle http://jsfiddle.net/9R4hZ/40/ use the up/down arrows or scroll wheel.
Using padding and margin for the positioning are probably why you're experiencing rendering issues. While my code uses scroll or keyboard input for the effect you can loop the relavent portion and check the $moving variable until you reach the desired element on screen.
function parallaxScroll(scroll) {
// current moving object
var ml = $moving.position().left;
var mt = $moving.position().top;
var mw = $moving.width();
var mh = $moving.height();
// calc velocity
var fromTop = false;
var fromBottom = false;
var fromLeft = false;
var fromRight = false;
var vLeft = 0;
var vTop = 0;
if($moving.hasClass('from-top')) {
vTop = scroll;
fromTop = true;
} else if($moving.hasClass('from-bottom')) {
vTop = -scroll;
fromBottom = true;
} else if($moving.hasClass('from-left')) {
vLeft = scroll;
fromLeft = true;
} else if($moving.hasClass('from-right')) {
vLeft = -scroll;
fromRight = true;
}
// calc new position
var newLeft = ml + vLeft;
var newTop = mt + vTop;
// check bounds
var finished = false;
if(fromTop && (newTop > t || newTop + mh < t)) {
finished = true;
newTop = (scroll > 0 ? t : t - mh);
} else if(fromBottom && (newTop < t || newTop > h)) {
finished = true;
newTop = (scroll > 0 ? t : t + h);
} else if(fromLeft && (newLeft > l || newLeft + mw < l)) {
finished = true;
newLeft = (scroll > 0 ? l : l - mw);
} else if(fromRight && (newLeft < l || newLeft > w)) {
finished = true;
newLeft = (scroll > 0 ? l : l + w);
}
// set new position
$moving.css('left', newLeft);
$moving.css('top', newTop);
// if finished change moving object
if(finished) {
// get the next moving
if(scroll > 0) {
$moving = $moving.next('.parallax');
if($moving.length == 0)
$moving = $view.find('.parallax:last');
} else {
$moving = $moving.prev('.parallax');
if($moving.length == 0)
$moving = $view.find('.parallax:first');
}
}
// for debug
$('#direction').text(scroll + " " + l + "/" + t + " " + ml + "/" + mt + " " + finished + " " + $moving.text());
}
May not be related to your specifics, but I had a jumpy parallax scrolling problem, I was able to solve it adding the following CSS for the fixed portions of the page:
#supports (background-attachment: fixed)
{
.fixed-background
{
background-attachment: fixed;
}
}
Not sure of all the specifics, but found at Alternate Fixed & Scroll Backgrounds