Cameron
Cameron

Reputation: 28823

Check scrollTop on touch device

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.

Upvotes: 18

Views: 18718

Answers (9)

Jean-Paul
Jean-Paul

Reputation: 21170

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:

Upvotes: 7

ihor marusyk
ihor marusyk

Reputation: 2090

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.

Upvotes: 2

Jaibuu
Jaibuu

Reputation: 578

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/

Upvotes: 2

krishna singh
krishna singh

Reputation: 1073

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";
    }
},

Upvotes: 1

Ramsharan
Ramsharan

Reputation: 2064

$(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.

Upvotes: 1

Wasim A.
Wasim A.

Reputation: 9890

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

Upvotes: 1

a better oliver
a better oliver

Reputation: 26828

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.

Upvotes: 5

Abdoul Sy
Abdoul Sy

Reputation: 590

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 !

Upvotes: 1

Jason Lydon
Jason Lydon

Reputation: 7180

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.

Upvotes: 1

Related Questions