8

My website features a sticky navigation, but it won't work on mobile devices...

How do I make it sticky on mobile devices, or how do I prevent it from being sticky (while keeping the navigation, of course)?

I looked everywhere for an answer, but couldn't find one that worked (and didn't disable my navigation:) Your help is much appreciated!

This is a part of my index.html header:

<head>

<link href="css/reset.css" rel="stylesheet" type="text/css" />
<link href="css/960.css" rel="stylesheet" type="text/css" />
<link href="css/styles.css" rel="stylesheet" type="text/css" />
<link href="fancybox/jquery.fancybox-1.3.4.css" rel="stylesheet" type="text/css" />

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
<script src="js/smooth-scroll.js" type="text/javascript"></script>
<script src="js/jquery.sticky.js" type="text/javascript"></script>
<script src="contactform.js" type="text/javascript"></script>

<!-- sticky nav -->
<script type="text/javascript">
$(window).load(function(){
$("nav").sticky({ topSpacing: 0, className: 'sticky', center: true });
});
</script>

</head>

And this is the code in the jquery.sticky javascript:

(function($) {
var defaults = {
        topSpacing: 0,
        bottomSpacing: 0,
        className: 'is-sticky',
        center: false
    },
    $window = $(window),
    $document = $(document),
    sticked = [],
    windowHeight = $window.height(),
    scroller = function() {
        var scrollTop = $window.scrollTop(),
            documentHeight = $document.height(),
            dwh = documentHeight - windowHeight,
            extra = (scrollTop > dwh) ? dwh - scrollTop : 0;
        for (var i = 0; i < sticked.length; i++) {
            var s = sticked[i],
                elementTop = s.stickyWrapper.offset().top,
                etse = elementTop - s.topSpacing - extra;
            if (scrollTop <= etse) {
                if (s.currentTop !== null) {
                    s.stickyElement.css('position', '').css('top', '').removeClass(s.className);
                    s.currentTop = null;
                }
            }
            else {
                var newTop = documentHeight - s.elementHeight - s.topSpacing - s.bottomSpacing - scrollTop - extra;
                if (newTop < 0) {
                    newTop = newTop + s.topSpacing;
                } else {
                    newTop = s.topSpacing;
                }
                if (s.currentTop != newTop) {
                    s.stickyElement.css('position', 'fixed').css('top', newTop).addClass(s.className);
                    s.currentTop = newTop;
                }
            }
        }
    },
    resizer = function() {
        windowHeight = $window.height();
    };

// should be more efficient than using $window.scroll(scroller) and $window.resize(resizer):
if (window.addEventListener) {
    window.addEventListener('scroll', scroller, false);
    window.addEventListener('resize', resizer, false);
} else if (window.attachEvent) {
    window.attachEvent('onscroll', scroller);
    window.attachEvent('onresize', resizer);
}

$.fn.sticky = function(options) {
    var o = $.extend(defaults, options);
    return this.each(function() {
        var stickyElement = $(this);
        if (o.center)
            var centerElement = "margin-left:auto;margin-right:auto;";

        stickyId = stickyElement.attr('id');
        stickyElement
            .wrapAll('<div id="' + stickyId + 'StickyWrapper" style="' + centerElement + '"></div>')
            .css('width', stickyElement.width());
        var elementHeight = stickyElement.outerHeight(),
            stickyWrapper = stickyElement.parent();
        stickyWrapper
            .css('width', stickyElement.outerWidth())
            .css('height', elementHeight)
            .css('clear', stickyElement.css('clear'));
        sticked.push({
            topSpacing: o.topSpacing,
            bottomSpacing: o.bottomSpacing,
            stickyElement: stickyElement,
            currentTop: null,
            stickyWrapper: stickyWrapper,
            elementHeight: elementHeight,
            className: o.className
        });
    });
};
})(jQuery);
Stefaan
  • 212
  • 3
  • 6
  • 16

4 Answers4

22
function isMobile() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}

if (!isMobile()) {
//place script you don't want to run on mobile here

}
CarlosO
  • 294
  • 2
  • 5
3

Abstain from reading the user agent string as it may be tampered with or spoofed. Use Modernizr which performs a test of what features exactly the user environment supports, specifically for touch: if (Modernizr.touch)

For your needs:

<head>

<link href="css/reset.css" rel="stylesheet" type="text/css" />
<link href="css/960.css" rel="stylesheet" type="text/css" />
<link href="css/styles.css" rel="stylesheet" type="text/css" />
<link href="fancybox/jquery.fancybox-1.3.4.css" rel="stylesheet" type="text/css" />

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
<script src="js/smooth-scroll.js" type="text/javascript"></script>
<script src="js/jquery.sticky.js" type="text/javascript"></script>
<script src="http://ajax.aspnetcdn.com/ajax/modernizr/modernizr-2.0.6-development-only.js" type="text/javascript"></script>
<script src="contactform.js" type="text/javascript"></script>

