I've got a javascript slideshow at the top of my page. When a slide changes to the next image, I call another function to change the background colour of the page.
The client wants the background colour to stop changing when the slideshow is no longer in view, i.e. when the user has scrolled down the page.
Is there any way to detect if an element is no longer visible due to scrolling?
Test code in jQuery
function test() {
var $elem = $('.test');
var visibleAtTop = $elem.offset().top + $elem.height() >= $(window).scrollTop();
var visibleAtBottom = $elem.offset().top <= $(window).scrollTop() + $(window).height();
if (visibleAtTop && visibleAtBottom) {
alert('visible');
} else {
alert('invisible (at ' + (visibleAtTop ? 'bottom' : 'top') + ')');
}
}
Full working example at http://jsfiddle.net/9PaQc/1/ (Updated: http://jsfiddle.net/9PaQc/2/ )
P.S. This only checks for vertical scroll. For horizontal, just do the same with top replaced with left, Y -> X and height() -> width()
EDIT
Made it all the way jQuery (to ensure x-browser compatibility) by changing window.scrollY -> $(window).scrollTop()
You can use the jQuery $.scrollTop function, probably from a scroll event handler to script this.
Use the window.pageYOffset to determine scroll amount in window. Use current offset of the object to check if it is in view. Note that these values are mostly browser dependent, so first check if it exists then act on it.
Related
When I click my button, I append a new div. I would like that, if any part of the div is not visible to the window, scroll untill it shows.
So if it is upwards from the center of the window, scroll up JUST until you see it's top, and if it is downwards from the center, scroll down just until you see its bottom.
In my searchings I found https://api.jquery.com/scrollTop/ , but that doesn't seem to be what I'm describing
I only want to scroll enough to display it entirely on screen, not always on top
Check this working fiddle for the solution.
You can use scrollTop() to scroll to any div. First we need to find the direction of the scroll to correctly provide the value for scrollTop() function. This can be found using position().
$('#button4').click(function() {
// check if the div lies below the button
if ($('#div3').position().top - $('#button4').position().top > 0) {
// in this case we need to add shift
var shift = $(window).height() - $('#div3').height();
} else {
var shift = 0;
}
$(window).scrollTop($('#div3').offset().top - shift);
});
Alternatively if you wish to check the position of the div relative to the current viewport (or the center of the current window view) you can use getBoundingClientRect() like:
$('#button4').click(function() {
// check if the div lies below the viewport
if ($('#div3')[0].getBoundingClientRect().top > 0) {
// in this case we need to add shift
var shift = $(window).height() - $('#div3').height();
} else {
var shift = 0;
}
$(window).scrollTop($('#div3').offset().top - shift);
});
Here is the alternate fiddle.
So if you are using jQuery.
//scroll top of element
var $myNewElement = $(".new-element-class");
$('html, body').scrollTop($myNewElement.offset().top)
I think this should work. Make sure your div is appended before calling the scrollTop function.
If I understand correctly, this jsfiddle is what you are looking for.
I also added a simple animation so the document doesn't awkwardly jump around the viewport.
Features
If a new element is created, and part of that new element is below the current viewport, the document will be scrolled until the bottom of the viewport is shown. Vice versa if part of the new element is above the current viewport.
If the new element is completely visible within the current viewport, no scrolling will occur.
All scrolling effects are animated.
A configurable offset (Leave some padding instead of scrolling to the very edge of the new element)
How it works
When the button is clicked, a new div element is appended.
$('section').append('<div>');
Next, we figure out if this div is above the current viewport using the below function:
function isElementAboveViewport(element) {
return $(element).offset().top < $(window).scrollTop();
}
in an if statement:
if (isElementAboveViewport($('section > div:last-child'))) {
...
}
If the condition is truthy, we scroll! (offset is configurable - In the JS fiddle, I used 40 which is the height of the button covering the top 40px of the viewport)
$('html, body').animate({
scrollTop: $('section > div:last-child').offset().top - offset
}, 1000);
Now, we check if the new element is below the current viewport with a similar function:
function isElementBelowViewport(element) {
return $(element).offset().top + $(element).height() > $(window).scrollTop() + $(window).height();
}
If the element is too far down in the document, we scroll! (Again, offset is configurable)
$('html, body').animate({
scrollTop: $('section > div:last-child').offset().top + $('section > div:last-child').height() - $(window).height() + offset
}, 1000);
Please let me know if this is what you were looking for and/or if you have any questions about how it works.
On the top of my site I have a bar with similar blog posts.
**It should SlideUp (with jQuery) when the user have scrolled to the top again - so e.g. after he read the article.
How can I detect this situation and then show the bar in the head of my site?**
you can monitor scroll event of window element,and check its scrollTop() :
$(window).scroll(function () {
if ($(this).scrollTop() <= 0 ){
// your code
}
});
It's easy to check the scroll position with .scrollTop() in jquery.
So the idea is to bind an event on scroll to check the position periodically.
hasReadArticle = false
$window = $(window)
articleBottomPosition = ... # get the position of the bottom of the article
timeout = nil
$window.on "scroll", (e) ->
clearTimeout timeout if timeout
timeout = setTimeout ->
top = $window.scrollTop()
# you might want to improve this to detect when the bottom of your window arrives at the bottom of the article.
hasReadArticle = top > articleBottomPosition unless hasReadArticle
if top <= 0 && hasReadArticle
# $('your header').slideDown()
, 100
A simpler way to do it would be to use jquery-waypoints. You would set a handler to detect when you get to the bottom of your article, then set a variable to remember this. Then set another handler to detect when you get to the top of the page (with direction == "up").
What would be computationally-efficient ways to select elements touching the top edge of browser window viewport as the page is scrolled?
See attached image. Green elements are selected because they are touching the top edge.
UPDATE
An example of how I'll use this is to fade elements that are going off-screen. There may be hundreds of them on the page. Imagine a page like Pinterest. Computing offset and scrollTop for hundreds of them at the rate of scroll event, even if throttled still feels really inefficient.
This is what I came up with. I think that it could be improved upon by caching the scrollTop values, but this is pretty good. I have included the framework for caching the boxtops, but not the implementation code. I have also only implemented scrolling down to hide divs. I have left reshowing them on upscroll as an exercise for you.
When the window is scrolled we get the last hidden div. We know that everything before this div is already hidden. Then use a 'while next element is off the screen' hide it. As soon as a div isn't off the screen we abort. Thus saving time from iterating through the entire list.
http://jsfiddle.net/kkv3h/2/
//track whether user has scrolled up or down
var prevScrollTop = $(document).scrollTop();
$(document).scroll(function() {
var currentScrollTop = $(this).scrollTop();
if (currentScrollTop > prevScrollTop) {
//down
var lasthiddenbox = $('.fadeboxhidden:last');
var nextbox = (lasthiddenbox.length > 0) ? lasthiddenbox.next('.fadebox') : $('.fadebox:first');
while (nextbox.length) {
console.log('box: ' + nextbox.offset().top + ' scroll: ' + currentScrollTop);
if (nextbox.offset().top < currentScrollTop) {
nextbox.animate({ opacity: 0 }, 3000).addClass('fadeboxhidden');
}
else { return; }
nextbox = nextbox.next('.fadebox:first');
}
} else {
//up
}
prevScrollTop = currentScrollTop ;
});
//create an object to hold a list of box top locations
var boxtops = new Object;
//gather all boxes and store their top location
$('.fadebox').each( function(index) {
//you may want to dynamically generate div ids here based on index. I didn't do this
//because i was already using the id for positioning.
var divid = $(this).prop('id');
boxtops[divid] = $(this).offset().top;
//console.log(boxtops[divid]);
});
I'm thinking the best way would be that you could mark elements you want to determine hit testing with by some class, say "hit-test-visible" or something. Then, for those elements, on the scroll event, you should be able to find their offset compared to the document - see jQuery offset, and then based on the scroll value, if the offset is less than the scroll, and the offset + element height is greater than the scroll offset, then the element should be "touching" the top edge.
in javascript can I make sure that my large div scroll vertically
only in chunks of (let's say) 16 pixels
In java, those are called 'units of increment'.
I can't find anything similar in javascript:
I want to ensure that a certain area (div) when partially scrolled is always a multiple of 16 the view.
That allows me to do tricks with background images and others.
thanks
var lastScroll = 0;
$('div').scroll(function(){
var el = $(this),
scroll = el.scrollTop(),
round = lastScroll < scroll ? Math.ceil : Math.floor;
lastScroll = round(scroll/16) * 16;
el.scrollTop(lastScroll);
});
http://jsfiddle.net/m9DQR/2/
Ensures scrolls are done in multiples of 16 pixels. You can easily extend this to be a plugin that allows for a variable amount (not a fixed, magical 16).
Yes, this is possible, but it will require using javascript to capture the scroll event and then manipulate it. This script (sorry jQuery is what I had) and overrides the scroll event. It then replaces it with the exact same scroll distance. You could perform your own math to adjust the value of scrollTo. We have to check both mousewheel and DOMMouseScroll events because the first is not supported by FF. This doesn't seem to apply in your case, but a user may have the number of lines to scroll set to something other than the default three. So the if statement calculates the distance. I left it in there though in case other people stumble on this question and it is important to them though.
$('body').bind('mousewheel DOMMouseScroll', function(e) {
var scrollTo = null;
if (e.type == 'mousewheel') {
scrollTo = (e.wheelDelta * -1);
}
else if (e.type == 'DOMMouseScroll') {
scrollTo = 40 * e.detail;
}
//adjust value of scrollTo here if you like.
scrollTo = 16;
if (scrollTo) {
e.preventDefault();
$(this).scrollTop(scrollTo + $(this).scrollTop());
}
});
Coming from another programming language I also found JavaScript difficult when dealing with UI. In your case I would just set a handler to the event onscroll and query the position of the div relative to the scroll position. Return false whenever position of div is not divisible by 16px and create a counter to allow reposition after another 16px is scrolled.
I have 2 divs and one is nested in the other. I want to get the child div position relative to browser window. The use case is this: when user scroll down browser, I want to detect the position of the child div and if it is 100px above the bottom of the browser window, I want to fade it out slowly.
How do I do that with jQuery? The 2 divs have relative position or absolution position but not fixed position.
Try this:
$(window).scroll(function () {
var distanceFromBottom = 100;
if ( ( $("#outerdiv").offset().top + $("#innerdiv").height() - $(window).scrollTop() ) > $(window).height() - distanceFromBottom ) {
$("#innerdiv").fadeOut("slow");
} else {
$("#innerdiv").fadeIn("slow");
}
})
You didn't state if you wanted the #innerdiv to fade back in if greater than 100 pixels from the bottom, but I wrote this assuming that you did... In this case, you would need to detect the offset of the #outerdiv if you want the #innerdiv to fade back in as an invisible element has no position.
If you don't want the #innerdiv to fade back in then change the if statement to look at the #innerdiv element and remove the else portion of the function.
Edit: Looking at your example page, I'm guessing you wanted this effect to work on the music player. Since, it's probably not the best idea to fade or slowly hide an embedded object using jQuery - it just doesn't animate well - so, I just did it abruptly. The above script will still work, but as you can see in the revision below, you don't have to use 2 Divs, I used the div and the embedded object within it. The outer div should closely wrap the inner div for this script to work, so you can't use the div with id "container-msg" in this case.
$(window).scroll(function () {
var distanceFromBottom = 100;
if ( ( $(".windowMediaPlayer").offset().top + $(".windowMediaPlayer object").height() - $(window).scrollTop() ) > $(window).height() - distanceFromBottom ) {
$(".windowMediaPlayer object").hide();
} else {
$(".windowMediaPlayer object").show();
}
})
I modified your example and saved it to this pastebin so you can see it working.
Edit: Oops, you said you wanted it to disappear when it got closer to the bottom... I just changed the "<" to ">" and now it should do what you want. I updated the pastebin code too.
var inner_offset = $("#innerdiv").offset();
var window_size = $(window).height();
if( ( inner_offset.top + $("#innerdiv").height() ) > window_size - 100 )
$("#innerdiv").fadeOut("slow");
Not vetted, but should give you the general idea.
offset at the
jQuery documentation