How to make a div scroll when I scroll after certain point? - javascript

I would like to create a div, that is situated beneath a block of content but that once the page has been scrolled enough to contact its top boundary, becomes fixed in place and scrolls with the page. I know I've seen at least one example of this online but I cannot remember it for the life of me.
Any thoughts?

[Working demo]
var el = $("#sticky");
var win = $(window);
var width = el.width();
var height = el.height();
var win_height = $(window).height();
window.onscroll = function() {
var offset = el.offset().top + height - win_height;
if ( win.scrollTop() > offset ) {
window.onscroll = function() {
el.css({
width: width,
position: "absolute",
top: win.scrollTop() + win_height - height
});
};
}
};
If you don't need to support IE based browsers you can use:
position: "fixed"
bottom: 0

Related

Get the visible height of a div with jQuery

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

Animate div on click

I'm trying to animate the margins of a div set by the window height and width.
Please see the fiddle.
http://jsfiddle.net/cf4zM/
The bottom and right margins animate fine on the first click, however left and top do not animate. After first click all margins animate.
Any help would be great.
$( document ).ready(function() {
$("#resizer").height(
$(window).innerHeight()
);
$("#resizer").width(
$(window).innerWidth()
);
});
var xminus = window.innerWidth/15;
var yminus = window.innerHeight/15;
function resizediv(){
var x = $("#resizer").width() - xminus;
var y = $("#resizer").height() - yminus;
var heightoffset = (window.innerHeight - $("#resizer").height())/2
var widthoffset = (window.innerWidth - $("#resizer").width())/2
$("#resizer").animate({
width : x,
height : y,
marginTop : heightoffset,
marginBottom : heightoffset,
marginLeft : widthoffset,
marginRight : widthoffset
}, 1000, function(){
});
}
Demo
It's because of the margin you are setting with jquery.
Check out margin of the div on each click through browser debugger you will find the error.
You can also set margin auto in the css assigning top, bottom, left, right position 0 in order to have absolute centering of the div and remove margin set from script.
CSS
#resizer{
background-color: white;
position: fixed;
top:0;
bottom:0;
right:0;
left:0;
margin:auto;
padding: 0;
border:1px solid #000;
}
jquery
$( document ).ready(function() {
$("#resizer").height(
$(window).innerHeight()
);
$("#resizer").width(
$(window).innerWidth()
);
});
var xminus = window.innerWidth/15;
var yminus = window.innerHeight/15;
function resizediv(){
var x = $("#resizer").width() - xminus;
var y = $("#resizer").height() - yminus;
var heightoffset = (window.innerHeight - $("#resizer").height())/2
var widthoffset = (window.innerWidth - $("#resizer").width())/2
$("#resizer").animate({
width : x,
height : y
}, 1000, function(){
});
}
You're setting the left margin to:
var widthoffset = (window.innerWidth - $("#resizer").width()) / 2
Originally, the resizer width is the same as the window's width, which means that at the first click, the offset will always be 0, which is causing the element to remain in its position.
On the other hand, width is set to:
$("#resizer").width() - xminus
which means that it will be reduced from the very first click.

Stick last scrolled header to the top of the viewport?

jsfiddle: http://jsfiddle.net/R3G2K/1/
I have multiple divs with content, and each one has a header. I'd like to make the last header that left the viewport to be "sticky" or fixed to the top of the viewport.
I've seen a few different approaches to this, but none of them seem to work for multiple headers.
$('.content').scroll(function(){
console.log('scrolling')
$('.itemheader').each(function(){
var top = $(this).offset().top;
console.log(top);
if(top<25){
$(this).addClass('stick');
}else{
$(this).removeClass('stick');
}
})
});
I think there might be a conflict since each header has the same class name, and no unique identifier, so my above attempt isn't working out right.
http://jqueryfordesigners.com/iphone-like-sliding-headers/
This was exactly what I was looking for:
http://jqueryfordesigners.com/demo/header-slide.html
$(document).ready(function () {
// 1. grab a bunch of variables
var $container = $('#box');
var $headers = $container.find('h2');
var zIndex = 2;
var containerTop = $container.offset().top + parseInt($container.css('marginTop')) + parseInt($container.css('borderTopWidth'));
var $fakeHeader = $headers.filter(':first').clone();
// 2. absolute position on the h2, and fix the z-index so they increase
$headers.each(function () {
// set position absolute, etc
var $header = $(this), height = $header.outerHeight(), width = $header.outerWidth();
zIndex += 2;
$header.css({
position: 'absolute',
width: $header.width(),
zIndex: zIndex
});
// create the white space
var $spacer = $header.after('<div />').next();
$spacer.css({
height: height,
width: width
});
});
// 3. bind a scroll event and change the text of the take heading
$container.scroll(function () {
$headers.each(function () {
var $header = $(this);
var top = $header.offset().top;
if (top < containerTop) {
$fakeHeader.text($header.text());
$fakeHeader.css('zIndex', parseInt($header.css('zIndex'))+1);
}
});
});
// 4. initialisation
$container.wrap('<div class="box" />');
$fakeHeader.css({ zIndex: 1, position: 'absolute', width: $headers.filter(':first').width() });
$container.before($fakeHeader.text($headers.filter(':first').text()));
});

