I'm working on a website and I'm using jQuery and the CSS framework Zurb Foundation. This framework changes actually every CSS default property of every object on the page, so it's quite hard to get if this is a CSS problem.
I have this code:
function handleNavBarDot() {
var $navbar = $('nav#nav-bar'),
$win = $(window);
// inserting the div#circle after the nav bar in the markup
$navbar.after('<div id="circle"></div>');
var $dot = $('div#circle');
function attachDot() {
// using + (and not -) because the margin is negative
var topStart = parseDim($navbar.css('top')) + parseDim($navbar.css('margin-top'));
var availHeight = $navbar.outerHeight();
var pageScrolled = $win.scrollTop() / ($(document).height() - $win.height());
$dot.css({
'position': 'fixed',
'right': (parseDim($navbar.css('right')) - Math.floor($dot.width() / 2)) + 'px',
'top': (topStart + availHeight * pageScrolled) + 'px',
});
}
attachDot();
$win.resize(attachDot);
$win.scroll(attachDot);
}
which moves this little dot up and down a vertical line near the navigation bar. As you can see, the vertical offset is calculated based on how much page has been scrolled down; the horizontal position is calculated based on the position of the navigation bar, which is also set by jQuery in another part of the script (in order to make it always fixed to the side of the page content - which has fixed width - instead of fixed to the browser window).
When I run this in Chrome/Safari/Opera everything works fine and the dot is always in the right position.
In Firefox though, the dot is where it should be only when I first load the page. After a single call to $(window).scroll() or to $(window).resize() the dot moves to the right, altering its right CSS property.
When I inspect the page, the element.style right property changes on resize and scroll and it's of the right value, but the computed right is different - a little moved on the right.
Final note: in Firebug, if I disable/re-enable the
*, *:after, *:before { -moz-box-sizing: border-box }
the dot comes back to the right position.
What can be the cause of this problem?
(if you want to see the problem in action, head to the actual site)
Related
I've been working on a project and I've noticed some inconsistency in bootstrap's behavior that I would like to solve.
When a popover (or tooltip, whatever, they're basically the same) is nearing the edge of the screen - if it's a right-sided one, when nearing the edge - it will contract so as not to go offscreen (it only works up to a point, but that's usually enough).
This doesn't happen when the placement is to the left.
i.e.:
right placement:
Normal width:
Close to the edge:
left placement:
Normal width:
close to the edge:
These images are from a small DEMO I wrote to illustrate the problem.
I've messed around with the source code, so far to no avail. I can't seem to place my finger on what exactly causes this behavior in the first place.
Any ideas?
p.s.
I'm using Bootstrap 3.1.1. The new 3.2 does not solve the issue (and I would like to avoid upgrading at this point).
Major Update!
After some digging, I figured out that this has nothing to do with bootstrap - it's simple css - it seems that when you position an element absolutely and push it to the sides it will try and stay withing the screen.
I never knew about this behavior, and it happens automatically - but only to the the direction you're pushing - i.e. a div pushed from the left will contract when reaching the right edge of the screen and vice versa.
It just so happens that popovers are only positioned with the left assignment - which is why we're seeing the inconsistend behavior - when it's pushed to the right it contracts but not the other direction.
So the solution is to assign right instead - sounds simple?
Not so much. I took the source code and manipulated it a bit, adding these lines (somewhere arond line 250 in the jsfiddle):
if (placement == 'left' && offset.left < 0) {
var right = ( $(window).width() + 10 ) - ( offset.left + actualWidth );
offset.left = 0;
$tip.offset(offset);
$tip.css({ 'right': right });
}
Seems reasonable, right? If the offset to the left is less than 0 (i.e., it goes offscreen) then calculate the window width and remove from that the left offset and the width of the popover (actualWidth) itself and you get the distance from the right.
Then, make sure to reset the offset left and apply the right positioning. But... it only sorta works - which is to say it only works the second time around.
Check it out for yourself! Hover once, and it's misplaced, pull the mouse to the side and try again and suddenly it's positioned correctly. What the hell?
edit
Ok this seems to come up a lot, so I'll make it clear:
I know about auto placement. I don't want it. I want to control where the popover goes, letting it decide automatically is not a solution to the problem, it's merely avoiding it
Ok, I've gotten a little closer.
Once you assign the right in css, the actualWidth and actualHeight will change, so you need to update those variables. Around line 253 in your jsfiddle:
if (placement == 'left' && offset.left < 0) {
var right = ( $(window).width() + 10) - ( offset.left + actualWidth );
offset.left = 0;
$tip.offset(offset);
$tip.css({ 'right': right });
actualWidth = $tip[0].offsetWidth;
actualHeight = $tip[0].offsetHeight;
}
This works the first time you hover, but every time after that, it sets the top to be too high, so you can't read the top of the tooltip.
UPDATE:
It appears that having the right value set is messing up the positioning in the applyPlacement function. To fix this, clear the right value before setting the initial actualWidth and actualHeight (around line 225):
$tip.css({ 'right': '' });
// check to see if placing tip in new offset caused the tip to resize itself
var actualWidth = $tip[0].offsetWidth
var actualHeight = $tip[0].offsetHeight
I believe this has a lot to do with the browser/client that accesses the webpage. For instance, in order to display the tip's on the proper side (not bunched up or illegible off the the left or right), determine the offsetLeft & offsetTop of the object element with javascript and place it accordingly. You could have different pages for different resolutions.
CSS example for a screen width from 400-720 pixels:
#media screen and (min-width:400px) and (max-width:721px)
some pseudo code:
if (this.offsetLeft < 200) //if there's not enough room to display the tip
tip.offsetLeft += 200;
I think you've basically got it, it's working fine for me.
Just add in the minimum width detection so that it doesn't go too small.
if (/bottom|top/.test(placement)) {
var delta = 0
if (offset.left < 0) {
delta = offset.left * -2
offset.left = 0
$tip.offset(offset)
actualWidth = $tip[0].offsetWidth
actualHeight = $tip[0].offsetHeight
}
this.replaceArrow(delta - width + actualWidth, actualWidth, 'left');
} else {
if (placement == 'left' && offset.left < 0) {
var right = ( $(window).width() + 10) - ( offset.left + actualWidth );
offset.left = 0;
$tip.offset(offset);
$tip.css({ 'right': right });
}
this.replaceArrow(actualHeight - height, actualHeight, 'top');
}
I have a project where the requirement is to move the footer ( #footer ) upward while scrolling down the page in a parallax-like effect. When you start scrolling down the page, the footer should start moving upward only until it's visible in the (bottom part of the) viewport.
The footer should have covered most of the preceding <div> half way up and in full when it has reached the top of the viewport.
The page may have a similar html structure like this :
<body>
<div id="sectionA" class="div">First section</div>
<div id="sectionB" class="div">Second section</div>
<div id="sectionC" class="div">Third section
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
</div>
<div id="footer" class="div cf">Footer</div>
</body>
The parallax-like effect is achieved via javascript/jQuery adding a dynamic negative value to the top CSS property of the (relative positioned) footer. Here is the code for what it matters :
var $window = jQuery(window),
$footer = jQuery("#footer"),
$viewport = window.innerHeight,
$startEffect = $footer.offset().top - $viewport;
function footerParallax() {
var $scrollPos = $window.scrollTop() - $startEffect,
$ratio = 0.6;
$footer.css({
top: -($scrollPos * $ratio)
});
}
$window.scroll(function () {
footerParallax();
});
The (obvious) issue is that as soon as the top property starts getting a negative value, the footer starts moving away from the bottom of the page.
I have prepared a JSFIDDLE and assigned colors to each section and body to make it clearer. The body (dark-red) is visible under the footer after scrolling to the bottom.
What have I tried?
modifying the margin-top instead of the top property: this does the trick, however the preceding <div> that has to be covered by the footer (#sectionC in the example above) overlaps the contents of the footer and breaks its layout regardless that it is not visible due to its z-index property (added some floating boxes in the fiddle to make it evident.... a clearfix hack didn't help either.)
setting a static position to the footer: neither top or margin-top have effect over a static element.
Changing/reducing dynamically the height of #sectionC instead of top of footer to produce the effect of moving the second upwards : the footer stops moving as soon as height is equal to 0 (neither negative size or negative paddings are allowed)
Changed the height dynamically of the html and/or body tags to no avail.
I have also tried some parallax plugins like skrollr and skrollr-stylesheets and some others.
The problem with this solution (same with others) is that it relays in an specific (offset) position of the footer measured in px and set in a data attribute, but if the content changes dynamically, for example using the masonry plugin to arrange elements in another section of the document, the measures become inaccurate and the footer may start moving too early or too late.
By the way, other CSS sticky-footer techniques won't work because, well, they actually push the footer to the bottom of the page, and here we are doing the opposite.
I guess the question is either :
how to keep the footer stick to the bottom of the page while it is moved upwards? - or -
how to reduce the gap to 0 between the end of the document and the bottom edge of the footer?
I am starting to think that this issue has not a real solution the way it is, or maybe I am already too tired to see the obvious. I am interested in learning alternative solutions or hacks via CSS / javascript / jQuery or all of the above.
Bear in mind that I am not asking how to create the parallax effect UNLESS a totally different approach (or tweaks to the existing js code) solves the position issue.
IMPORTANT : Please consider that this is a WP site with an XHTML 1.0 Transitional DOCTYPE, and has installed many other jQuery plugins like masonry, scrollTo, jQuery UI, etc. I may have not control to change many things from the original structure (and I don't want to) so the idea is to implement this without breaking too many things and from a modular script.
EDIT #1 : Added a graphic to clarify the question.
Figure A. shows a regular web page scrolled down to the end. The red square represents the viewport and the footer (grey) is slighted moved to the right for illustration purposes. The body has a reddish background color (not visible in normal conditions) just for illustration purposes too. NOTE: the height of each section as well as the height of the footer is determined by their content (forms, images, text, etc.) so is NOT fixed.
Figure B. shows the current issue: If footer slides up in a parallax-like effect (see JSFIDDLE for reference) while scrolling down the page, it starts covering any preceding section above it (WITHOUT modifying neither its own height or the height of the preceding sections) AND it also starts separating itself from the bottom of the page, therefore the body's color background becomes visible. NOTE: the bigger the viewport is (fullscreen mode for instance) the higher the footer is moved upward (and more content is covered by it)
Figure C. is the expected result: the footer should be stuck to the bottom of the page, in other words, it should be the last visible element after the page has been totally scrolled down (and not the body background as in Figure B.) Notice that the contents and the size of each section (including the footer) should (ideally) remain untouched. Having said that, adding padding bottom to the footer or increasing its height is not the expected result since it would break its original visual layout.
Updated Version
Below is an updated version that should better matches your requirements.
This version goes back to relative positioning for the footer element and uses margin-top to position it.
margin-top is calculated off of the previous elements offset, height and current window scroll position. It then uses either
the viewport height if the footer starts offscreen
the initial top value of the footer element ($startEffect) if the footer started onscreen
to determine the actual value for margin-top.
To help keep the footer's layout from being affected by this, wrapping the content of the footer in an absolutely positioned div did the trick for the sample code provided.
Example Fiddle
CSS:
#footer > div {
position: absolute;
top: 0;
left: 0;
...
}
HTML:
<div id="footer" class="div cf"><div>Footer</div></div>
Code:
var $window = jQuery(window),
$footer = jQuery("#footer"),
$viewport = window.innerHeight,
$startEffect = $footer.offset().top;
$prev = $footer.prev(),
$useStartEffect = $startEffect < $viewport;
function footerParallax() {
var $scrollPos = $window.scrollTop() - $startEffect,
$ratio = 0.6;
var prevOffset = $prev.offset().top + $prev.height() - $window.scrollTop();
var marginTop = 0;
if(prevOffset < $viewport && prevOffset < $startEffect) {
if($useStartEffect) {
marginTop = (prevOffset - $startEffect)*$ratio;
} else {
marginTop = (prevOffset - $viewport)*$ratio;
}
}
$footer.css({
"margin-top": marginTop + 'px'
});
}
$window.scroll(function () {
footerParallax();
});
footerParallax();
How was it solved?
As I mentioned in my question, I was too tired to see the obvious but #dc5's answer put me on the right track :
To help keep the footer's layout from being affected,
wrapping the content of the footer in an absolutely
positioned div does the trick
Based on that comment, the answer became simpler than the whole code he proposed needing only :
(dynamically) wrapping the content of the footer in an absolutely positioned div using jQuery's .wrapInner() method
animating the footer by setting the margin-top property instead of the top property
So this extra CSS :
#footerInnerWrapper {
position: absolute;
top:0;
left:0;
width: 100%;
background-color: #666 /* same as footer */
}
and the tweaked original code
var $window = jQuery(window),
$footer = jQuery("#footer"),
$viewport = window.innerHeight,
$startEffect = $footer.offset().top - $viewport;
// add inner wrapper
$footer.wrapInner('<div id="footerInnerWrapper" />');
function footerParallax() {
var $scrollPos = $window.scrollTop() - $startEffect,
$ratio = 0.6;
$footer.css({
// top: -($scrollPos * $ratio)
marginTop: -($scrollPos * $ratio)
});
}
$window.scroll(function () {
footerParallax();
});
did the trick. See JSFIDDLE
This does what I think you need, the footer sticks when it has scrolled in view entirely:
jsFiddle
Code added:
function footerParallax() {
var $scrollPos = $window.scrollTop() - $startEffect,
$ratio = 0.6,
$newTop = -($scrollPos * $ratio),
$oldTop = parseInt($footer.css('top')),
$nonRelTop = $footer.offset().top - $oldTop,
$wanted = ($window.scrollTop()+$viewport-$footer.height());
if ($nonRelTop + $newTop < $wanted) {
$('#sectionC').css('display', 'none');
$wanted = ($window.scrollTop()+$viewport-$footer.height());
$nonRelTop = $footer.offset().top - $oldTop;
$newTop = $wanted - $nonRelTop;
} else {
$('#sectionC').css('display', 'block');
}
$footer.css('top', $newTop);
}
$window.scroll(footerParallax);
And in the CSS I added this so that $footer.css('top') wouldn't produce NaN:
#footer {
top:0;
/* ... */
}
EDIT: A completely new approach after more clarification of OP. I now have a fixed position footer that starts increasing in height to take over the entire screen when the user has scrolled passed half of the document. The HTML, CSS and Javascript have all been updated to achieve this:
jsFiddle
I have a div, absolutely positioned, originally non-visible that is shown at the position of an element being clicked rendering its preview (top position of the preview is lined to the top of the element clicked).
When the element being clicked is positioned low, the preview is render somewhat below the original page border, and scrolling is necessary. I want to move the preview upward to have its bottom edge on the previous page bottom limit. The problem is the code I use doesn't return what is expected for the page height (it is greater than the sum of the preview height and the clicked-element top position).
Here's the code:
file 1:
jQuery('elementClicked').live('click',function(){
...
jQuery("previewDiv").setTopAtClickedElement(jQuery(this));
...
}
file 2:
jQuery.fn.setTopAtClickedElement = function (element) {
//original positioning
this.css('top', element.offset().top + 'px');
// the troublesome part where the eventual correction should be done
if (element.offset().top + this.height() > jQuery(document).height())
{
this.css('top', jQuery(document).height() - this.height() + 'px');
}
}
Similar happens when I use
Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight)
for the measure of the document height as suggested on a link
Do you have any suggestions on how I should implement this troublesome part of the code?
Please tell if I wasn't clear enough,
Thank you,
Instead of .height() Try using jQuery's outer.height() - api docs, which will take into account any padding (and optionally marign) you have on the page.
A jsfiddle or codepen will help us all out in solving your problem.
I'm trying to write my own lightbox script but I'm stuck on a problem.
The wrapper div centering is done through position: absolute and top / left positioned by calculating...
top:
_center_vertical = function() {
return (($(window).height() - wrapper.height()) / 2) - (options.margin + options.border) + $(window).scrollTop()
}
left:
_center_horizontal = function() {
return (($(window).width() - wrapper.width()) / 2) - (options.margin + options.border) + $(window).scrollLeft()
}
The wrapper div is centered on .load() and on $(window).resize() / $(window).scroll().
When the image is loaded and appended to wrapper, top and left is calculated using the functions above, horizontal centering is correct, but vertical centering is not. It is off by around 10px or more.
When the browser window is resized or scrolled, it calls the function which animates the centering which uses the same function to calculate the top and left. The window resize / scroll does center the image properly.
I have tried using jQuery deferred.then() to have it calculate the top / left after the image is appended, but it didn't change anything.
Example: http://jsfiddle.net/vfMNQ/
I initially thought that the difference in top position changed when I played around with things like wrapper padding (aka my border), however, I found that I was wrong.
I added some console.log('image load height: ' + ((($(window).height() - wrapper.height()) / 2) - (options.margin + options.border)) + 'px') to .load() and .scroll() and found that the difference was oddly 21px no matter what. The default border is 10px, margin is 30... so where did the 21 come from?
I'd hate to use + 21 as a hack, but seems like nobody can figure it out.
Your problem appears to be in the loading div:
.lbe-loading {
background: #578DB2 url(/public/images/loading.gif) no-repeat center center;
width: 32px;
height: 32px;
padding: 5px;
}
height:
32 + padding: (5 * 2) = 42
42 / 2 = 21px
Looks like you've appended the image with the loading div still appended to the wrapper.
wrapper.append(loading);
...
$(function() {
var img = $(new Image());
img.load(function() {
wrapper.append(this) // .lbe-loading still appended here
.css({ // Position wrapper.
...
});
loading.remove(); // Too late.
If I remove .append(loading), it centers fine.
Put .lbe-loading on a different div so it's not being added to the wrapper's height.
Best guess:
You are trying to calculate the height of the wrapper before actually putting in the image.
i.e. you are append(this) and then immediately trying to calculate the height before giving the browser a chance to display and load the image.
When I put in debugging code wrapper.height() changed by 40 pixels after resizing the display. 40 pixels is exactly the border + margin. (And when I changed those, the difference changed too.)
Its a delay in wrapper getting the height and width of the image. The jQuery for centering is executing before Browser has preformed its reflow and given 'wrapper' the height and width of the image.
I forked your fiddle and fixed it here: http://jsfiddle.net/3th5k/
by setting wrapper's height and width with javascript before centering. This way your centering calculation draws from the right data source, the javascript image object rather than the dom.
Also please note that the css statement with 'opacity: 0' has been replaced with .hide(); I did this because opacity and ie are not friends and it would most likely cause a problems down the line.
Cheers!
A view in my web app has a table which may be extremely long, so I wrapped it in a div with overflow: auto; max-height: 400px; so users can scroll through it while keeping the other controls on the page visible.
I want to use a bit of JavaScript to dynamically adjust the max-height CSS property so the div stretches to the bottom of the browser window. How can I determine this value? jQuery solutions are fine.
The table doesn't start at the top of the page, so I can't just set the height to 100%.
Something like this would work I think:
var topOfDiv = $('#divID').offset().top;
var bottomOfVisibleWindow = $(window).height();
$('#divID').css('max-height', bottomOfVisibleWindow - topOfDiv - 100);
I had a very similar problem, except in my case I had a dynamic pop-up element (a jQuery UI Multiselect widget), to which I wanted to apply a max-height so that it never went below the bottom of the page. Using offset().top on the target element wasn't enough, because that returns the x coordinate relative to the document, and not the vertical scroll-position of the page.
So if the user scrolls down the page, the offset().top won't provide an accurate description of where they are relative to the bottom of the window - you'll need to determine the scroll position of the page.
var scrollPosition = $('body').scrollTop();
var elementOffset = $('#element').offset().top;
var elementDistance = (elementOffset - scrollPosition);
var windowHeight = $(window).height();
$('#element').css({'max-height': windowHeight - elementDistance});
window.innerHeight gives you the visible height of the entire window. I did something almost identical recently so I'm pretty sure that's what you need. :) Let me know, though.
EDIT: You'll still need the Y-value of the overflowed div which you can get by document.getElementById("some_div_id").offsetHeight, seeing that .style.top won't give you a result unless it has been specifically set to a point via CSS. .offsetHeight should give you the correct 'top' value.
Then it's just a matter of setting the size of the table to the window height, minus the 'top' value of the div, minus whatever arbitrary wiggle room you want for other content.
something like max-height: 100%, but not to forget the html and body height 100%.