Efficient way to select elements touching top edge of viewport - javascript

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.

Related

What's the most accurate way of measuring scroll?

I'm making a slide scrolling page, and I'm trying to have it scroll like you're pulling a notecard up and with the next one right behind it.
To do this, I'm making them all fixed, and then moving their "top" position based off of scroll. But then I also need to make the body the size of the panel.
It's hard to describe what I'm doing, so here's the demo: https://codepen.io/NotDan/pen/vzraJE
Here's the particular piece of code that's causing my problem:
//what's going on here?
$(window).scroll(function(){
var panelNum = parseInt($(window).scrollTop()/$(window).height());//detemines panel number
var pixelMovement = ($(window).scrollTop())-(panelNum*$(".panel").height()); determines how many pixels to move the panel by
$('body').find(".panel:eq("+panelNum+")").css("top", -1*pixelMovement);
});
The problem is when the user scrolls quickly, the top position is not set accurately and there's some overhang. Again, hard to explain, but if you jump to the demo and scroll quickly you'll see what I mean.
Is there a more precise way of measuring scroll? Or is there a better way to do what I'm trying to? I've tried scrollmagic, and its "section wipe" feature is really close, but they bring the previous one up rather than move the current one up.
I tried making a condition to determine the panel number and everything started working.
var panelNum = 0;
var pixelMovement = 0;
$(window).scroll(function () {
pixelMovement = $(window).scrollTop() - panelNum * $(".panel").height(); // determines how many pixels to move the panel by
$("body")
.find(".panel:eq(" + panelNum + ")")
.css("top", -1 * pixelMovement);
if (Math.abs(pixelMovement) >= $(window).height()) {
panelNum++;
} else if (pixelMovement <= 0) {
panelNum--;
}
});
Here's the working demo: https://codepen.io/NotDan/pen/RYJeZq

Stop element from scrolling at the footer

So I'm trying to get this element to scroll which it does but I'd like it to stop scrolling before the footer.
At the moment I have this but the pages don't have the same length so the >= 17900 is not a good solution for me.
$(window).scroll(function (event) {
var windowTop = $(this).scrollTop();
if (windowTop >= 17900) {
$(".product-form__item--quantity").addClass("non-fixed");
$(".product-form__item--submit").addClass("non-fixed");
$("#ProductPhotoImg").addClass("non-fixed");
$("#option_total").addClass("non-fixed");
$(".product-single__title").addClass("non-fixed");
$(".product-form__item--quantity").removeClass("change");
$(".product-form__item--submit").removeClass("change");
$("#ProductPhotoImg").removeClass("change");
$("#option_total").removeClass("change-option");
$(".product-single__title").removeClass("change");
} else {
//console.log('a');
$(".product-form__item--quantity").removeClass("non-fixed");
$(".product-form__item--submit").removeClass("non-fixed");
$("#ProductPhotoImg").removeClass("non-fixed");
$("#option_total").removeClass("non-fixed");
$(".product-single__title").removeClass("non-fixed");
}
});
Thanks for the help
You have more issues than only finding the footer's position here...
First is to find the position of the footer instead of hardcoding a value.
Okay...
Second is that you constantly add and remove classes on scroll.
This sure isn't the desired effect.
The scroll event fires like a dozen times or more on a single mouse wheel spin.
Third is that you force jQuery to lookup for elements, as #Taplar mentionned in comments, each times the script executes (Which is real bad if the script execute constantly!!). This is bad... And unuseful, since this those elements don't change.
So I modified your script... Almost completely :
;)
// Define an element collection ONCE.
var elementsList = $(".product-form__item--quantity, .product-form__item--submit, #ProductPhotoImg, #option_total, .product-single__title");
// Find the footer's position.
var footerPosition = $("#footer").offset().top;
// Set a flag to prevent the the script action when already done.
var footerVisible = false;
$(window).scroll(function (event) {
// How many pixels scrolled + viewport height = position of the last pixel at the bottom of the viewport relative to the document's top.
var viewportBottom = $(this).scrollTop() + $( window ).height();
if (viewportBottom >= footerPosition) {
if(!footerVisible){
// Will update classes on the element in the elementslist collection on user scroll enought to show the footer in viewport.
elementsList.addClass("non-fixed").removeClass("change change-option");
// Set a flag
footerVisible = true;
}
} else {
if(footerVisible){
// Will update classes on the element in the elementslist collection on user scroll from a "visible footer" to a footer below the viewport.
// In other words, You don't want to do it CONSTANTLY except when the footer is visible and dissapears due to user scroll up.
elementsList.removeClass("non-fixed");
// reset the flag.
footerVisible = false;
}
}
});