jQuery Image Viewport Calculation Algorithm to Avoid Scrollbars

I am creating an image hover effect but I am having problem with it. When I hover over certain images, the scrollbars appear which I want to avoid but don't know how to do so. I believe it has to do with viewport and calculations but am not sure how that is done.
Example Here
JSBin Code
Here is the code:
$('.simplehover').each(function(){
var $this = $(this);
var isrc = $this[0].src, dv = null;
$this.mouseenter(function(e){
dv = $('<div />')
.attr('class', '__shidivbox__')
.css({
display: 'none',
zIndex : 9999,
position: 'absolute',
top: e.pageY + 20,
left: e.pageX + 20
})
.html('<img alt="" src="' + isrc + '" />')
.appendTo(document.body);
dv.fadeIn('fast');
})
.mouseleave(function(){
dv.fadeOut('fast');
});
});
Can anyone please help me how do I make it so that hovered image appears at such place that scrollbars dont appear? (Of course we can't avoid scrollbar if image size is very very big)
I just want to show original image on zoom while avoiding scrollbars as much as possible.
Please note that I am planning to convert it into jQuery plugin and therefore I can't force users of plugin to have overflow set to hidden. The solution has do with viewport, left, top, scroll width and height, window width/height properties that I can incorporate into plugin later on.
Update:
I have come up with this:
http://jsbin.com/upuref/14
However, it is very very hacky and not 100% perfect. I am looking for a better algorithim/solution. I have seen many hover plugins that do this very nicely but since I am not that good at this, I can't do it perfectly well. For example Hover Zoom Chrome Plugin does great job of showing hovered images at appropriate place based on their size.
Like this:
html{overflow-x:hidden;}
html{overflow-y:hidden;}
All you need to do is add these definitions to your CSS and you're done.
Update with Resize: this is the mouseenter code for resizing and repositioning the pictures BOTH horizontally and vertically. Now, no matter where the HOVER image shows up, it's resized and positioned to always show in full AND uncut. As far as the scrollbars are concerned, if you show more thumbnails than can fit on the page, you will have scrollbars even before the HOVER images show up.
FINAL AND WORKING UPDATE: Because you had focused on the scrollbars being hidden, I think you overlooked the fact that if you put more thumbnails than the viewport can contain, the scrollbars would show up anyway and that therefore, since the user can scroll down the document, when you calculate the position of the hover image, not only do you need to account for the resize but you also to account for the scrollTop position too! FINAL JSBIN HERE, all pictures are showing RESIZED and in FULL no matter where the scrollTop is and no matter what the viewport size is.
$this.mouseenter(function () {
dv = $('<div />')
.attr('class', '__shidivbox__')
.css({
'display': 'none',
'z-index': 9999,
'position': 'absolute',
'box-shadow': '0 0 1em #000',
'border-radius': '5px'
})
.html('<img alt="" src="' + isrc + '" />')
.appendTo(document.body);
var DocuWidth = window.innerWidth;
var DocuHeight = window.innerHeight;
var DvImg = dv.find('img');
var TheImage = new Image();
TheImage.src = DvImg.attr("src");
var DivWidth = TheImage.width;
var DivHeight = TheImage.height;
if (DivWidth > DocuWidth) {
var WidthFactor = (DivWidth / DocuWidth) + 0.05;
DivHeight = parseInt((DivHeight / WidthFactor), 10);
DivWidth = parseInt((DivWidth / WidthFactor), 10);
}
var ThumbHeight = $this.height();
var ThumbWidth = $this.width();
var ThumbTop = $this.position().top;
var ThumbLeft = $this.position().left;
var SpaceAboveThumb = ThumbTop - $(document).scrollTop();
var SpaceBelowThumb = DocuHeight - ThumbTop - ThumbHeight + $(document).scrollTop();
var MaxHeight = Math.max(SpaceAboveThumb, SpaceBelowThumb);
if (DivHeight > MaxHeight) {
var HeightFactor = (DivHeight / MaxHeight) + 0.05;
DivHeight = parseInt((DivHeight / HeightFactor), 10);
DivWidth = parseInt((DivWidth / HeightFactor), 10);
}
var HoverImgLeft = 0;
var HoverImgTop = 0;
if (SpaceBelowThumb > SpaceAboveThumb) {
HoverImgTop = ThumbTop + ThumbHeight;
} else {
HoverImgTop = ThumbTop - DivHeight;
}
HoverImgTop = (HoverImgTop < 0) ? 0 : HoverImgTop;
HoverImgLeft = (DocuWidth - DivWidth) / 2;
dv.find('img').css({
'width': DivWidth,
'height': DivHeight,
'border-radius': '5px'
});
dv.css({
'left': HoverImgLeft,
'top': HoverImgTop
});
dv.fadeIn('fast');
});
Well, this looks fun. Anyway, here's my answer. I've been watching this for a few days and though I'd chip in too. The following will make sure that the hovering images do not go out of the viewport and in the event that the width of the image is bigger than the available space for display, the display of the image will be resized (You can comment out the code that does this if you don't want it. Just look for the word "resize" in the code).
var $document = $(document);
$('.simplehover').each(function(){
var $this = $(this);
// make sure that element is really an image
if (! $this.is('img')) return false;
var isrc = $this[0].src, ibox = null;
if (! isrc) return false;
ibox = $('<img />')
.attr('class', 'simpleimagehover__shidivbox__')
.css({
display: 'none',
zIndex : 99,
MozBoxShadow: '0 0 1em #000',
WebkitBoxShadow: '0 0 1em #000',
boxShadow: '0 0 1em #000',
position: 'absolute',
MozBorderRadius : '10px',
WebkitBorderRadius : '10px',
borderRadius : '10px'
})
.attr('src', isrc)
.appendTo(document.body);
$this.bind('mouseenter mousemove', function(e) {
$('.simpleimagehover__shidivbox__').hide();
var left = e.pageX + 5,
top = e.pageY + 5,
ww = window.innerWidth,
wh = window.innerHeight,
w = ibox.width(),
h = ibox.height(),
overflowedW = 0,
overflowedH = 0;
// calucation to show element avoiding scrollbars as much as possible - not a great method though
if ((left + w + $document.scrollLeft()) > ww)
{
overflowedW = ww - (left + w + $document.scrollLeft());
if (overflowedW < 0)
{
left -= Math.abs(overflowedW);
}
}
// 25 is just a constant I picked arbitrarily to compensate pre-existing scrollbar if the page itself is too long
left -= 25;
left = left < $document.scrollLeft() ? $document.scrollLeft() : left;
// if it's still overflowing because of the size, resize it
if (left + w > ww)
{
overflowedW = left + w - ww;
ibox.width(w - overflowedW - 25);
}
if (top + h > wh + $document.scrollTop())
{
overflowedH = top + h - wh - $document.scrollTop();
if (overflowedH > 0)
{
top -= overflowedH;
}
}
top = top < $document.scrollTop() ? $document.scrollTop() : top;
ibox.css({
top: top,
left: left
});
ibox.show();
});
$('.simpleimagehover__shidivbox__').mouseleave(function(){
$('.simpleimagehover__shidivbox__').hide();
});
$document.click(function(e){
$('.simpleimagehover__shidivbox__').hide();
});
$document.mousemove(function(e){
if (e.target.nodeName.toLowerCase() === 'img') {
return false;
}
$('.simpleimagehover__shidivbox__').hide();
});
});
While my solution itself is not perfect, you might find something useful in there that can help you determine the viewport. Also, you might want to think about the performance of the code. Since this is a plugin that you're building, you'll want to make some optimizations before releasing it to public. Basically, just make sure it's not slow.
You can position the image based on the available width: http://jsbin.com/upuref/19/
This demo takes in account the available space for positioning the image (i.e. the window width minus the image width). Also I've improved the event order, with the popup div only starting its fade-in after the image has been loaded.
My answer too (JSBin DEMO)
$('.simplehover').each(function(){
var $this = $(this);
// make sure that element is really an image
if (! $this.is('img')) return false;
var isrc = $this[0].src, dv = null;
if (! isrc) return false;
$this.mouseenter(function(e){
// mouse x position
var initXPos = e.pageX;
var initYPos = e.pageY+20-$(window).scrollTop();
var windowWidth = $(window).width();
var windowHeight = $(window).height();
// load original image
var $img = $('<img/>');
$img.on('load',function(eload) {
var widthImage = this.width;
var heightImage = this.height;
// set inline style for get sizes after (see problems webkit and cache)
$(this).css('width',widthImage);
$(this).css('height',heightImage);
var ratio = widthImage/heightImage;
var finalXPos = initXPos+widthImage>windowWidth? windowWidth-widthImage-5 : initXPos;
var finalYPos = initYPos;
var img = this;
// resize image if is bigger than window
if(finalXPos<0) {
finalXPos = 0;
$img.css('width', windowWidth-10);
$img.css('height',(windowWidth-10)/ratio);
}
// If overflow Y
if(finalYPos+getSize($img,'height')>windowHeight) {
// calculate where is more space (top or bottom?)
var showOnTop = (windowHeight-initYPos-10)<windowHeight/2;
if(showOnTop) {
if(initYPos<getSize($img,'height')) {
$img.height(initYPos-30);
$img.width(getSize($img,'height')*ratio);
}
finalYPos = 0;
finalXPos = initXPos+getSize($img,'width')>windowWidth? windowWidth-getSize($img,'width')-5 : initXPos;
}else {
// show on bottom
if(windowHeight-initYPos<getSize($img,'height')) {
$img.height(windowHeight-initYPos-10);
$img.width(getSize($img,'height')*ratio);
}
finalXPos = initXPos+getSize($img,'width')>windowWidth? windowWidth-getSize($img,'width')-5 : initXPos;
}
}
dv = $('<div />')
.attr('class', '__shidivbox__')
.css({
display: 'none',
zIndex : 9999,
position: 'absolute',
MozBorderRadius : '5px',
WebkitBorderRadius : '5px',
borderRadius : '5px',
top: finalYPos+$(window).scrollTop(),
left: finalXPos
}).append($img)
.appendTo(document.body);
dv.fadeIn('fast');
});
// load the original image (now is the same, but I think is better optimize it)
$img.attr("src",$this.attr("src"));
function getSize($el,widthOrHeight) {
// horrible but working trick :)
return +$el.css(widthOrHeight).replace("px","");
}
})
.mouseleave(function(){
dv.fadeOut('fast');
});
});
this script adapt the image to window size and adjust x position if needed.

