Get the visible height of a div with jQuery - javascript

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;
}

Related

Jquery transition Opacity based on var value

I am trying to animate an opacity value from 0 to 1, based on the scroll position within the viewport height. The code below sets variables for windowHeight and scrollTop, which can be combined to calculate percentageScrolled (0–100) of the viewport height. Based on this I am able to switch CSS values at set points, but instead I want to gradually change the opacity from 0–100 of percentageScrolled.
How can I adjust the code below to transition/animate the opacity?
Thanks.
$(window).on('scroll', function(){
// Vars
var windowHeight = $(window).height();
var scrollTop = $(this).scrollTop();
var percentageScrolled = (scrollTop*100)/windowHeight;
if( percentageScrolled < 100 ) {
$('.colour-overlay').css('opacity', '1');
} else {
$('.colour-overlay').css('opacity', '0');
}
});
You can remove the if and set the opacity to the percentage divided by 100
$(window).on('scroll', function() {
// Vars
var windowHeight = $(window).height();
var scrollTop = $(this).scrollTop();
$('.colour-overlay').css('opacity', scrollTop / windowHeight);
});
.colour-overlay {
display: block;
width: 20px;
height: 1200px;
background-color: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="colour-overlay"></div>
$(‘.colour-overlay’).css(opacity, percentageScrolled / 100);
Instead of if else statement.
Also as a general advice try to avoid using var, use const or let instead and if your project doesnt depend on jquery try to avoid it too.
const overlays = document.querySelectorAll(‘.colour-overlay’);
window.addEventListener('scroll', () => {
const windowHeight = window.offsetHeight;
const scrollTop = window.scrollTop;
const percentageScrolled = (scrollTop * 100) / windowHeight;
for (const overlay of overlays) {
overlay.style.opacity = percentageScrolled / 100;
}
});
This would be the pure js solution.
Dont know if i understood you right, but a wrote an example have a look.
$(document).on('scroll', function(){
// Vars
// use body instead of window, body will return the right height where window will return the view size
var windowHeight = $("body").height();
var scrollTop = $(this).scrollTop();
var percentageScrolled = Math.abs((((scrollTop / windowHeight) * 100) / 100 ));
$('.colour-overlay').css('opacity', percentageScrolled);
});
.colour-overlay{
background:red;
height:1000px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="colour-overlay"></div>

Get actual height of document with jQuery

I have a problem with my jQuery plugin, I want to display footer in specific way right before end of page. For this I get actual bottom position and compare it with height of entire body.
But there is a problem jQuery function height() return bad value. For example I know that site has height of 5515px but function returns to me a value: 8142px
I use the plugin mCustomScrollbar in few places on page, and I think that it may cause the problem. I can't disable it, because page has elements which require it to look good.
My code:
(function($){
$.fn.scrollingElements = function(){
var height = $(window).height();
var max_height = $(document).height();
var prev_top = $(window).scrollTop();
var prev_bottom = top + height;
$(window).scroll(function(){
var top = $(window).scrollTop();
var bottom = top + height;
console.log(max_height, bottom);
if(prev_top < height && top > height){
// console.log('show header', 'show sticky', 'show footer small');
}
if(prev_top > height && top < height){
// console.log('hide header', 'hide sticky', 'hide footer');
}
if(prev_top < 2*height && top > 2*height){
// console.log('show sroll top');
}
if(prev_top > 2*height && top < 2*height){
// console.log('hide scroll top');
}
if(prev_bottom < max_height - 100 && bottom > max_height - 100){
// console.log('show footer large');
}
if(prev_bottom > max_height - 100 && bottom < max_height - 100){
// console.log('show footer small');
}
prev_top = top;
prev_bottom = bottom;
});
$(window).resize(function(){
height = $(window).height();
});
}
})(jQuery);
Answering to Tommy Riordan:
changing $(document).height() to document.body.clientHeight didn't work still getting bad results. Tried using $('body').height() with same effect.
Please use
document
instead of window
for example :
$(window).resize(function(){
height = $(document).height();
});

Fixed sidebar on the scroll stop at div

I try to make sure that a div "filter" becomes fixed when scrolling and then stop when it comes down to "outside_footer_wrapper". use the following script but can not get it to work?
jsfiddle
$(function() {
var top = $('#filter').offset().top - parseFloat($('#filter').css('marginTop').replace(/auto/, 0));
var footTop = $('#outside_footer_wrapper').offset().top - parseFloat($('#outside_footer_wrapper').css('marginTop').replace(/auto/, 0));
var maxY = footTop - $('#filter').outerHeight();
$(window).scroll(function(evt) {
var y = $(this).scrollTop();
if (y > top) {
if (y < maxY) {
$('#filter').addClass('fixed').removeAttr('style');
} else {
$('#filter').removeClass('fixed').css({
position: 'absolute',
top: (maxY - top) + 'px'
});
}
} else {
$('#filter').removeClass('fixed');
}
});
});
If you want to stop the position:fixed after you reach the footer you can do something like this faking with the top:
$(function() {
var top = $('#filter').offset().top,
footTop = $('#outside_footer_wrapper').offset().top,
maxY = footTop - $('#filter').outerHeight();
$(window).scroll(function(evt) {
var y = $(this).scrollTop();
if (y > top) {
$('#filter').addClass('fixed').removeAttr('style');
if (y > maxY-20){
var min = y - maxY + 20;
$('#filter').css('top','-'+min+'px');
}
} else {
$('#filter').removeClass('fixed');
}
});
});
Also take in care with the CSS for the class fixed you need to make that with equal specificity of #filter I made this change:
#sidebar #filter.fixed /*Add #sidebar*/
Check This Demo Fiddle
if you know at which pixel number the filter have to be fixed and at which pixel number the footer starts you can try this function:
scrollTop
Is it something like this?
jsfiddle
// get box div position
var box = document.getElementById('box').offsetTop;
window.onscroll = function(){
// get current scroll position
var scroll_top = document.body.scrollTop || document.documentElement.scrollTop;
document.getElementById('scbox').innerText = scroll_top + ' ' + box;
// if current scroll position >= box div position then box position fixed
if(scroll_top >= box)
document.getElementById('box').style.position = 'fixed';
else
document.getElementById('box').style.position = 'relative';
}
try this:
#sidebar {
position: fixed;
}
jsfiddle here

calculate centered absolute div inside relative

I'm using 2 Javscript methods to position a hovering button inside a static element on my page. The button that is centered is inputted inside the first element and uses position absolute. The code I'm using to get the parent element measurements:
// calculate if the element is in the visible window
function elementVisibleRect(element) {
element = $(element);
var rect = {
top: Math.round(element.offset().top),
left: Math.round(element.offset().left),
width: Math.round(element.outerWidth()),
height: Math.round(element.outerHeight())
};
var scrollTop = $(window).scrollTop();
var windowHeight = $(window).height();
var scrollBottom = scrollTop + windowHeight;
var elementBottom = Math.round(rect.height + rect.top);
if (scrollTop < rect.top && scrollBottom > elementBottom) {
return rect;
}
if (scrollTop > rect.top) {
rect.top = scrollTop;
}
if (scrollBottom < elementBottom) {
rect.height = scrollBottom - rect.top;
} else {
rect.height = windowHeight - (scrollBottom - elementBottom);
}
return rect;
}
and for using this information and centering the button inside
// center the element based on visible screen-frame
function elementPosition (element) {
var visibleRect = elementVisibleRect(element);
$('.editHoverButton').css({
top: visibleRect.top + ((visibleRect.height / 2) - ($('.editHoverButton').outerHeight() / 2)),
left: visibleRect.left + (visibleRect.width / 2) - ($('.editHoverButton').outerWidth() / 2)
});
}
Now my problem is that a third party library requires the parent DIV to change position from the browser default "static" to "relative" which breaks my calculations in the second function.
It might be late, but no matter what I try I can't seem to figure out how to get this working for when the parent element has position set to relative. I can't seem to get the maths quite right, and my head is beginning to hurt. Any suggestions?
EDIT - ADDED JSFIDDLE
http://jsfiddle.net/RhTY6/
Elements with absolute positioning are removed from the natural flow (e.g. they don't leave a space where they were) and positioned relative to their first parent with non-static positioning. Since the positioning of the right-hand box is relative (not static), you can position the button with top: 50%; and left: 50%;. This will make the top-left corner at the center of the parent. Then all you have to do is subtract half the element's height and width from the position, using margin-top and margin-left. This is much simpler than what you were doing, as you can see below:
JavaScript:
function elementPosition() {
$('.editHoverButton').css('margin-top', 0 - $('.editHoverButton').outerHeight() / 2);
$('.editHoverButton').css('margin-left', 0 - $('.editHoverButton').outerWidth() / 2);
};
CSS:
.editHoverButton {
position: absolute;
z-index: 99;
font-family: sans-serif;
font-size: 14px;
font-weight: normal;
background-color: #00bb00;
top: 50%;
left: 50%;
}
Nothing else has to change except to remove this from the elementPosition() function.
DEMO (Notice that the left one no longer works. This is because it is positioned static.)
EDIT--Using the same basic idea, this method should work:
The problem is that you have take the top and left positions of the element when defining rect. on the positioning calculations. Changing those to 0 (not the best method, but it works) fixes the problem for relative elements.
DEMO (Notice that the left one now does work. This is because it is positioned at 0,0 anyway.)
EDIT--This will work when the page scrolls:
This sets the container in a variable so that when the page scrolls, it can be repositioned automatically.
DEMO
EDIT: made it worked with your CSS and HTML (relative and absolute positioning) by altering the Script only.
The horizontal axis calcs were completely missing (I've applied the same calcs you applied to the vertical axis).
I've added some data and a ruler to help you finish the job: as you can see, it is (and it was, in your original fiddle) not perfectly centered (obviously you need to look at it when the container is smaller than the viewport), but this will be easy to work out.
Running Demo
Try to resize the fiddle window and to scroll both vertically and horizontally to see it works.
function elementVisibleRect(element) {
$("#data").html("");
element = $(element);
var rect = {
top: Math.round(element.offset().top),
left: Math.round(element.offset().left),
width: Math.round(element.outerWidth()),
height: Math.round(element.outerHeight())
};
var scrollTop = $(window).scrollTop();
var windowHeight = $(window).height();
var scrollBottom = scrollTop + windowHeight;
var elementBottom = Math.round(rect.height + rect.top);
var scrollLeft = $(window).scrollLeft();
var windowWidth = $(window).width();
var scrollRight = scrollLeft + windowWidth;
var elementRight = Math.round(rect.width + rect.left);
addData("rect.top", rect.top);
addData("rect.left", rect.left);
addData("rect.width", rect.width);
addData("rect.height", rect.height);
addData("scrollTop", scrollTop);
addData("windowHeight", windowHeight);
addData("scrollBottom", scrollBottom);
addData("elementBottom", elementBottom);
addData("scrollLeft", scrollLeft);
addData("windowWidth", windowWidth);
addData("scrollRight", scrollRight);
addData("elementRight", elementRight);
if (rect.top < scrollTop) {
rect.top = scrollTop;
}
if (scrollBottom < rect.top < scrollTop) {
rect.top = scrollTop;
}
if (scrollBottom < elementBottom) {
rect.height = scrollBottom - rect.top;
} else {
rect.height = windowHeight - (scrollBottom - elementBottom);
}
if (rect.left < scrollLeft) {
rect.left = scrollLeft;
}
if (scrollRight < rect.left < scrollLeft) {
rect.left = scrollLeft;
}
if (scrollRight < elementRight) {
rect.width = scrollRight - rect.left;
} else {
rect.width = windowWidth - (scrollRight - elementRight);
}
return rect;
}

jquery scroll div only if parent is taller

How can I calculate the height of the parent so that the left div is only animated if there is empty space below itself. I have tried to calculate the parents height is greater then the question height + the margin-top.
This is a simplified version of the code: http://jsfiddle.net/rewsn/6/ as a example. Although in the real version the questions/answer are changed via AJAX so this is the reason I need to calculate the height each time.
If I understood correctly, then a simple condition like below should solve your problem.
if ((y + el.outerHeight()) < $container.height()) {
DEMO
var el = $('.answer');
var elpos = el.offset().top;
var $container = $('.container');
$(window).scroll(function() {
var y = $(this).scrollTop();
var mTop = y - elpos;
if ((y + el.outerHeight()) < $container.height()) {
if (y < elpos) {
el.stop().animate({
'margin-top': 0
}, 500);
}
else {
el.stop().animate({
'margin-top': y - elpos
}, 500);
}
}
});
not 100% sure what you mean - you want to stop "answers" being pushed lower than "questions"? something like this?
http://jsfiddle.net/X3cbB/1/

Categories