If we make a simple test case like:
document.documentElement.addEventListener('scroll', function() {
console.log(document.documentElement.scrollTop);
});
And then go and scroll using the scrollbar by clicking the track, or by using PageDown/PageUp, then we can see that we only get one event at the end of the scrolling animation.
Now theoretically I could fix some of that behaviour by simulating the scroll events. Example code with jQuery and Underscore:
$(function () {
var $document = $(document), until = 0;
var throttleScroll = _.throttle(function () {
$document.scroll();
if (+new Date < until) {
setTimeout(throttleScroll, 50);
}
}, 50);
$document.keydown(function (evt) {
if (evt.which === 33 || evt.which === 34) {
until = +new Date + 300;
throttleScroll();
}
});
});
But it still does not work. We only get scroll events with the original scrollTop and the destination scrollTop, no values in between.
If then go and console.log(document.documentElement.scrollTop) every 10ms, then we can see that IE just does not update the scrollTop as it scrolls.
This is very frustrating if we want to "pin" something to the scroll position. It gets jerky with IE.
I did not observe this behaviour on any other browser, and did not test with previous IE versions.
If anyone has found a way to fix IE's behaviour (maybe there's a magic CSS to turn off smooth scrolling in IE 11?) then I would very much like to hear about it!
Thanks :-)
You said: "If anyone has found a way to fix IE's behaviour (maybe there's a magic CSS to turn off smooth scrolling in IE 11?) then I would very much like to hear about it!"
This does not turn it off, but here is what I use to resolve the smooth scroll issue in ie with Fixed elements.
if(navigator.userAgent.match(/Trident\/7\./)) {
$('body').on("mousewheel", function ( event ) {
event.preventDefault();
var wd = event.wheelDelta;
var csp = window.pageYOffset;
window.scrollTo(0, csp - wd);
});
}
The issue you're describing is limited to instances of Internet Explorer 11 running on Windows 7. This problem doesn't affect the platform upon which IE 11 was born, Windows 8.1. It seems as though IE 11 on Windows 7 falls into a similar category as other scrolling implementations mentioned above. It's not ideal, but it's something we have to work with/around for the time being.
I'm going to continue looking into this; in fact, just dug a Windows 7 machine out of the closet to setup first thing in the morning so as to investigate further. While we cannot address this head-on, perhaps, maybe, there's a way we can circumvent the problem itself.
To be continued.
As a crazy last resort (seems not so crazy actually if the issue is critical) - maybe turn off native scrolling completely and use custom scrolling (i.e. http://jscrollpane.kelvinluck.com/)? And bind onscroll stuff to its custom events: http://jscrollpane.kelvinluck.com/events.html
Looks like there's a post on IE and forcing a screen "paint" to help with drag-drop. Seems the opposite of most performance efforts but might work? https://stackoverflow.com/a/12395506/906526 (code from https://stackoverflow.com/users/315083/george)
function cleanDisplay() {
var c = document.createElement('div');
c.innerHTML = 'x';
c.style.visibility = 'hidden';
c.style.height = '1px';
document.body.insertBefore(c, document.body.firstChild);
window.setTimeout(function() {document.body.removeChild(c)}, 1);
}
You might try CSS animations so the browser handles animation/ transition. Eg applying a show/ hide class on scroll and, CSS animation.
.hide-remove {
-webkit-animation: bounceIn 2.5s;
animation: bounceIn 2.5s;
}
.hide-add {
-webkit-animation: flipOutX 2.5s;
animation: flipOutX 2.5s;
display: block !important;
}
If not having a browser handle animation (with creative css), keyframes and JS performance might offer leads. As a plus, I've seen several sites with navigation bars that "reappear" after scroll end.
Related
I am losing my mind with this seemingly simple code.
I have created a sticky menu for a few sites and they all share the same problem. On iOS devices, at least the iPhone 6 with up to date iOS, the menu jumps into its fixed position too early. It's as if iOS miscalculates the offset for the element and runs the function too early. Though for the life of me I can't figure out how or why. On desktop it works fine. On Android it works fine. Please help!! The site is [DreaD Illustrations][1]. I have tried everything I can think of and find on the internet. Also, I noticed, it calculates incorrectly on initial load, but when you scroll down and scroll back up it seems to work. Help! The code is below.
var navBar = jQuery("nav.site-navigation.main-navigation.primary.mobile-navigation");
var topofNav = navBar.offset().top;
stickyDiv = "being-sticky";
mahMain = jQuery('#main').outerWidth();
jQuery(window).load(function(){
jQuery(window).on('scroll', function() {
if (jQuery(document).scrollTop() > topofNav) {
navBar.addClass(stickyDiv);
navBar.outerWidth(mahMain);
} else {
navBar.removeClass(stickyDiv);
}
});
});
.being-sticky {
position:fixed;
top:0;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
Thanks everyone for your help!
So it was a simple fix for me for safari. I created a variable of whenToScroll and set it differently if it was safari or another browser! That seemed to fix it! Though I tried the safari setting for chrome and no go.
if (jQuery.browser.safari)
var whenToScroll = jQuery("div.hgroup.full-container").outerHeight();
else
var whenToScroll = navBar.offset().top;
Have you tried setting a timeout and seeing how that displays on IOS? If it's a timing thing that's being read differently, you can use navigator.userAgent to remove the class a bit later for IOS devices only.
if(/iPhone|iPad|iPod/.test(navigator.userAgent)) { //IOS browsers
setTimeout(function(){
navBar.removeClass(stickyDiv);
}, 7000); // however many milliseconds you need it to wait for
}else{
navBar.removeClass(stickyDiv);
}
I'm using SlickGrid.js library and it is excellent!
Only major problem right now is with Internet Explorer (confirmed in 9, 10, and 11), but the standards compliant browsers like Chrome and FF work fine.
Problem: When grid is scrolled and then hidden and then re-shown in IE the scroll position is reset to top of grid, and the viewport/data is either cut off or completely hidden (depending on scroll amount).
Here is a fiddle that demonstrates the SlickGrid.js IE bug (using the author's simple example 1):
http://jsfiddle.net/crwxoc17/1/
Anybody have a generic fix for this or patch to slick grid?
I can call grid.resizeCanvas() to sorta fix the issue, but it resets scrollbar to top and it's very annoying to do this for every single grid just to deal with Internet Explorer.
Semi-working fix, but still screws up the scrolltop:
function onShowGrid1() { grid.resizeCanvas(); }
(Reviewing JS code now, but I have not yet confirmed whether the bug is Microsoft's or SlickGrid's)
This issue applies to any element in IE with overflow set to scroll or auto and whose visibility is toggled. There's a simple example here: https://jsfiddle.net/qkhxL6r8/4/
That said, if you'd like the scrollTop position to be preserved you could extend SlickGrid or create a wrapper a class that subscribes to the onScroll event, records the scrollTop value, and sets it on the viewport element when showing or hiding the grid. I modified your example code as a proof of concept here: http://jsfiddle.net/h9cu2cmp/4/
var lastScrollTop;
var scrollTimeout;
function updateScrollTop(e, args){
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(function(){
lastScrollTop = args.scrollTop;
}, 30);
}
//...
grid.onScroll.subscribe(updateScrollTop);
$('body').on('click', '.toggle-button', function(){
$("#myGrid").toggle();
if(lastScrollTop !== undefined){
$("#myGrid").find('.slick-viewport').get(0).scrollTop = lastScrollTop;
}
});
If you're using a remote data provider you can trigger ensureData for the updated scrollTop with grid.onViewportChanged.notify()
I have added smooth scrolling to a site of mine using this piece of JavaScript when clicking on hash links.
$('a[href*=#]')
.click(onAnchorClick);
function onAnchorClick(event)
{
return ! scrollTo(this.hash);
}
function scrollTo(target)
{
var e = $(target);
var y = e.exists() ? e.offset().top : 0;
if(y == 0 && target != '#top')
return false;
if(Math.max($('html').scrollTop(), $('body').scrollTop()) != y)
$('html,body')
.animate({scrollTop: y}, 500, function() { location.hash = target; } );
else
location.hash = target;
return true;
}
$.fn.exists = function()
{
return this.length > 0 ? this : false;
}
Works fantastic in desktop browsers and looks to work fine on at least iOS devices as well. However, on my WinPhone 8 device it was garbage. Scrolling was a mess and didn't even end up where it should. So I decided to not enable it there through an if( ! /Windows Phone 8\.0/.test(navigator.userAgent)).
Now it works well, and seems the browser on the WinPhone actually is smooth scrolling by default, which is great.
But it is of course a bit dumb to have a smooth scroll script active if the browser already does this by default. Is there a way I can detect if a browser already has a smooth scrolling feature enabled?
I managed to solve it this way:
<style>
body {
scroll-behavior: smooth;
}
</style>
<script>
if(getComputedStyle(document.body).scrollBehavior === 'smooth'){
console.log('This browser supports scrollBehavior smooth');
}
</script>
Yes and no. Unfortunately there are no standards for these types of things. However there are work arounds, one of which you are already doing: browser sniffing.
Basically, that's about as advanced as this kind of detection goes because some browsers don't yet even support smooth scrolling officially or without experimental developments (like Chromium). And standards won't be set unless the majority are on the same page. Not only that but it also comes down to GPU hardware abilities as some devices and computers struggle with smooth scrolling and animations. So technically speaking, even if a browser did support smooth scrolling, who's to say the device or desktop running it can render fast enough to even display the effect. That's another bottle neck.
I believe someday in the future there will be more of a need for browser feature specifications such as this to better improve user interactions, but until that day you're doing it right.
Several years later, there now is a CSS property in the Working Draft for this:
scroll-behavior 🎉
So instead of the Javascript in my original question, or similar, we can just do this:
html {
scroll-behavior: smooth;
}
#media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
}
Right now, it seems to work for all browsers except IE and Edge, and since this is just a nice-to-have feature that makes things look a bit nicer... Yeah, I don't really care about IE or Edge. 😛👍
I have the following function that checks the scrolling position of a user so that the menu becomes fixed once they scroll past the masthead
function checkScrolling() {
if( $(window).scrollTop() > $('.masthead').height() ) { // we check against ACTUAL height
$('.menu').addClass('fixed');
}else {
$('.menu').removeClass('fixed');
}
}
and here the desktop and touch screen event listeners:
$(document).bind('touchstart touchend touchcancel touchleave touchmove', function(e){
checkScrolling();
});
$(window).scroll(function(){
checkScrolling();
});
However the touch events only make the menu fixed during the touchmove reliably. If I scroll really fast with a swipe up or down, there is a delay before the menu becomes fixed or unfixed.
Any ideas on how to fix this? See a code example here: http://dev.driz.co.uk/mobileMasthead.html (has been amended based on some answers below, but still does not work correctly on an iPad or iPhone)
Update:
And reading up on the topic reveals that JS like checking scroll position don't happen during the scroll... BUT... I've noticed that http://www.facebook.com/home/ doesn't have this issue when testing it on my iPad. So it's definitely possible to achieve this effect without using any custom JavaScript scrollbars like iScroll or similar.
Maybe I understand the question wrong, but to ensure functionality with high speed scrolling, why don't you tackle it the pure CSS way (aka faking the 'fancy' effect):
Old School (fast but primitive)
HTML
<div id="menu2"></div>
<div class="scroll" id="scroller">
<div id="overlay"></div>
<div id="menu"></div>
</div>
With the simple CSS:
html, body { overflow: hidden; height: 100% }
body { margin:0; padding:0; }
.scroll {
-webkit-overflow-scrolling: touch;
height:960px;
width:640px;
}
#menu2 {
width:640px;
height:20px;
background:red;
position:absolute;
z-index:-1;
}
#menu {
width:100%;
height:20px;
background:red;
z-index:0;
}
You can see a working example HERE.
It may be a bit primitive: but hey! It works! :)
New School (fancy but slow)
Check all of the other answers supplied.
But note that it 'is known' that the usage of JavaScript in combination with scrolling on mobile devices is causing a lot of trouble regarding speed.
That's why I think the simple CSS approach may be better.
If you want to learn about JavaScript and scrolling on mobile devices (and other functions), then there are two articles which I highly recommend reading:
Fast animations with iOS Webkit
Native scrolling: Grins and Gotchas
Facebook doesn't use JavaScript but pure css:
position: -webkit-sticky;
If i remember it correctly this makes the element stick at the top of its parent container when scrolled.
You just needed to attach your scroll events, not to window, document or body, but to a custom container.
On iOS you can't programatically react during these hardware-accelerated window scrolling behaviour.
Here's a fiddle:
a wrapper:
<div id="content">
some not-so-relevant css:
html,body,#content {
width:100%;
height:100%;
}
#content {
background-color: lightblue;
overflow:scroll;
}
attaching the listeners to the container:
function checkScrolling() {
if ($('#content').scrollTop() > mastheadHeight) {
menu.addClass('fixed');
} else {
menu.removeClass('fixed');
}
}
$('#content').scroll(function () {
checkScrolling();
});
You can see it working here for the JS-only fallback:
http://jsfiddle.net/jaibuu/YqPzS/
direct URL: http://fiddle.jshell.net/jaibuu/YqPzS/show/
I once tried to implement sticky headers on mobile version of a site but had encountered whole set of problems.
First and most important is that scroll event does not fire on mobile devices as often as it does on desktop. Usually it does fire when page stops. I don't know the exact reason for that but I can suggest it's because browsers on mobile "send" such tasks to GPU meantime CPU can not keep JS objects up to date with what happens to the canvas (but that's just my suggestion). Newer version of iOSes are making it better for us and probably scroll will work on iPhones.
You should use touch events. This makes you write a separate version of code for devices that support touch input. So have to look for reliable ways of testing for platform.
But touch events are also tricky especially on Android. I thought that in the callback for touchmove event I will be able to figure out current coordinates and go further from that point.
But There this issue https://code.google.com/p/android/issues/detail?id=5491 on Android, in summary touchmove fires once or twice at the very beginning of the touch action and does not fire again. Which makes it totally useless. People are suggesting preventDefault() but you no longer can scroll with that.
So I ended up with idea to reimplement scrolling from scratch using CSS transforms. And here is my results so far:
http://jsbin.com/elaker/23
Open that link on your device and http://jsbin.com/elaker/23/edit on your desktop: you'll be able to edit code and it will live update on you device, very convenient.
NOTE:
I'd like to warn you that this CSS scrolling thing is raw and there are some known problems that are not resolved yet: like you can sometimes scroll beyond top or bottom boundaries or if you just touch (not move) it still will scroll. Also the notorious URL bar will not hide. So there is work to do.
Do you need the touch events to fire this at all? Modern devices should return $(window).scroll() events and scrollTop values. On older Android and and pre-ios5 (I think), position:fixed: didn't work as expected because the of how the viewport worked. But it has been corrected in all new releases.
To further debug devices, I highly recommend Adobe Edge Inspect. You could console.log the scrollTop and see if the devices you care about actually work correctly with out any trickery. You'll get everything you need with their free version.
A great way to dealing with scroll events is not to attach your checks to the scroll event
takes a lot of resources and doesn't work very well with older browsers.
fortunately you can have a lot more control if you just perform a time loop to do that.
codewise that looks like that:
(It's used by twitter)
var MyMenu = $('#menu'),
didScroll = false;
$(window).scroll(function() {
didScroll = true;
});
setInterval(function() {
if ( didScroll ) {
didScroll = false;
//Do your check here
checkScrolling();
}
}, 180);
You should put your $('.masthead').height() outside this checkScrolling function of yours (in a higher scope) as this kind of operations takes a lot of resources and always ask jquery to "select your element" and calculate its size will eventually make your application laggy:
var headerHeight = $('.masthead').height()
function checkScrolling()
.....
Last thing , you can change the value of the interval attribute (right now it's 180 (a bit more that 60hz -. refresh time of the screen) you can make this value bigger, if you want your app to be more performant but a bit less accurate)
Thanks John Resig and twitter:
http://ejohn.org/blog/learning-from-twitter/
Hope that helped
Ajios !
I have just tested your functions for efficiency. You should try this
function checkScrolling() {
if( $(window).scrollTop() > mastheadHeight )menu.css('position','fixed');
else menu.css('position','');
}
This will reduce function call of addClass and RemoveClass and your execution time will take effect. If you want to reduce more execution time, then better to use pure JavaScript
$(document).ready(function(){
var p = $("#stop").offset().top;
$(window).scroll(function(){
if(p<$(window).scrollTop()){
console.log("div reached");
$("#stop").css({position:"fixed",top:0});
}
else{
console.log("div out");
$("#stop").css({position:"static"});
}
})
});
I think this will help you.
The total code is here in jsfiddle.net.
I have tested it for ipad using safari of online ipad2 simulator in http://alexw.me/ipad2/ and it has worked there. So, I think it will work on real ipad too.
setScrollTop: function(inTop) {
var top = Math.max(0,inTop);
if (wm.isMobile) { // should always be true for touch events
top = Math.min(top, this.listNode.clientHeight - this.listNodeWrapper.clientHeight);
if (dojo.isWebKit) {
this.listNode.style.WebkitTransform = "translate(0,-" + top + "px)";
} else if (dojo.isMoz) {
this.listNode.style.MozTransform = "translate(0,-" + top + "px)";
} else if (dojo.isOpera) {
this.listNode.style.OTransform = "translate(0,-" + top + "px)";
} else if (dojo.isIE) {
this.listNode.style.MsTransform = "translate(0,-" + top + "px)";
}
this.listNode.style.transform = "translate(0,-" + top + "px)";
this._scrollTop = top;
this._onScroll();
} else {
this.listNode.scrollTop = top + "px";
}
},
Is there a way to disable the bounce effect in a scrolling div?
So far I have tried these things but none worked. Please help!
How to disable vertical bounce/scroll on iPhone in a mobile web application
Can't disable bounce with UIScrollView and pagingEnabled=YES
ipad safari: disable scrolling, and bounce effect?
Disable UITableView vertical bounces when scrolling
And
http://www.iphonedevsdk.com/forum/iphone-sdk-development/996-turn-off-scrolling-bounces-uiwebview.html
Thanks!
If you're using Cordova 1.7, just open the Cordova.plist file and set the key UIWebViewBounce to NO.
Open your phoneGap project's config.xml file and change UIWebViewBounce from default true to false:
<preference name="UIWebViewBounce" value="false" />
Can't imagine why the default is true...
Based on your comment, the code your are using is the disable scrolling altogether. If you want scrolling, but without the bounce effect, try something like this:
var xStart, yStart = 0;
document.getElementById("scrollableDiv").addEventListener('touchstart',function(e) {
xStart = e.touches[0].screenX;
yStart = e.touches[0].screenY;
});
document.getElementById("scrollableDiv").addEventListener('touchmove',function(e) {
var xMovement = Math.abs(e.touches[0].screenX - xStart);
var yMovement = Math.abs(e.touches[0].screenY - yStart);
if((yMovement * 3) > xMovement) {
e.preventDefault();
}
});
I found this solution here. Let me know if it works for you.
This will help you out place the .scroll class on the element you wish to still have scrolling.
Whats happening is all touch moves are disabled by default. If the element you wish to scroll has the .scroll class on it then it sets the gate to true to allow it to pass.
On touch end you reset the gate to false.
This works on IOS5 and 6 and could work in Chrome and Safari
Look # this post to extend it How to prevent page scrolling when scrolling a DIV element?
The only catch to this is that if you over scroll the scrollable element the elastic effect allows the scroll to be passed up the tree while scroll is set to true. Manually setting the scroll position gets overridden by the dreaded bounce effect.
I bet those Apple friggers have a native implementation of scroll running in a set time out with each step hard wired in. So if you scroll to -20, I think it hard wires each step into a loop not checking where it was. Scrolling to -20 -19 -18 etc in sequence. We must think of a way around this! ( in fact typing it out load I have an idea! )
$(function(){
var scroll = false
var scroll_element;
$('body').on('touchmove',function(e){
if(!scroll){
e.preventDefault()
}
})
$('body').on('touchend',function(e){
scroll = false
})
$('.scroll').on('touchstart',function(e){
scroll_element = this
scroll = true
})
})
I know this may not be the best way but it works.
Here is what I did -
#scrollableDiv {
position:fixed;
top:50px;
width:300px;
height:500px;
word-wrap: break-word;
overflow-y: scroll;
}
document.getElementById("scrollableDiv").innerHTML = longText;
document.getElementById("scrollableDiv").scrollTop = 0;