jQuery animate height expand outward instead of down

I am building my sister a website at SarahNWatson.com/new. I have it set up as a big photo/video album. For actual content pages, such as her Bio, I have it so that it opens a modal window.
Right now I have the modal window so that the height starts at 0px and then animates open, however this gives me a slide down effect. I want it to open outwards as if something were in the box pushing in both directions. How can I accomplish this?
Here's the code:
function createModal(filler) {
var $this = $(this);
var $body = $('body');
var winHeight = $(window).height();
var winWidth = $(window).width();
$body.prepend('<div id="blackout">');
$("#blackout").css({ height:winHeight }).fadeIn(1800);
$body.prepend('<div id="modal_window">');
$("#modal_window").html(filler).fadeIn(2000);
var modalHeight = $("#modal_window").height();
var modalWidth = $("#modal_window").width();
var offsetH = winHeight/2 - modalHeight;
var offsetW = winWidth/2 - modalWidth/2;
$("#modal_window").css({ top:offsetH, left:offsetW, height:'0px' }).animate({ height:modalHeight });
}
And the CSS:
#modal_window {
position: absolute;
z-index: 1000;
width: 600px;
background: rgba(0,0,0,.8);
padding: 15px;
}
Start with offsetH = winHeight/2 and offsetW = winWidth/2. Then, animate all of the top, left, and height CSS properties. The final top will be (winHeight - modalHeight)/2 and final left will be (winWidth - modalWidth)/2.
function createModal(filler) {
var $this = $(this);
var $body = $('body');
var winHeight = $(window).height();
var winWidth = $(window).width();
$body.prepend('<div id="blackout">');
$("#blackout").css({ height:winHeight }).fadeIn(1800);
$body.prepend('<div id="modal_window">');
$("#modal_window").html(filler).fadeIn(2000);
var modalHeight = $("#modal_window").height();
var modalWidth = $("#modal_window").width();
var offsetH1 = winHeight/2;
var offsetH2 = (winHeight-modalHeight)/2;
var offsetW = (winWidth-modalWidth)/2;
$("#modal_window")
.css({ top:offsetH1, left:offsetW, height:'0px' })
.animate({ top:offsetH2, height:modalHeight });
}
UPDATE: Code sample updated to only animate vertically.
Essentially, you're moving the box up as it grows taller. So instead of sliding down it gives the appearance of expanding from the middle.
You could give it a margin-top of half the ultimate height (modalHeight) and add marginTop:"toggle" to your animation:
.animate({ height:modalHeight, marginTop:"toggle"})
Try using the CSS setting Bottom instead of Top. You should then use the bottom of the element to position it thus having it animte upwards instead of downwards.
.animate() always animate away from the anchor.

Categories