<!-- sticky nav -->
<script type="text/javascript">
$(window).load(function(){
    if (Modernizr.touch) {
        $("nav").sticky({ topSpacing: 0, className: 'sticky', center: true });
    }
});
</script>

</head>
Konstantin Dinev
  • 30,746
  • 13
  • 64
  • 91
  • Are you assuming that any device supporting "touch" is a mobile device? – Arash Milani Sep 04 '12 at 09:04
  • Pretty much, because the browser needs to fire the correct touch events. Even if you have a touch monitor with IE9 or FF15, etc. you are not getting any touch events out of those browsers. The mobile browsers do fire them however. – Konstantin Dinev Sep 04 '12 at 09:07
  • Yes pretty much correct. :) I think it's better to add a browser size checking mechanism to the script to make the solution near zero defect. – Arash Milani Sep 04 '12 at 09:10
  • @ArashMilani: Won't your solution cause a problem with retina devices, featuring a very high resolution? They're mobile as well... Thanks for your input! – Stefaan Sep 04 '12 at 09:25
  • 1
    @Stefaan I think "spadar-shut" made a good point there. I'm just copying here for reference: "No, clientWidth reports CSS pixels, not device pixels. A retina iphone still reports its dimensions as 320x480 px (when you set viewport to device-width)." – Arash Milani Sep 04 '12 at 09:42
2

You should check if user is using a mobile browser or not. if so don't execute the sticky plugin method.

<!-- sticky nav -->
<script type="text/javascript">

var isMobile = {
Android: function() {
    return navigator.userAgent.match(/Android/i) ? true : false;
},
BlackBerry: function() {
    return navigator.userAgent.match(/BlackBerry/i) ? true : false;
},
iOS: function() {
    return navigator.userAgent.match(/iPhone|iPad|iPod/i) ? true : false;
},
Windows: function() {
    return navigator.userAgent.match(/IEMobile/i) ? true : false;
},
any: function() {
    return (isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Windows());
}
};

$(function(){
    if(! isMobile.any()){
        $("nav").sticky({ topSpacing: 0, className: 'sticky', center: true });
    }
});
</script>

The code for user agent detection is borrowed from here here

Arash Milani
  • 5,852
  • 2
  • 38
  • 45
  • 1
    User agent sniffing is generally a bad idea and feature detection is better. This would, of course, require understanding why the feature doesn't work and either fixing it to work on mobile or devising a test for the missing feature that makes it not work. – jfriend00 Sep 04 '12 at 08:47
  • I completely agree with you @jfriend00 :) – Arash Milani Sep 04 '12 at 08:48
2

I think a good soluion would be is to just check for screen or viewport size. This is in no way a check for mobile devices, but unsticking the navigation on small screens/viewports seems pretty reasonable. I'ts not the most important part of the page on a small screen, site content is. Sticky navigation isn't all that critical.

Checking for particular platforms or devices is so 1999-ish, I wouldn't recommend it. What if a user is seeing your site on a win8 tablet or something, do you still need that sticky nav?

Add the following to the <head> to let mobile devices know the width of your site content (change 960 to whatever width you have):

<meta name="viewport" content="width=960px, initial-scale=1">

And in javascript check like this (change 640 to whatever you feel appropriate):

if (document.documentElement.clientWidth > 640 ) {
    $("nav").sticky({ topSpacing: 0, className: 'sticky', center: true });
}
Spadar Shut
  • 13,513
  • 5
  • 39
  • 51
  • Won't this cause a problem with retina devices, featuring a very high resolution? They're mobile as well... Thanks for your input! – Stefaan Sep 04 '12 at 09:23
  • 2
    No, clientWidth reports CSS pixels, not device pixels. A retina iphone still reports its dimensions as 320x480 px (when you set viewport to device-width). – Spadar Shut Sep 04 '12 at 09:36
  • Thanks! I inserted the meta tag and adjusted the javascript in the head as shown below, but it disabled the sticky navigation on my deskop as well... Any thoughts? ` ` – Stefaan Sep 04 '12 at 10:08
  • Probably you run the script too early in the head when `document` is `undefined`, or indeed the viewport of your browser window is less than 640px. – Spadar Shut Sep 04 '12 at 10:21
  • You should add the code to the place where all your initialization logic is placed. It should look like `$(function(){ /* code here */ });` – Spadar Shut Sep 04 '12 at 10:34
  • I'm a Javascript newbie, sorry. Pasted my header code here: http://pastebin.com/sP9XFmeQ Could you have a quick look at it? Thanks a lot! – Stefaan Sep 04 '12 at 10:50
  • This should work. Do you see any errors in the error console? – Spadar Shut Sep 04 '12 at 11:03
  • Sticky navigation works on desktop and ipad. In the ipad error console I only see 'Viewport argument value "960px" for key "width" was truncated to its numeric prefix'... – Stefaan Sep 04 '12 at 11:11