Lets say we have a long scrollable page, and a separate side block, which is shorter than page, but longer than screen height.
I'm trying to make the sidebar always inside the screen by one of it's ends.
When scrolling down, sidebar scrolled until fixes with it's bottom to the bottom of the screen:
Then if you scroll upwards, it will scroll until fixed with top:
So sidebar will never leave the screen.
It is simmilar to bootstrap affix, but with scroll ability.
Seems to be a common feature, saw few sites with this behavior, suprised I couldn't find ready implementation.
I just can't make it right, always find new bugs in unusual situations.
Maybe there is ready solution in some library?
Today I realised I don't need "fixed" positon and can update offset on each scroll.
Here is my code if anyone will search for the same question.
html:
<div id="screen">
<div id="page">
</div>
<div id="sidebar">
</div>
</div>
js:
var margin = 0;
var sidebarIsFixed = false;
var lastScroll = 0;
var topOffset = 0;
var sidebar = document.getElementById('sidebar');
var screen = document.getElementById('screen');
var yPos = undefined;
function OnBodyScroll() {
if (yPos == undefined) {
yPos = 0;
var parent = sidebar.offsetParent;
while (parent && parent != screen) {
yPos += parent.offsetTop;
parent = parent.offsetParent;
}
margin = sidebar.offsetTop;
topOffset = margin;
}
var windowHeight = screen.clientHeight;
var barHeight = sidebar.clientHeight;
var scroll = screen.scrollTop;
if (scroll >= lastScroll) {
if (scroll + windowHeight > yPos + topOffset + barHeight + margin) {
topOffset = scroll -yPos + windowHeight - barHeight - margin;
sidebar.style.marginTop = topOffset + "px";
}
} else {
if (scroll < yPos + topOffset - margin) {
topOffset = scroll - yPos + margin;
if (topOffset < margin) {
topOffset = margin;
}
sidebar.style.marginTop = topOffset + "px";
}
}
lastScroll = scroll;
}
screen.onscroll = OnBodyScroll;
OnBodyScroll();
http://jsfiddle.net/ilyad/da543f40/3/
So, I'm trying to reproduce the main scrolling effect on this beautiful portfolio: http://melaniedaveid.com/
I've followed this tutorial on Codyhouse: http://codyhouse.co/gem/fixed-background-effect/
And came up with the following Javascript function.
$(window).scroll(function(){
var scrollTop = $(window).scrollTop(),
windowHeight = $(window).height(),
contentright1 = document.getElementById('contentright1');
function checkScroll(id) {
var offset = scrollTop - $(id).offset().top;
if (offset >= 0 && offset < windowHeight) {
$(id).addClass('fixed_content');
if ((scrollTop/2) <= windowHeight) {
$(id).removeClass('fixed_content');
}
}
else {
$(id).removeClass('fixed_content');
}
};
checkScroll(contentright1);
The fidex_content class apply the following CSS:
.fixed_content {
position: fixed;
top:0; }
As you can see, my main problem is that I can't manage to remove this class when I go back to the position of the element in the first place.
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;
}
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
I made the navbar on the top of my page static (the rest of the page is dynamic)
The navbar is in a div that is given the ID "header" and and everything else is in a div with the ID "main".
I use this code to make tooltips.
This is the Javascript/jquery/qtip
$(document).ready(function() {
//Tooltips
$(".tiptrigger").hover(function(){
tip = $(this).find('.tip');
tip.show(); //Show tooltip
}, function() {
tip.hide(); //Hide tooltip
}).mousemove(function(e) {
var mousex = e.pageX + 20; //Get X coodrinates
var mousey = e.pageY + 20; //Get Y coordinates
var tipWidth = tip.width(); //Find width of tooltip
var tipHeight = tip.height(); //Find height of tooltip
//Distance of element from the right edge of viewport
var tipVisX = $(window).width() - (mousex + tipWidth);
//Distance of element from the bottom of viewport
var tipVisY = $(window).height() - (mousey + tipHeight);
if (tipVisX < 20) { //If tooltip exceeds the X coordinate of viewport
mousex = e.pageX - tipWidth - 20;
} if (tipVisY < 20) { //If tooltip exceeds the Y coordinate of viewport
mousey = e.pageY - tipHeight - 20;
}
//Absolute position the tooltip according to mouse position
tip.css({ top: mousey, left: mousex });
});
});
$(document).ready(function() {
//Tooltips
$(".tipheader").hover(function(){
tip = $(this).find('.tip');
tip.show(); //Show tooltip
}, function() {
tip.hide(); //Hide tooltip
}).mousemove(function(e) {
var mousex = e.pageX + 30; //Get X coodrinates
var mousey = e.pageY + -20; //Get Y coordinates
var tipWidth = tip.width(); //Find width of tooltip
var tipHeight = tip.height(); //Find height of tooltip
//Distance of element from the right edge of viewport
var tipVisX = $(window).width() - (mousex + tipWidth);
//Distance of element from the bottom of viewport
var tipVisY = $(window).height() - (mousey + tipHeight);
if (tipVisX < 20) { //If tooltip exceeds the X coordinate of viewport
mousex = e.pageX - tipWidth - 20;
} if (tipVisY < 20) { //If tooltip exceeds the Y coordinate of viewport
mousey = e.pageY - tipHeight - 20;
}
//Absolute position the tooltip according to mouse position
tip.css({ top: mousey, left: mousex });
});
});
Then this is the CSS
.tip {
color: #fff;
background: #1d1d1d;
display: none; /*--Hides by default--*/
padding: 10px;
position: absolute;
z-index: 1000;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
}
This is the HTML that calls the tooltip.
The first line is for the main section, the second is for the header.
<img src="IMAGE URL" alt="" /><span class="tip">CONTENT</span>
<img src="IMAGE URL" alt="" /><span class="tip">CONTENT</span>
The reason I used two different javascript sections is because the tooltips in the header and the tooltips in the main section needed different parameters.
Now, the problem is that the tooltips work fine in the header, but they're not working in the main section and I can't think of any possible reason why, I tried everything I could think of and it's not working. Does anyone else know how to fix it?
Do you need absolute positioning on your div elements (it doesn't appear so since you aren't specifying any top or left values)? Since the tooltip uses absolute positioning and is nested within another element which also uses absolute positioning, it can't "break out" from the parent element.
I recommend that you remove the position: absolute rule from the #header and #main styles. Alternatively, removing the overflow: auto rule from the #header style seems to work as well.
http://jsfiddle.net/tz59G/3/
I was finally able to fix it by setting the #header div to fixed positioning (so it stays at the top of the window without absolute positioning) and I made the #main div static positioned and moved it down with just page breaks.
*I decided it would be more efficient to only use one type of tooltip, so I removed one.
*Note how I set the min-height so I can display how the #header div stays at the top when you scroll, that is the effect I wanted.
http://jsfiddle.net/tz59G/5/
to see the finished result, go here: ultimatemmo.webege.com
The navbar on top is my header div and everything else is my main div.
Note: that site isn't a normal traffic site, I made it for a school project, now it's just a personal project that I'm constantly improving. The only reason it's on the internet is so my friends and girlfriend can see my progress on it.