Jquery change div based on another div's position

Let me start of by saying, I'm just now learning JS and Jquery, so my knowledge is very limited.
I've been looking around for 2 days now, and tried all sorts of combinations. But I just can't get this to work.
Below is an example of the layout
I'm looking for a way to trigger an event when div 1 is X px from the top of the screen. Or when div 1 collides with div 2.
What I'm trying to accomplish is to change the css of div 2 (the fixed menu) when div 1 is (in this case) 100px from the top of screen (browser window). Alternatively, when div1 passes div2 (I'm using responsive design, so the fixed height from top might become a problem on smaller screens right? Seeing as the header for example won't be there on a hand held.). So maybe collision detection is better here? Would really appreciate some thoughts and input on this matter.
Another issue is, div2 has to revert back to is previous css once div1 passes it (going back (beyond the 100px)).
This is what I have but it has no effect
$(document).ready(function() {
var content = $('#div1');
var top = $('#div2');
$(window).on('scroll', function() {
if(content.offset().top <= 100) {
top.css({'opacity': 0.8});
}else{
top.css({'opacity': 1});
}
});
});
I am not sure of the reason but $("#content").offset().top was giving a constant value on console. So I added window.scrollTOp() to check its distance from top, here is how it works,
$(document).ready(function() {
var top = $("#menu");
$(window).on('scroll', function(){
if(($('#content').offset().top - $(window).scrollTop()) <= 100){
top.css({'opacity': 0.4});
}else{
top.css({'opacity': 1});
}
});
});
And DEMO JSFIDDLE....

How to determine if HTML Control is being displayed in page?

I'd like to know how could I get only the displayed controls in the screen in the very moment.
For example:
If I have a scrollbar which precludes the user from seeing everything
below the page, I'd like to make a selector which selects only what the user can see in his screen now. It would also be nice If I could select everything he does not see.
Is that possible? How?
Thanks
You could calculate the offsets (say, as the user scrolls) of what the user can see:
var top = $(window).scrollTop();
var bottom = top + $(window).height();
Then, you can see if an element is within this range.
$('*').each( function() {
var el = $(this);
var offsetTop = el.offset().top;
var inView = offsetTop >= top && offsetTop <= bottom;
el.addClass( inView ? 'in-view' : 'out-of-view' );
} );
Obviously there are some downsides performance wise to doing this. Depending what you want to do with this information you could select only inputs or whatever which might help.
I don't know if there is an easy or elegant solution to this. What you could do is calculate the offset position of all elements and the scroll offset to find out which elements are visible or not. This could become expensive if you have a lot of elements to check, but could work quite nicely otherwise.

Scrolling down to the next element with javascript

I'm implementing keyboard controls on my website that allow the user to move down to the next div (that has the class "post"), as well as up to the previous div, with the keyboard arrow keys.
I can bind a function to the keypress just fine, I just need some help with the javascript (i'm using jquery) that determines which div is the next / previous.
It's important that if the user scrolls halfway down the screen with the standard scrollbar, the next item is whatever is next, relative to the top of their viewing window, so I know I need to calculate the next/previous divs using the users current scroll position.. but I'm a bit lost on how to do that.
Firstly, you're going to have to test for the current scrollbar position with $(document).scrollTop().
Once you have that, you can iterate over your elements using $(".post").each(), comparing their top offset to the scrollbar position. If it's lower than the scrollbar, that is your next element, so break out of the loop with return false.
Full example:
var scrollPosition = $(document).scrollTop(),
nextPost = 0,
currentPosition = 0;
$(".post").each(function() {
currentPosition = $(this).offset().top;
if (currentPosition > scrollPosition) {
nextPost = currentPosition;
return false; // break the loop
}
});
$(document).scrollTop(nextPost); // Scrolls the page to the post

Categories