I want to have a div be fixed at the bottom of the window when the window is taller than the content height. If the content height is taller than the window height, I want the div position to remain relative.
I currently have this mostly working, however I don't want the div to overlap the content at all. I tried various forms of below, but still not working:
var body = content+bottomBar
if (body > viewport) {
$(".bottom-bar").css({
'position':'relative'
});
} else {
$(".bottom-bar").css({
'position': 'fixed'
})
}
I also am having trouble getting the window.resize to work.
Any help would be appreciated!
http://jsfiddle.net/no05x1vx/1/
Referring to the jsfiddle linked by the OP, here are a few changes to make the code work as expected, please see the comments:
var content = $(".content").height()
var viewport = $(window).height();
// Use innerHeight here as the bottom-bar div has height 0 and padding 60px
// .height() would return 0
var bottomBar = $(".bottom-bar").innerHeight();
var body = parseInt(content)+parseInt(bottomBar)
$(window).on('resize', function(){
// Get new viewport height
viewport = $(window).height();
if (content > (viewport-bottomBar) ) {
$(".bottom-bar").css({
'position':'relative'
});
} else {
$(".bottom-bar").css({
'position': 'fixed'
})
}
});
// Trigger resize after page load (to avoid repeated code)
$(document).ready(function(){
$(window).resize();
});
In IOS8 Safari there is a new bug with position fixed.
If you focus a textarea that is in a fixed panel, safari will scroll you to the bottom of the page.
This makes all sorts of UIs impossible to work with, since you have no way of entering text into textareas without scrolling your page all the way down and losing your place.
Is there any way to workaround this bug cleanly?
#a {
height: 10000px;
background: linear-gradient(red, blue);
}
#b {
position: fixed;
bottom: 20px;
left: 10%;
width: 100%;
height: 300px;
}
textarea {
width: 80%;
height: 300px;
}
<html>
<body>
<div id="a"></div>
<div id="b"><textarea></textarea></div>
</body>
</html>
Based on this good analysis of this issue, I've used this in html and body elements in css:
html,body{
-webkit-overflow-scrolling : touch !important;
overflow: auto !important;
height: 100% !important;
}
I think it's working great for me.
The best solution I could come up with is to switch to using position: absolute; on focus and calculating the position it was at when it was using position: fixed;. The trick is that the focus event fires too late, so touchstart must be used.
The solution in this answer mimics the correct behavior we had in iOS 7 very closely.
Requirements:
The body element must have positioning in order to ensure proper positioning when the element switches to absolute positioning.
body {
position: relative;
}
The Code (Live Example):
The following code is a basic example for the provided test-case, and can be adapted for your specific use-case.
//Get the fixed element, and the input element it contains.
var fixed_el = document.getElementById('b');
var input_el = document.querySelector('textarea');
//Listen for touchstart, focus will fire too late.
input_el.addEventListener('touchstart', function() {
//If using a non-px value, you will have to get clever, or just use 0 and live with the temporary jump.
var bottom = parseFloat(window.getComputedStyle(fixed_el).bottom);
//Switch to position absolute.
fixed_el.style.position = 'absolute';
fixed_el.style.bottom = (document.height - (window.scrollY + window.innerHeight) + bottom) + 'px';
//Switch back when focus is lost.
function blured() {
fixed_el.style.position = '';
fixed_el.style.bottom = '';
input_el.removeEventListener('blur', blured);
}
input_el.addEventListener('blur', blured);
});
Here is the same code without the hack for comparison.
Caveat:
If the position: fixed; element has any other parent elements with positioning besides body, switching to position: absolute; may have unexpected behavior. Due to the nature of position: fixed; this is probably not a major issue, since nesting such elements is not common.
Recommendations:
While the use of the touchstart event will filter out most desktop environments, you will probably want to use user-agent sniffing so that this code will only run for the broken iOS 8, and not other devices such as Android and older iOS versions. Unfortunately, we don't yet know when Apple will fix this issue in iOS, but I would be surprised if it is not fixed in the next major version.
I found a method that works without the need to change to position absolute!
Full uncommented code
var scrollPos = $(document).scrollTop();
$(window).scroll(function(){
scrollPos = $(document).scrollTop();
});
var savedScrollPos = scrollPos;
function is_iOS() {
var iDevices = [
'iPad Simulator',
'iPhone Simulator',
'iPod Simulator',
'iPad',
'iPhone',
'iPod'
];
while (iDevices.length) {
if (navigator.platform === iDevices.pop()){ return true; }
}
return false;
}
$('input[type=text]').on('touchstart', function(){
if (is_iOS()){
savedScrollPos = scrollPos;
$('body').css({
position: 'relative',
top: -scrollPos
});
$('html').css('overflow','hidden');
}
})
.blur(function(){
if (is_iOS()){
$('body, html').removeAttr('style');
$(document).scrollTop(savedScrollPos);
}
});
Breaking it down
First you need to have the fixed input field toward the top of the page in the HTML (it's a fixed element so it should semantically make sense to have it near the top anyway):
<!DOCTYPE HTML>
<html>
<head>
<title>Untitled</title>
</head>
<body>
<form class="fixed-element">
<input class="thing-causing-the-issue" type="text" />
</form>
<div class="everything-else">(content)</div>
</body>
</html>
Then you need to save the current scroll position into global variables:
//Always know the current scroll position
var scrollPos = $(document).scrollTop();
$(window).scroll(function(){
scrollPos = $(document).scrollTop();
});
//need to be able to save current scroll pos while keeping actual scroll pos up to date
var savedScrollPos = scrollPos;
Then you need a way to detect iOS devices so it doesn't affect things that don't need the fix (function taken from https://stackoverflow.com/a/9039885/1611058)
//function for testing if it is an iOS device
function is_iOS() {
var iDevices = [
'iPad Simulator',
'iPhone Simulator',
'iPod Simulator',
'iPad',
'iPhone',
'iPod'
];
while (iDevices.length) {
if (navigator.platform === iDevices.pop()){ return true; }
}
return false;
}
Now that we have everything we need, here is the fix :)
//when user touches the input
$('input[type=text]').on('touchstart', function(){
//only fire code if it's an iOS device
if (is_iOS()){
//set savedScrollPos to the current scroll position
savedScrollPos = scrollPos;
//shift the body up a number of pixels equal to the current scroll position
$('body').css({
position: 'relative',
top: -scrollPos
});
//Hide all content outside of the top of the visible area
//this essentially chops off the body at the position you are scrolled to so the browser can't scroll up any higher
$('html').css('overflow','hidden');
}
})
//when the user is done and removes focus from the input field
.blur(function(){
//checks if it is an iOS device
if (is_iOS()){
//Removes the custom styling from the body and html attribute
$('body, html').removeAttr('style');
//instantly scrolls the page back down to where you were when you clicked on input field
$(document).scrollTop(savedScrollPos);
}
});
I was able to fix this for select inputs by adding an event listener to the necessary select elements, then scrolling by an offset of one pixel when the select in question gains focus.
This isn't necessarily a good solution, but it's much simpler and more reliable than the other answers I've seen here. The browser seems to re-render/re-calculate the position: fixed; attribute based on the offset supplied in the window.scrollBy() function.
document.querySelector(".someSelect select").on("focus", function() {window.scrollBy(0, 1)});
Much like Mark Ryan Sallee suggested, I found that dynamically changing the height and overflow of my background element is the key - this gives Safari nothing to scroll to.
So after the modal's opening animation finishes, change the background's styling:
$('body > #your-background-element').css({
'overflow': 'hidden',
'height': 0
});
When you close the modal change it back:
$('body > #your-background-element').css({
'overflow': 'auto',
'height': 'auto'
});
While other answers are useful in simpler contexts, my DOM was too complicated (thanks SharePoint) to use the absolute/fixed position swap.
Cleanly? no.
I recently had this problem myself with a fixed search field in a sticky header, the best you can do at the moment is keep the scroll position in a variable at all times and upon selection make the fixed element's position absolute instead of fixed with a top position based on the document's scroll position.
This is however very ugly and still results in some strange back and forth scrolling before landing on the right place, but it is the closest I could get.
Any other solution would involve overriding the default scroll mechanics of the browser.
Haven't dealt with this particular bug, but maybe put an overflow: hidden; on the body when the text area is visible (or just active, depending on your design). This may have the effect of not giving the browser anywhere "down" to scroll to.
A possible solution would be to replace the input field.
Monitor click events on a div
focus a hidden input field to render the keyboard
replicate the content of the hidden input field into the fake input field
function focus() {
$('#hiddeninput').focus();
}
$(document.body).load(focus);
$('.fakeinput').bind("click",function() {
focus();
});
$("#hiddeninput").bind("keyup blur", function (){
$('.fakeinput .placeholder').html(this.value);
});
#hiddeninput {
position:fixed;
top:0;left:-100vw;
opacity:0;
height:0px;
width:0;
}
#hiddeninput:focus{
outline:none;
}
.fakeinput {
width:80vw;
margin:15px auto;
height:38px;
border:1px solid #000;
color:#000;
font-size:18px;
padding:12px 15px 10px;
display:block;
overflow:hidden;
}
.placeholder {
opacity:0.6;
vertical-align:middle;
}
<input type="text" id="hiddeninput"></input>
<div class="fakeinput">
<span class="placeholder">First Name</span>
</div>
codepen
None of these solutions worked for me because my DOM is complicated and I have dynamic infinite scroll pages, so I had to create my own.
Background: I am using a fixed header and an element further down that sticks below it once the user scrolls that far down. This element has a search input field. In addition, I have dynamic pages added during forward and backwards scroll.
Problem: In iOS, anytime the user clicked on the input in the fixed element, the browser would scroll all the way to the top of the page. This not only caused undesired behavior, it also triggered my dynamic page add at the top of the page.
Expected Solution: No scroll in iOS (none at all) when the user clicks on the input in the sticky element.
Solution:
/*Returns a function, that, as long as it continues to be invoked, will not
be triggered. The function will be called after it stops being called for
N milliseconds. If `immediate` is passed, trigger the function on the
leading edge, instead of the trailing.*/
function debounce(func, wait, immediate) {
var timeout;
return function () {
var context = this, args = arguments;
var later = function () {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
function is_iOS() {
var iDevices = [
'iPad Simulator',
'iPhone Simulator',
'iPod Simulator',
'iPad',
'iPhone',
'iPod'
];
while (iDevices.length) {
if (navigator.platform === iDevices.pop()) { return true; }
}
return false;
}
$(document).on("scrollstop", debounce(function () {
//console.log("Stopped scrolling!");
if (is_iOS()) {
var yScrollPos = $(document).scrollTop();
if (yScrollPos > 200) { //200 here to offset my fixed header (50px) and top banner (150px)
$('#searchBarDiv').css('position', 'absolute');
$('#searchBarDiv').css('top', yScrollPos + 50 + 'px'); //50 for fixed header
}
else {
$('#searchBarDiv').css('position', 'inherit');
}
}
},250,true));
$(document).on("scrollstart", debounce(function () {
//console.log("Started scrolling!");
if (is_iOS()) {
var yScrollPos = $(document).scrollTop();
if (yScrollPos > 200) { //200 here to offset my fixed header (50px) and top banner (150px)
$('#searchBarDiv').css('position', 'fixed');
$('#searchBarDiv').css('width', '100%');
$('#searchBarDiv').css('top', '50px'); //50 for fixed header
}
}
},250,true));
Requirements: JQuery mobile is required for the startsroll and stopscroll functions to work.
Debounce is included to smooth out any lag created by the sticky element.
Tested in iOS10.
I just jumped over something like this yesterday by setting height of #a to max visible height (body height was in my case) when #b is visible
ex:
<script>
document.querySelector('#b').addEventListener('focus', function () {
document.querySelector('#a').style.height = document.body.clientHeight;
})
</script>
ps: sorry for late example, just noticed it was needed.
This is now fixed in iOS 10.3!
Hacks should no longer be needed.
I had the issue, below lines of code resolved it for me -
html{
overflow: scroll;
-webkit-overflow-scrolling: touch;
}
I had this working with no problems for the entire build of the site. Then, the day I was supposed to launch, the sticky menu stopped working right. The menu is supposed to start at the bottom, scroll to the top, then stick (position: fixed).
Now, it scrolls about 10px and then jumps to the top. Why is the scrollTop distance not calculating correctly?
Live site at [site no longer exists]
Here's the code for the sticky menu. I'm also using JS to set min-height of divs to window height, but haven't included that code here.
$(function(){
var stickyRibbonTop = $('#wrapper-wcf53badf7ebadf7').offset().top;
$(window).scroll(function(){
if( $(window).scrollTop() > stickyRibbonTop ) {
$('#wrapper-wcf53badf7ebadf7').css({position: 'fixed', top: '0px', 'background-image':'url(http://amarshall.360zen.com/wp-content/uploads/2014/07/menu-fade-background2.png)'});
$('#block-bcf53bf14093931c').css({display: 'block'});
} else {
$('#wrapper-wcf53badf7ebadf7').css({position: 'static', top: '0px','background-image':'none'});
$('#block-bcf53bf14093931c').css({display: 'none'});
}
});
});
Thanks in advance for any help! I'm not a JS or jQuery expert yet, so any suggestions for cleaning things up would be appreciated.
NOTE: The site is built on WordPress, so no-conflict mode is in effect.
I think you are initialising the sticky menu function before you set the min-height of $('big-div').
On page load, the menu starts at 54px from the top, and so when you store the offset().top value as stickyRibbonTop, it is stored at 54px. Then on your scroll event you are comparing against this.
Try setting the min-height of the divs first in your code, then run this same script afterwards. The value of stickyRibbonTop should then be correct.
Bear in mind that you will need to reset stickyRibbonTop every time the window.height() is updated, so you should probably make this sticky menu function a named function and call it at the end of the wrapper_height function. something like this:
function stickyNav() {
var stickyRibbonTop = $('#wrapper-wcf53badf7ebadf7').offset().top;
$(window).unbind('scroll', scrollEvent);
$(window).on('scroll', stickyRibbonTop, scrollEvent);
};
function scrollEvent(event) {
var stickyRibbonTop = event.data;
if ($(window).scrollTop() > stickyRibbonTop) {
$('#wrapper-wcf53badf7ebadf7').css({ position: 'fixed', top: '0px', 'background-image': 'url(http://www.adammarshalltherapy.com/wp-content/uploads/2014/07/menu-fade-background2.png)' });
$('#block-bcf53bf14093931c').css({ display: 'block' });
}
else {
$('#wrapper-wcf53badf7ebadf7').css({ position: 'static', top: '0px', 'background-image': 'none' });
$('#block-bcf53bf14093931c').css({ display: 'none' });
}
};
function wrapper_height() {
var height = $(window).height();
var wrapperheight = height - 75;
wrapperheight = parseInt(wrapperheight) + 'px';
$(".bigDiv").css('min-height', wrapperheight);
$("#wrapper-wcf53bad125d7d9a").css('height', wrapperheight);
stickyNav();
}
$(function () {
wrapper_height();
$(window).bind('resize', wrapper_height);
});
I want to move a bootstrap "navbar" header off the page when the navbar's position on the page reaches 400px.
If you look at this jsfiddle, I want the .navbar to leave the top of the page when the blue block begins (at 400px). The navbar would stay on the page through the red div, then leave the top of the page when the blue block begins.
I have tried to do this with scrollorama (jquery plugin), but have not had success yet:
$(document).ready(function() {
var scrollorama = $.scrollorama({ blocks:'.scrollblock' });
scrollorama.animate('#fly-in',{ delay: 400, duration: 300, property:'top', start:-1400, end:0 });
});
I am looking for either a pure javascript solution, or with the scrollorama plugin. Thanks for any ideas!
I'm not very familiar with the scrollorama plugin but you can get this done simply with jQuery via the scroll() event:
$(window).scroll(function () {
var winTop = $(this).scrollTop();
var redHeight = $('#red').height();
if (winTop >= redHeight) {
/*if the scroll reaches the bottom of the red <div> make set '#move' element
position to absolute so it will move up with the red <div> */
$('#move').css({
'position': 'absolute',
'bottom': '0px',
'top': 'auto'
});
} else {
//else revert '#move' position back to fixed
$('#move').css({
'position': 'fixed',
'bottom': 'auto',
'top': '0px'
});
}
});
See this updated jsfiddle: jsfiddle.net/52VtD/1945/
Edit: make it so that the navbar disappears at the same point that the red div ends
I noticed that earlier as well but I'm having trouble locating the problem so I removed your imported style sheet and created a basic style for the navbar. To get the navbar disappears at the same point that the red div ends you need to subtract the navbar's height to the condition:
if (winTop >= redHeight - $('#move').height()) {
I've also restructured the markup to get this working properly. I've nested the navbar inside the red div and set the red div's position to relative.
See this jsfiddle: jsfiddle.net/52VtD/1981/
listen to the scroll event using jquery to find if the navbar overlaps with the red or blue div
Assign a class to the red div
<div class="redDiv" style="height:400px; background-color: red;">
Then listen to the scroll event and use the getBoundingClientRect() to find the co-ord of the navbar and the div in the view port to check for overlap
$(document).scroll(function(event)
{
var rect1 = $('.navbar').get(0).getBoundingClientRect();
var rect2 = $('.redDiv').get(0).getBoundingClientRect();
var overlap = !(rect1.right < rect2.left ||
rect1.left > rect2.right ||
rect1.bottom < rect2.top ||
rect1.top > rect2.bottom)
if(!overlap)
{
if ( $(".navbar").is(":visible") ) {
$('.navbar').hide();
}
}
else
{
if ( !$(".navbar").is(":visible") ) {
$('.navbar').show();
}
}
});
Here is a working fiddle
http://jsfiddle.net/SXzf7/
I know this question has been asked before, but I'm pretty sure after checking them out, that none of the navigation bars where built like this one.
I'm basically having trouble making the navigation bar "seamlessly" switch to a fixed position at the top of the screen after scrolling past its original position, then back again.
I have included the code, and an example here: http://jsfiddle.net/r2a6U/
Here is the actual function which makes the div switch to fixed position mode:
var navPos = $('#navContainer').offset().top;
$(window).scroll(function(){
var fixIT = $(this).scrollTop() >= navPos;
var setPos = fixIT ? 'fixed' : 'relative' ;
var setTop = fixIT ? '0' : '600' ;
$('#navContainer').css({position: setPos});
$('#navContainer').css({'top': setTop});
});
Any help would be much appreciated.
Cheers
You can fix your issue to remove the styles instead of setting them to relative and 600px. I suggest you add/remove a class in JavaScript which will then apply the fixed CSS though. You will end up with much nicer and cleaner JavaScript.
Also make sure you center #navContainer properly when it's fixed.
jsFiddle
CSS
#navContainer.fixIT {
position:fixed;
top:0;
/* these will ensure it is centered so it doesn't jump to the side*/
left:0;
right:0;
text-align:center;
}
JS
var navPos = $('#navContainer').offset().top;
$(window).scroll(function(){
var fixIT = $(this).scrollTop() >= navPos;
if (fixIT)
$('#navContainer').addClass('fixIT');
else
$('#navContainer').removeClass('fixIT');
});
Fix it in here: jsFiddle
Only a small script update:
var navPos = $('#navContainer').offset().top;
$(window).scroll(function(){
var navContainer = $('#navContainer');
if( $(this).scrollTop() >= navPos ) {
// make it fixed to the top
$('#navContainer').css({ 'position': 'fixed', 'top': 0 });
} else {
// restore to orignal position
$('#navContainer').css({ 'position': 'relative', 'top': 600 });
}
});