I have a web application that sizes the html and body elements at 100% width and height and puts overflow: scroll on body to create full screen slide elements. I'm using jQuery Waypoints for sticky navigation and to determine which slide is currently visible.
Since the body element is technically the one scrolling, I set context: body. This works as expected in Firefox, but the waypoints won't fire in Chrome or Safari.
I can get the waypoints to fire by manually calling $.waypoints('refresh'); after scrolling to a point where they should have fired, but calling this after every scroll event seems like a very cumbersome solution.
$('body').on('scroll', function(){$.waypoints('refresh');}) —it works, but sure isn't pretty.
I'm assuming this has something to do with how each browser interprets the DOM, but is there a known reason why Chrome and Safari wouldn't play nicely with waypoints in scrollable elements?
I'm looking for one of two things, either what I've done backwards in my use of waypoints, or what the underlying issue is so I can fix it and make waypoints work properly for everyone.
For the record (and before anyone asks), I've done my research and this isn't an issue with fixed elements.
Edit: finally got a CodePen built for this. Take a look.
Remove overflow:hidden from html. Unfortunately looks like this is required - I hope it doesn't break your layout.
Next, you'll need #nav.stuck { position: fixed; } instead of absolute for a sticky header.
Use this js:
$('#nav').waypoint(function(direction) {
if (direction == 'down') {
$(this).addClass('stuck');
} if (direction == 'up') {
$(this).removeClass('stuck');
};
});
That works for me - see http://codepen.io/anon/pen/GgsdH
How about this?
$(window).load(function() {
$('#myheader').waypoint('sticky');
});
… instead of this:
$(document).ready(function(){
$('#myheader').waypoint('sticky');
});
This is of course stupid if you have a huge amount of images to load, but this solution saved my day.
Try min-height: 100% on body and html instead of height, if appropriate for your layout.
Delete overflows and heights in html and body, also context is not needed. Worked for me.
Related
My problem is very similar to this one except the thing that fixed element may change his height dynamically during application lifecycle (other data, viewport change, etc... ).
I'm using setInterval() function with 100ms interval to update offset of content element depending on header height.
jQuery(function($){
setInterval(function(){
$('article').css('padding-top', $('header').outerHeight());
}, 100)
});
Here is jsfiddle for it (change the width of the resulted page to see how it works).
For user experience it looks just great, but I'm curious is there a better way?
What are the disadvantages of this approach?
The major disadvantage is that you consume CPU every 100ms. And it doesn't do anything most of the time.
There is a better way. Just emit an event after the fixed element changes height and bind your css adjusment to it. Something like:
$(document).trigger('my_element_changed_height');
wherever the height changes and
$(document).on('my_element_changed_height', function() {
$('article').css('padding-top', $('header').outerHeight());
});
I suppose you can use jquery.ba-resize.js library. Here is a link: http://benalman.com/projects/jquery-resize-plugin
It allows you to use resize event on any DOM element. But if I'm not mistaken this library uses setTimeout functionality and I'm not sure that's better in performance.
UPDATE: time goes and web evolve, position: sticky
header{
position: sticky;
}
Old Answer:
Here is another solution that comes to my head. I was thinking how would be great have such position : fixed-relative :) (That fixed on viewport but doesn't desapear from normal flow) And here is an idea how to emulate this behaviour. Set header element position as relative.
header{
position: relative;
}
And add some listner to scroll event.
jQuery(function($){
$(window).scroll(function(){
$('header').css('top',$(this).scrollTop() );
});
});
It's much pretty than have infinity loop with setInterval or trigger some event across your application.
Unfortunately it will not work on most touch devices
I created a dynamic table that scrolls left and right, has resizable columns, has a fixed header, etc. This table works great on EVERY browser I've tried. Even IE8 looks good (missing features, but still good).
This issue arises when I try to view the table in Safari 7.0.4 on my Macbook.
Attached is what is should look like (the fixed header is on the bottom for demonstration purposes):
when you scroll, the fixed header, body, and fixed scrollbar all are connected via some jQuery scrollLeft() functions (scroll one, scroll all):
var tableHeaderSpace = $('.table-full-wrap-space'),
tableHeader = $('.table-full-wrap-header'),
tableBody = $('.table-full-wrap-body'),
tableScroll = $('.table-full-wrap-scroll');
tableScroll.bind('scroll', function() {
tableHeader.scrollLeft(tableScroll.scrollLeft());
tableBody.scrollLeft(tableScroll.scrollLeft());
});
tableHeader.bind('scroll', function() {
tableScroll.scrollLeft(tableHeader.scrollLeft());
tableBody.scrollLeft(tableHeader.scrollLeft());
});
tableBody.bind('scroll', function() {
tableScroll.scrollLeft(tableBody.scrollLeft());
tableHeader.scrollLeft(tableBody.scrollLeft());
});
$(window).bind("scroll", function() {
var tableHeaderOffset = tableHeaderSpace.offset().top;
if (this.pageYOffset >= tableHeaderOffset) {
tableHeader.addClass('isFixed');
} else {
tableHeader.removeClass('isFixed');
}
});
Again, this works great...but as you scroll right a bit more, the browser starts duplicating content within that fixed header:
The issue is is that no 'actual' content is being duplicated - this is some sort of browser fragmenting that is showing duplicates - without adding elements in the DOM.
The next picture is the browser doing some more "magic". at certain points in horizontal scrolling, the whole fixed header's colors gets inverted:
I wasn't able to get a snapshot of it, but it also once duplicated the "record count" bar below it.
Anyone have any ideas what's going on here? I tried to duplicate this in jsFiddle but no dice. From that, I would assume that this is an issue with my code, but the results are only with ONE specific browser on mac (safari), and it is doing some STRANGE stuff.
Last note - since I can't replicate this in jsFiddle, i'm not sure how I could report this to Apple (the working (or 'broken') example is proprietary and I can't give out access to it).
EDIT:
here's the jsfiddle where I tried to duplicate the issue (very rough - but it's functional):
jsFiddle Duplication Attempt
so - I knew this wouldn't be a hot topic question, but I thought I would still give it a go ahead.
as for the answer, I found some old table css that was overlapping my new stuff - which in turn was somehow flipping safari out so bad that it was fragmenting it.
previous old code: background: transparent;
new code: background: #fff;
This doesn't make sense to me - but until someone else comes up with an hypothesis, I'll mark this as the answer.
now my number-one contender for worst browser: safari - look out, IE.
This may come as a huge surprise to some people but I am having an issue with the IE browser when I am using the $(window).scroll method.
My goal:
I would like to have the menu located on the left retain it's position until the scroll reaches > y value. It will then fix itself to the top of the page until the scroll returns to a < y value.
My error:
Everything seems just fine in Chrome and Firefox but when I go to Internet Explorer it would seem the browser is moving #scroller every time the scroll value changes, this is causing a moving/flickering event.
If someone could point me to a resource or give me a workaround for this I would be very grateful!
Here is a fiddle:
http://jsfiddle.net/CampbeII/nLK7j/
Here is a link to the site in dev:
http://squ4reone.com/domains/ottawakaraoke/Squ4reone/responsive/index.php
My script:
$(window).scroll(function () {
var navigation = $(window).scrollTop();
if (navigation > 400) {
$('#scroller').css('top',navigation - 220);
} else {
$('#scroller').css('top',183);
$('#scroller').css('position','relative');
}
});
You might want to take a look at the jQuery Waypoints plugin, it lets you do sticky elements like this and a lot more.
If you want to stick with your current method, like the other answers have indicated you should toggle fixed positioning instead of updating the .top attribute in every scroll event. However, I would also introduce a flag to track whether or not it is currently stuck, this way you are only updating the position and top attributes when it actually make the transition instead of every scroll event. Interacting with the DOM is computationally expensive, this will take a lot of load off of the layout engine and should make things even smoother.
http://jsfiddle.net/WYNcj/6/
$(function () {
var stuck = false,
stickAt = $('#scroller').offset().top;
$(window).scroll(function () {
var scrollTop = $(window).scrollTop();
if (!stuck && scrollTop > stickAt) {
$('#scroller').css('top', 0);
$('#scroller').css('position','fixed');
stuck = true;
} else if (stuck && scrollTop < stickAt) {
$('#scroller').css('top', stickAt);
$('#scroller').css('position','absolute');
stuck = false;
}
});
});
Update
Switching the #scroller from relative to fixed removes it from the normal flow of the page, this can have unintended consequences for the layout as it re-flows without the missing block. If you change #scroller to use an absolute position it will be removed from the normal flow and will no longer cause these side-effects. I've updated the above example and the linked jsfiddle to reflect the changes to the JS/CSS.
I also changed the way that stickAt is calculated as well, it uses .offset() to find the exact position of the top of #scoller instead of relying on the CSS top value.
Instead of setting the top distance at each scroll event, please consider only switching between a fixed position and an absolute or relative position.All browsers will appreciate and Especially IE.
So you still listen to scroll but you now keep a state flag out of the scroll handler and simply evaluate if it has to switch between display types.
That is so much more optimized and IE likes it.
I can get flickers in Chrome as well if I scroll very quickly. Instead of updating the top position on scroll, instead used the fixed position for your element once the page has scrolled below the threshold. Take a look at the updated fiddle: http://jsfiddle.net/nLK7j/2/
I have an element where I'm using the Twitter Bootstrap Affix plugin. If the window gets vertically resized to the point where it is smaller than the height of the item, I'd like to remove the affix functionality from the element since you wouldn't be able to see all of it in the window.
So far I've tried this in the console just to see if it can be removed, but it doesn't seem to be working.
$("#myElement")
.removeClass("affix affix-top affix-bottom")
.removeData("affix");
$(window)
.off("scroll.affix.data-api, click.affix.data-api");
Maybe I'm going about this the wrong way? How Can I programmatically remove the affix from an element that already had it applied?
I ended up going for a mostly CSS solution, similar to what #Marcin Skórzewski suggested.
This just adds a new class when the height of the window is shorter than the height of the element.
var sizeTimer;
$(window).on("resize", function() {
clearTimeout(sizeTimer);
sizeTimer = setTimeout(function() {
var isWindowTallEnough = $overviewContainer.height() + 20 < $(window).height();
if (isWindowTallEnough) {
$overviewContainer.removeClass("affix-force-top");
} else {
$overviewContainer.addClass("affix-force-top");
}
}, 300);
});
And then in CSS, this class just gets added:
.affix-force-top{
position:absolute !important;
top:auto !important;
bottom:auto !important;
}
EDIT
For bootstrap 3, this seems to be effective:
$(window).off('.affix');
$("#my-element")
.removeClass("affix affix-top affix-bottom")
.removeData("bs.affix");
Deprecated: Answer refers to Twitter Bootstrap v2. Current version is v4.
There are few options to try.
Use function for data-offset-top. Normally, you use the integer value, for number of scrolled pixels to fix the element. According to documentation you can use the JS function, that will calculate the offset dynamically. In this case you can make your function to return different number depending on the conditions of your choice.
Use media query to override affix CSS rule for small window (eg. height 200px or less).
I think, the second variant should be suitable for you. Something like:
#media (max-height: 200px) {
.affix {
position: static;
}
}
If you would provide jsfiddle for your problem others could try to actually solve it, instead of giving just theoretical suggestion, that may or may not work.
PS. Bootstrap's navbar component uses media query for max-width to disable fixed style for small devices. It is good to do that not just because the screen size is to small for navbar, but in mobile devices position: fixed; CSS works really ugly. Take w look at navbar inside the bootstrap-responsive.css file.
Your $(window).off is close, according to #fat (author of bootstrap-affix.js) you can disable the plugin like this:
$(window).off('.affix');
That will disable the affix plugin.
See: https://github.com/twitter/bootstrap/issues/5870
On line 1890 of bootstrap is a conditional for whether the default action should be prevented. This allows your to listen for events and if some condition is met, prevent the affix from happening.
line 1890 from bootstrap:
if (e.isDefaultPrevented()) return
Example:
$('someselector')
.affix()
.on(
'affix.bs.affix affix-top.bs.affix affix-bottom.bs.affix'
, function(evt){
if(/* some condition */){
evt.preventDefault();
}
}
);
Even though this was answered, I just wanted to give my solution for this in case someone ran into a similar situation as mine.
I modified the offset top option to a ridiculous number that would never get scrolled to. This made it so I did not have to do $(window).off('.affix'); and disable affix for everything.
$('#element-id').data('bs.affix').options.offset.top = 1000000000;
Not too long ago I asked about setting up a DIV which scrolls with the rest of the page. Post can be found here.
I've set this up, using the following code:
JS..
jQuery(function ($) {
var el = $('#sidebar'),
pos = el.position().top;
alert(pos);
$(window).scroll(function() {
el.toggleClass('fixed', $(this).scrollTop() >= pos);
});
});
CSS..
/* profile sidebar */
#sidebar>div{ width: 300px; margin-top: 10px; }
#sidebar.fixed>div{position:fixed;top:0}
A copy of the page can be found here. The alert was just some debugging.
The problem is, when you scroll a small amount, #sidebar suddenly appears at the very top of the page. In addition, sometimes as you scroll further down, the sidebar appears - and sometimes it doesn't.
Any idea what might be causing such seemingly random functionality?
I'm still trying to figure out why it works in the first place in the jsfiddle example, but anyway, I know how to fix it:
$(window).scroll(function() {
if($(this).scrollTop() >= pos){
el.addClass('fixed');
}else{
el.removeClass('fixed');
}
});
I tested this by unbinding the event you had and replacing it with this code. It seemed to work fine.
The reason I can't understand why it works in the example: toggleClass should be constantly adding and removing "fixed" if you have scrolled enough, because the conditional is true (true here means whether to toggle). The constant adding and removing of the fixed class causes the jumpy behavior.
You can watch this on your page: open up some dev tools (firegubg or Chrome) and watch what happens to your sidebar element.
[UPDATE]
Actually, I misread the docs. True means the class should be added (I don't think the docs are very clear though). Thus... the only way I could explain this is if #dunc was running jQuery v1.2 and the switch was getting ignored completely...