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. 😛👍
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);
}
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.
I'm creating my own HTML 5 Browser Player. All controls work apart form getting the full screen working in IE 10, Chrome, Safari and Firefox work great.
My JavaScript skills aren't the best so would be great if some could explain things in a simple way for me that would be great.
I've read on some website that IE doesn't support Full Screen, if this is the case why can I go Full Screen via the browser player controls on IE10? (hate Microsoft so crap and behind on everything!)
Would appreciate and help and suggestions! thanks in advance!
This is what I have so far for my full screen function:
function toggleFullScreen() {
if(vid.requestFullScreen) {
vid.requestFullScreen();
} else if(vid.webkitRequestFullScreen) {
vid.webkitRequestFullScreen();
} else if(vid.mozRequestFullScreen) {
vid.mozRequestFullScreen();
}
}
IE hasn't supported the Full Screen API, until version 11.
However, if you're looking to produce a similar effect in IE10<=, you could toggle an element between position: static and position: fixed. While the element has fixed positioning you could give it width: 100%; height: 100%.
You can see this is how it's done on YouTube's HTML5 player for IE.
Additionally, it appears you are able to send the F11 keypress from JavaScript which brings the browser window into a full screen viewing mode.
var wscript = new ActiveXObject("Wscript.shell");
wscript.SendKeys("{F11}");
With these two method's combined, I think this is the closest IE can get in emulating the Full Screen API.
I've read on some website that IE doesn't support Full Screen
It won't support the full screen api until version 11.
if this is the case why can I go Full Screen via the browser player controls on IE10?
Because they are native controls; they don't use the full screen API.
IE 11 does support it, works well, better than Chrome in some cases, there is a bug with iframes however:
https://connect.microsoft.com/IE/feedback/details/814527/ie11-iframes-body-offsetwidth-incorrect-when-iframe-is-in-full-screen-mode#tabs
Note that IE11 also needs a vendor prefix. msRequestFullscreen()
So for full cross browser functionality you need something like this:
var video = document.getElementById('videoID');
if (video.requestFullscreen) {
video.requestFullscreen();
} else if (video.mozRequestFullScreen) {
video.mozRequestFullScreen();
} else if (video.webkitRequestFullscreen) {
video.webkitRequestFullscreen();
} else if (video.msRequestFullscreen) {
video.msRequestFullscreen();
}
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";
}
},
I'm working on a site using css3 animations, it works perfectly in Safari and Firefox, but for some reason performance in Chrome is awful. (around 15 fps)
http://triple-tested.com/animations/
The animations are quite simple, basically a few large circles layered up. I've also added a few png sprite animations using javascript.
I know about hardware acceleration but I don't think that is the problem, it seems to be some quirk that is unique to Chrome. The css animations perform 'OK' alone but once I add the sprites performance drops considerably.
$.fn.spriteme = function(options) {
var settings = $.extend({ framerate: 30 }, options);
return this.each(function(){
var $el = $(this),
curframe = 0,
width = settings.width,
fr = 1000/settings.framerate;
(function animloop(){
if(curframe == settings.frames) { curframe = 0; }
$el.css('background-position', (curframe*width)*-1 + 'px center');
curframe++;
setTimeout( animloop, fr );
})();
});
};
This is the code I've wrote to animate my sprites, but as I said it performs perfect in Safari and Firefox so I don't think it's the problem. Chrome seems to have an issue with animating using css alongside sprites.
I've tried everything I can find online but if anyone has any suggestions please let me know.
I'm using the latest stable chrome on mac btw (17.0.963.93)
You can see the css (using less) here btw
http://triple-tested.com/animations/css/style.less
Thanks for the replies guys, I think it is an issue with certain versions of chrome as it works perfect in the latest canary builds.
I ended up stripping back some of the animations for chrome, falls back gracefully to static images.