Skip to content
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
Open

Add iOS support to position:fixed sample code #167

matthewbuchanan opened this issue Dec 21, 2010 · 39 comments

Comments

@matthewbuchanan
Copy link

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
Copy link
Member

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
Copy link
Author

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

@paulirish
Copy link
Member

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

@torbs
Copy link

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
Copy link

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
Copy link

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
Copy link

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

@torbs
Copy link

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
Copy link

torbs commented Sep 21, 2011

It is also necesarry to do the test onload.

@paulirish
Copy link
Member

torbs, is your code above final?

we should start a pull request if so

@torbs
Copy link

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
Copy link
Contributor

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
Copy link
Member

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

@arxpoetica
Copy link

BUMP! BUMP! ;)

@paulirish
Copy link
Member

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
Copy link

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

@lmeurs
Copy link

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
Copy link

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
Copy link

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
Copy link

RyanS commented Mar 17, 2012

bummer. Thanks for the heads up

@jokeyrhyme
Copy link
Contributor

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
Copy link

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
Copy link

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

@paulirish
Copy link
Member

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
Copy link

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
Copy link
Author

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

@grandecomplex
Copy link

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
Copy link

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
Copy link

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

@grandecomplex
Copy link

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
Copy link

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
Copy link
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
Copy link

@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
Copy link
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
Copy link
Member

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
Copy link

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
Copy link
Member

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
Copy link
Member

@grandecomplex hows it goin? :]

@patrickkettner
Copy link
Member

@grandecomplex hiya :D

any luck?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests