New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add iOS support to position:fixed sample code #167

Open
matthewbuchanan opened this Issue Dec 21, 2010 · 39 comments

Comments

Projects
None yet
@matthewbuchanan

matthewbuchanan commented Dec 21, 2010

The sample code provided in the addTest() documentation for detecting support for position:fixed reports a false positive in Mobile Safari. (This browser correctly positions objects with that style applied, but does not keep them fixed in the viewport when the page scrolls.)

@paulirish

This comment has been minimized.

Member

paulirish commented Jan 2, 2011

matt,

i'd be curious if you tried out this code:
https://github.com/jquery/jquery/blob/eed380/src/offset.js#L125-143

if it rectifies the iOS false positive.

cheers

@matthewbuchanan

This comment has been minimized.

matthewbuchanan commented Jan 18, 2011

Hi Paul, sorry for the delay. Just tested that code and it returns the same false positive on both iPhone and iPad :(

@paulirish

This comment has been minimized.

Member

paulirish commented Jul 19, 2011

Just a note that Marc Grabanski told me he spent 7 hours trying to solve this one without a successful resolution. :/

@torbs

This comment has been minimized.

torbs commented Sep 15, 2011

// Tested on iphone with IOS 5 beta and IOS 4

var IOS_FIXED_POSITION;

window.onload = function () { // Should be addEventListener etc...

if (typeof document.body.scrollIntoViewIfNeeded === 'function') {

    (function () {
        var container = document.body,
        el = document.createElement('div'),
        originalHeight = container.style.height, 
        originalScrollTop = window.pageYOffset;
        testScrollTop = 20;
        el.style.cssText = 'position:fixed;top:0px;height:10px;';

        container.appendChild(el);
        container.style.height="3000px";

        window.addEventListener('scroll', testScrollTop, false);
        window.setTimeout(testScroll, 20); // ios 4 does'n publish the scroll event on scrollto
        window.scrollTo(0,testScrollTop );

        function testScroll() {
            if (IOS_FIXED_POSITION === undefined) {
                el.scrollIntoViewIfNeeded();
                if (window.pageYOffset === testScrollTop) {
                    IOS_FIXED_POSITION = true;
                } else {
                    IOS_FIXED_POSITION = false;
                }
            }
            window.removeEventListener('scroll', testScroll, false);
            container.style.height=originalHeight;
            container.removeChild(el);
            window.scrollTo(0,originalScrollTop);
        }

    }());
} else {
    IOS_FIXED_POSITION = false;
}
}
@bobslaede

This comment has been minimized.

bobslaede commented Sep 16, 2011

Fixed it a bit, and used the original positionfixed test from the docs for regular browsers, it could be cleaned up a bit more, but it works.
Tested in Chrome, Firefox, IE9, iOS4, The Android 2.3.3 browser, and Firefox for Android

Modernizr.addTest('iospositionfixed', function () {
    var test  = document.createElement('div'),
        ret,
        fake = false,
        root = document.body || (function () {
            fake = true;
            return document.documentElement.appendChild(document.createElement('body'));
        }());

    if (typeof document.body.scrollIntoViewIfNeeded === 'function') {

        var oldCssText = root.style.cssText,
            testScrollTop = 20,
            originalScrollTop = window.pageYOffset;

        root.appendChild(test);

        test.style.cssText = 'position:fixed;top:0px;height:10px;';

        root.style.height="3000px";

        /* avoided hoisting for clarity */
        var testScroll = function() {
            if (ret === undefined) {
                test.scrollIntoViewIfNeeded();
                if (window.pageYOffset === testScrollTop) {
                    ret = true;
                } else {
                    ret = false;
                }
            }
            window.removeEventListener('scroll', testScroll, false);
        }

        window.addEventListener('scroll', testScrollTop, false);
        window.setTimeout(testScroll, 20); // ios 4 does'nt publish the scroll event on scrollto
        window.scrollTo(0, testScrollTop);
        testScroll();

        root.removeChild(test);
        root.style.cssText = oldCssText;
        window.scrollTo(0, originalScrollTop);

    } else {
        ret = Modernizr.positionfixed; // firefox and IE doesnt have document.body.scrollIntoViewIfNeeded, so we test with the original modernizr test
    }

    if (fake) {
        document.documentElement.removeChild(root);
    }

    return ret;
});

EDIT: Here's a gist of them both: https://gist.github.com/1221602

@torbs

This comment has been minimized.

torbs commented Sep 20, 2011

I did the timeout/eventlistener to test on Android devices for scrollIntoViewIfNeeded. If testScroll() is called right after scrollTo() and it works, the timeout and eventlistener is not needed. I will test this later. I don't have an Android available right now.

I found out after publishing my code here, that we need to test further on Android devices with user-scalable set to "yes". My test returns true if the timeout is set to a higher number, but Android doesn't render posistion fixed on scalable viewports.

@bobslaede

This comment has been minimized.

bobslaede commented Sep 20, 2011

Very good to know. My test on android was with user-scalable set to "no".

@torbs

This comment has been minimized.

torbs commented Sep 20, 2011

Just tested on an IOS beta 5. The eventlistener is necessary for iPhone IOS5 to return true. Calling testScroll() directly will return false.

@torbs

This comment has been minimized.

torbs commented Sep 21, 2011

It is also necesarry to do the test onload.

@paulirish

This comment has been minimized.

Member

paulirish commented Sep 21, 2011

torbs, is your code above final?

we should start a pull request if so

@torbs

This comment has been minimized.

torbs commented Sep 21, 2011

paulirish, this works for ios:

    var mno = {};
    mno.features = (function () {
        var features = {
            positionFixed: function (callback) {
                if (typeof fixed.vaule === 'undefined') {
                    fixed.callbacks.push(callback);
                } else {
                    callback(fixed.value);
                }
            }
        },
        fixed = {
            value:undefined,
            callbacks:[]
        };

        function runFixedCallbacks() {
            for (var i = 0; i < fixed.callbacks.length;i++) {
                fixed.callbacks[i](fixed.value);
            }
        }

       $(window).load(function () {
           var test  = document.createElement('div'),
               control = test.cloneNode(false),
               root = document.body,
               oldCssText = root.style.cssText;

           if (typeof root.scrollIntoViewIfNeeded === 'function') {

               var testScrollTop = 42,
                   originalScrollTop = window.pageYOffset;

               root.appendChild(test);

               test.style.cssText = 'position:fixed;top:0px;height:10px;width:100%;background:#900';

               root.style.height="3000px";

               /* avoided hoisting for clarity */
               var testScroll = function() {
                   window.removeEventListener('scroll', testScroll, false);
                   if (fixed.value === undefined) {
                       test.scrollIntoViewIfNeeded();
                       fixed.value = (window.pageYOffset === testScrollTop);
                   }

                   root.removeChild(test);
                   root.style.cssText = oldCssText;
                   window.scrollTo(0, originalScrollTop);
                   runFixedCallbacks();
               };

               window.addEventListener('scroll', testScrollTop, false);
               window.scrollTo(0, testScrollTop);
               window.setTimeout(testScroll, 15); // ios 4 doesn't publish the scroll event on scrollto

           } else {
               root.style.cssText = 'padding:0;margin:0';
               test.style.cssText = 'position:fixed;top:42px';

               root.appendChild(test);
               root.appendChild(control);

               fixed.value = test.offsetTop !== control.offsetTop;
               root.removeChild(control);
               root.removeChild(test);
               root.style.cssText = oldCssText;
               runFixedCallbacks();
           }



       });

       return features;
    }());
@jokeyrhyme

This comment has been minimized.

Contributor

jokeyrhyme commented Oct 25, 2011

So this is part of jQuery 1.7. Cool.

Is it worth programming Modernizr to detect jQuery and use the result from its tests (and vice versa) where they overlap and one is loaded before the other? Or is it faster to mess with the DOM twice over for this feature-detect than it would be to test for an existing result?

@paulirish

This comment has been minimized.

Member

paulirish commented Nov 2, 2011

the detect in jquery 1.7 gives a false positive for mobile webkit.

@arxpoetica

This comment has been minimized.

arxpoetica commented Jan 27, 2012

BUMP! BUMP! ;)

@paulirish

This comment has been minimized.

Member

paulirish commented Jan 27, 2012

if anyone wants to try torb's latest code.. and test it against some ios/android/op mobile devices.. that'd be great.

then we can bring it in.

its all you, rob. :)

@arxpoetica

This comment has been minimized.

arxpoetica commented Jan 27, 2012

I sort of half tested it, but achieved !victory.

@lmeurs

This comment has been minimized.

lmeurs commented Feb 9, 2012

torbs code needs a small correction before one can test it, at line 5 it says 'vaule' instead of 'value':

if (typeof fixed.vaule === 'undefined') {

After the correction I tested it on Chrome v16 and the iOS simulator (iOS v5.0 and v4.3), but unfortunately all returned a false value, while iOS v4.3 is the only non-supportive.

@RyanS

This comment has been minimized.

RyanS commented Mar 16, 2012

So I ran in to a problem where it was ok for position: fixed not to work but if it did I made some layout calculations based on that. There was an issue with browsers that did not support position fixed as they would still effect the document layout rather than acting like a position fixed element.

I was able to write a test that allowed me to see if a position fixed element was taken out of the document flow. I tested on: Mac chrome, FF, and safari, Windows IE, Blackberry Torch, Android 2.2.3 and iOS 5. I don't have easy access to an iOS4 device. Dunno if this helps anyone out or maybe I am being foolish but just thought i'd share: http://jsfiddle.net/s7TUs/4/

@lmeurs

This comment has been minimized.

lmeurs commented Mar 17, 2012

@RyanS: I tested your script on my Android 2.3.3 phone and iOS 4.3 and 5.0 simulator, all resulted in true values, while iOS 4 is not supporting position: fixed

@RyanS

This comment has been minimized.

RyanS commented Mar 17, 2012

bummer. Thanks for the heads up

@jokeyrhyme

This comment has been minimized.

Contributor

jokeyrhyme commented Mar 17, 2012

While it worries me, this is actually something I'm currently sniffing the user agent for. It sucks, but it seems really tricky to come up with a true feature-detect for this. Is it possible to feature-detect for iOS via iOS-specific JavaScript features/values (to help guard against spoofed user agents) and then trust the user agent to be reporting the correct version?

How about if we use 2 different tests that together have 100% coverage, and then just detect when it is appropriate to use one test instead of the other?

@lmeurs

This comment has been minimized.

lmeurs commented Mar 17, 2012

The Filament Group published a sort of proof of concept for a similar problem, cross platform support of the CSS overflow property. The partially ua sniffing part of this script might be of any help, see http://filamentgroup.com/lab/overthrow/.

@grandecomplex

This comment has been minimized.

grandecomplex commented Mar 29, 2012

You don't need two tests.. let me see if I can do a pull request...

@paulirish

This comment has been minimized.

Member

paulirish commented May 11, 2012

attention to people who want a pos:fixed test.. mr @grandecomplex has a draft ready in #539 but is stuck with some iOS4 scroll logic. could you take a look and see if we can get this squared away?

@keeyipchan

This comment has been minimized.

keeyipchan commented Jun 6, 2012

If anyone's interested, I'm playing with an alternative method that works for ios 4, android 2.2, and desktop chrome. I haven't had time to test on ios 5 yet.
http://jsfiddle.net/2yS4L/embedded/result/
You will need to move the code outside of the jsfiddle environment.

@matthewbuchanan

This comment has been minimized.

matthewbuchanan commented Jun 6, 2012

@Kee-Yip iOS5 supports position fixed but your script appears to report ‘false’ ten times and then set givingUp to ‘true’.

@grandecomplex

This comment has been minimized.

grandecomplex commented Jun 6, 2012

The pull request I proposed also works outside of Modernizr. In a nutshell, the problem is Modernizr doesn't like async (including timeouts) stuff.

Do you think that your script can work without timeouts?

@keeyipchan

This comment has been minimized.

keeyipchan commented Jun 6, 2012

I'll test it out some more, but sadly I think the scrolling/timeout is necessary. Android, for example, takes roughly 400ms to finish the detection.
I also realize that there are some assumptions with my script, such as the meta viewport and z-indexes, that may not fit everyone's needs.

@arxpoetica

This comment has been minimized.

arxpoetica commented Dec 29, 2012

Thoughts on why this has died? Is it because of issue #539 ?

@grandecomplex

This comment has been minimized.

grandecomplex commented Dec 30, 2012

Mr. Irish can touch on this more maybe.

I think it died because Modernizr needs async testing ability similar to async tests in jQuery's qunit in order for position fixed to be tested.

Happy new year!

@ChrisWojcik

This comment has been minimized.

ChrisWojcik commented Jan 5, 2013

Am curious if people are using UA-sniffing for this, or are avoiding position:fixed in situations where you'd need some sort of code-branching because they're an "undetectable"?

My (probably naive) takeaway was that in the proposed tests, the "hack" they were using needed to be patched up and became very specific to certain browsers, and then still reported false-positives/negatives.

If a feature-detect is unreliable and would require a lot of maintenance as new browsers/devices come out, aren't you getting most of the same downsides of UA-sniffing anyway?

http://caniuse.com/css-fixed - Appears the support is getting much better in the very latest mobile versions. And I think you could UA-sniff only to "blacklist" the categories of UA's that don't pretty easily using regex similar to how the filament group did as part of their solution. One line and you can knock out a whole group of them (e.g. iOS <= 5).

That probably isn't in-line with Modernizr, but any feature detect just seems like it would have to be "overly-elegant" at best.

@stucox

This comment has been minimized.

Member

stucox commented Jan 7, 2013

@americanyak - the async stuff's being done "properly" in the big AMD re-rub (Modernizr v3.0) - see #622 and #713 - so I guess this is blocked by those.

@ChrisWojcik - tbh there are a few Modernizr tests which give false positives/negatives; in some places, Modernizr covers these with UA sniffing itself. In this particular case, I gather the consensus is that @grandecomplex's test does the trick in all browsers(?) if run after a timeout - but Modernizr doesn't support that properly... yet. If I was desperate to use position: fixed, I'd probably grab his test and wrap it in some code to run it async outside Modernizr and add an appropriate class to <html> if successful - assuming it's just styles that are dependent on the presence of this feature.

In the words of @aFarkas - "innovative frontend development is hacky and always will be hacky!"

@ChrisWojcik

This comment has been minimized.

ChrisWojcik commented Jan 7, 2013

@stucox Thanks for the clarification. I was under the impression that the creator of Modernizr was against including UA sniffing into the library as a rule. (Only because feature-detection is intended to be the alternative). I thought I heard Paul Irish say that at some point, but I may just be misremembering.

The issue I'm working on involves javascript and not just styles, but it's not the most complex thing ever.

@stucox

This comment has been minimized.

Member

stucox commented Jan 7, 2013

It's a last resort really, usually to filter out buggy implementations which can't otherwise be detected.

@patrickkettner

This comment has been minimized.

Member

patrickkettner commented Dec 1, 2013

Another reminder, because I keep forgetting myself, @grandecomplex has a pull request for this in #539, and are just waiting for an update to the code style before we finally close out this ancient bug :D

@grandecomplex

This comment has been minimized.

grandecomplex commented Dec 2, 2013

Last week was Thanksgiving so it moved to my todos this week. I plan to
have something by Friday.

On Sun, Dec 1, 2013 at 3:41 PM, patrick kettner notifications@github.comwrote:

Another reminderhttps://github.com//issues/167#issuecomment-5642662,
because I keep forgetting myself, @grandecomplexhttps://github.com/grandecomplexhas a pull request for this in
#539 #539, and are just
waiting for an update to the code style before we finally close out this
ancient bug :D


Reply to this email directly or view it on GitHubhttps://github.com//issues/167#issuecomment-29586917
.

@patrickkettner

This comment has been minimized.

Member

patrickkettner commented Dec 2, 2013

Oh, sorry, that was a reminder for me, not for you. I keep on forgetting there is an open pull request, spend 10 minutes hacking on it, then remember to scroll up.

@patrickkettner

This comment has been minimized.

Member

patrickkettner commented Oct 3, 2014

@grandecomplex hows it goin? :]

@patrickkettner

This comment has been minimized.

Member

patrickkettner commented Sep 21, 2015

@grandecomplex hiya :D

any luck?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment