Update: An anonymous genius in the comments suggested using translateY instead of top for the animation. After some edits I’ve updated my demo, and it flies! The scrolling animation is smooth as silk. Apparently Webkit transforms are the only hardware-accelerated animations at this point. Thanks, random dude on the internet!
Update 2: This code is released to the public domain. You can use, modify, remix as you see fit.
Behold, fixed positioning on iPhone! http://doctyper.com/stuff/iphone/fixed/
Here’s a video for those without iPhones. This is running in the iPhone Simulator bundled with the SDK. Note that the animation is much choppier on an actual iPhone.
With the release of iPhone OS 2.0 came some great improvements over previous Mobile Safari versions. CSS animations are in (though buggy), as well as native touch events like touchstart, touchend, gesturechange, etc.
I played around with these new goodies while hunting for improvements to build into Pickleview. The most fascinating change to me was that you can now prevent the default behavior of elements with a simple preventDefault() call. It allows a user to drag an element around the screen without having to worry about the viewport wobbling about.
I grew curious as to what I could specifically call this on, and started testing out several elements. Turns out you can preventDefault on everything in the DOM, including the body element. This seemed incredibly useless for no other reason than the terrible usability it would bring if you couldn’t scroll the viewport. Then the proverbial light bulb went off: fixed positioning!
First, let’s recap why fixed positioning does not work off-the-bat. Mobile Safari uses a viewport to show you websites. Imagine a book in front of you. Take a piece of paper, cut a 320×416 square in it, and lay it over the book. To read the book, move the paper around and position the hole over the words you want to see. This is exactly what Mobile Safari’s viewport is doing. When you flick and scroll, you’re moving the viewport around while the website behind it stays static.
This renders fixed positioning null and void on iPhone. An element that has its position fixed is affixed to the body, not the viewport. So it is actually working as intended, though most people would prefer it attached to the viewport.
There are workarounds in the wild, but these are inelegant. You can reposition an element onscroll, but a scroll event in Mobile Safari is only fired after scrolling has stopped. This results in an evident “glitch” since you have to a) flick to your desired position, throwing the element off-screen, and b) wait for the element to reappear in the viewport after scrolling has stopped.
By disabling the default scroll behavior on the body element, you essentially glue the viewport down to its initial starting point, where it’s unable to go anywhere. This limits the viewport to exactly 320×416 pixels of space to show you. In this state, you have a perfectly useless experience.
This is where it gets interesting. In order to re-enable scrolling, I needed to only make the content area scrollable (think iframe, with header and footer above and below it). The touch and gesture events gives access to X/Y values, as well as timers and offset values. So by logging these and incrementing the top offset of the content area, we can create a scrollable block that does not affect the header or footer elements. A little spit and polish later: voila!
It’s pretty evident from my proof-of-concept that CSS animations need a lot of work. They’re promised to be “hardware-accelerated”, but there’s little proof of this. Most animations glitch, some to the point of non-use. The scrolling isn’t particularly smooth and even something as simple as animating a ‘top’ CSS property takes its toll. Still it’s usable, though I hope later Mobile Safari builds will address these issues.
If you’re interested, I’ve bundled together the source files for my proof-of-concept. Grab ‘em here.




39 Comments
That’s pretty killer. Thanks for sharing. Seems like a (browser) bug that it allows you to permanently hide the location field, but other than that, this shows a lot of potential.
Only a few of CSS properties are hardware accelerated during animations. In particular, animation of the webkit-transform property is hardware accelerated, animation of top is not.
Cool effort. Use translateY instead of top and you’ll get much, much better performance.
@James: Touching the status bar still reveals the location field.
Ah yes. Then I suppose you could scrollTo(0,1) again on any document touch event. Potential is growing.
Nice work! I’d love to be able to turn fixed positioning on and off with a switch inside the app. How would I go about removing the event listener? This:
document.body.removeEventListener(”touchmove”, function(e) { e.preventDefault(); }, false);
… doesn’t seem to do it.
–Kent
@Use CSS transforms: Thanks guy! I’ve updated my demo and used translateY. It’s silky smooth now!
@Kent Brewster You can only remove named event handlers. So:
var preventBehavior = function(e) {
e.preventDefault();
};
// Enable fixed positioning
document.addEventListener("touchmove", preventBehavior, false);
// Disable fixed positioning
document.removeEventListener("touchmove", preventBehavior, false);
Great tutorial, I am really look for this example. Thank you very much for sharing.
This will reposition after each scroll, which is ok if your using the panel as a control-panel…
window.onscroll=setCtrl;
function setCtrl()
{
var h = document.getElementById(”footer”);
// where 50 is the height of the panel
var h = (window.innerHeight + window.pageYOffset) – 50;
h.style.top = h.toString() + “px”;
}
in iphone 2.1 you can now have fullscreen web apps as long as you add the app as a webclip, im trying to build an app where the content div is continously reused i.e the innerHTMl keeps getting replaced. an example of a problem im having is i replace the content of the content div, then want to inject the UL back into the content div getting the li’s clickable/scrollable. any help would be appreciated.
There’s no need for any of this, actually. The fundamental problem is that you have a viewport which doesn’t match the size of your document. Safari is correct for fixed positioning to be in relation to the document; that’s what separates it from absolute positioning.
The appropriate fix is to add width=device-width in the viewport meta tag argument list. That sets the document width to match the available real estate, and simultaneously implies an initial scale of 1.0. Combine that with preventDefault() to prevent finger scrolling of the page and user-scalable=no to prevent resizing of the page, and you’ve got a winner.
Martiweb: it’s a little hard to understand the problem you’re having, but I think this might be what you want:
.style1 { color: red; } .style2 { color: orange; } .style3 { color: yellow; }
.style4 { color: green; } .style5 { color: blue; }
function ExampleForMarti() {
var TheBody = document.getElementById(’thebody’);
var SomeText = document.createElement(’p');
var SomeList = document.createElement(’ul’);
for (var i=0; i<5; ++i) {
var Item = document.createElement(’li’);
Item.className = ’style’ + i.toString();
SomeList.appendChild(Item);
}
SomeText.innerHTML = ‘I am the very model of a modern major general.’;
TheBody.appendChild(SomeText);
TheBody.appendChild(SomeList);
}
Grr, my code example’s HTML got stripped instead of quoted. That requires a body with the id “thebody”, and obviously those CSS rules go in a style block.
It was there. Really.
On position: fixed
http://www.w3.org/TR/CSS21/visuren.html#choose-position
“In the case of handheld, projection, screen, tty, and tv media types, the box is fixed with respect to the viewport and doesn’t move when scrolled.”!!!!!!!!!
fucking apple should read the fucking standards!
Apple is correct. The iPhone does not present stylesheets for the “handheld” target type (nor should it, given the screen resolution). The iPhone acts on normal browser stylesheets, as it provides normal browser pages, not reduced-version handheld pages.
Handheld is for things like Nintendo DS Opera.
@John Haugeland
You fail, can you read the spec? You know, those who can read are at an advantage right?
http://www.w3.org/TR/CSS2/visuren.html#fixed-positioning
QFT: “… for a fixed positioned box, the containing block is established by the viewport.”
You can claim any amount of BS, and you have, but it’s not going to change what is in the CSS spec.
Florian: you’re a bit angry there. It’s a simple mistake. No need to get nasty.
You may find my solution useful. http://cubiq.org/scrolling-div-on-iphone-ipod-touch/5
As of the “cupcake” branch, this now works in Google Android’s version of Webkit too.
Great article and script – Thanks Richard
I’d like to follow up what James suggested about bringing back the location field.
I’ve tried his suggestion by using scrollTo(0,1) on all touch events but it does not work.
Any ideas how you can get the location field back once you scroll to the top without touching the status bar?
Thanks everyone.
Babs
Thanks for sharing this. I’m trying to make a nice controller for media player daemon on my server and this is a perfect starting point (I’m not at all good a javascript). I’ve found that if I have a short list in the container that has a height less than the distance between the top and bottom bars, the scrolling behaves badly. I hacked it so that it seems to be working by looking at the value of ‘boundary’ in line 219 and only allowing scrolling if it is < 0. Thanks again so much!
Is FlowJS dead? Google has almost nothing about it. Nobody seems to talk about it or use it (or even know it). I like it because it seems very simple (I just want a couple of AJAX calls for now) and small. But I don’t wanna spend time familiarizing myself with something that will keep me behind everybody else…
A noble attempt, though (as to be expected) still buggy as all hell.
Keep at it though, very promising!
Amazing!!! Thanks a lot, u just made my life a whole lot easier!
Hello,
Thank you very much for this script.
I have a small question about the links.
When I use that “Nothing”, no problem.
But how to call a javascript function?
I tried that “Nothing” but it does not work.
Can you help me please?
Sorry there is a problème with de code :
When I use that “Nothing”, no problem.
But how to call a javascript function?
I tried that “Nothing” but it does not work.
To start off, awesome work to everyone. This info has definetely helped me understand the structure behind how the iPhone and safari allows us to view web pages. I have been developing web sites for a long time, but have only recently begun developing mobile apps. Disabling the viewport was exactly what I was looking for when trying to create a fixed area on my webapp, but however awesome this method is, it does still provide so functional problems, especially with the way the viewport is being used (portrait or landscape). So after implementing the info addressed above and many other attempts that have been made public, I began to think if instead of disabling the viewport, is there any way we could actually control it. I see the viewport as more of like a mask that sits on top of the page. I wonder if there is a way we could disable parts of it instead of all of it.. For example, disable it at the top of the page for a fixed header. Enable it in the middle. And then disable it again at the bottom. This could solve the scrolling animation issues. I’m just trying to start a discussion and this seems to be the best place to do it. It may not even be possible, but it wouldn’t hurt to brainstorm. Anyone have any ideas?
I downloaded the demo code and tried it with Palm Pre emulator and Android emulator. It does not work there (?!) Again cross-browser problems? Can anybody say something about it?
Thanks. However what do I need to do so that pressing on one of the fixed-position toolbar buttons actually does something (such as refering to another URL)? From what I can see, pressing on any of the tooldbar buttons only makes it turn blue (”selected” status – but nothing else).
TIA
@Meron
It’s a proof-of-concept. Feel free to modify the code and take it from there.
Hi Doctyper,
Thanks for the quick response!
I must have been distracted – the OBVIOUS answer was to add “onclick” for the footer toolbar buttons’ tag.
But I have a new issue: if I hold your demo horizontally (landscape) the footer toolbar is displayed in the middle of the page (instead of the bottom) and repositioning the iPhone verically (protrait) doesn’t solve the problem – the footer stays in the middle there, too. I know this is just proof-of-concept – but I’d appreciate any suggestions.
Lavie
Thanks for sharing the code. I noticed that bottom tab bar doesn’t get reposition to the bottom after changing the orientation. I want to handle this orientation change event to reposition the footer. Can you please let me know how I can modify the code?
Thanks
Is there a way to stop the rotation on iPhone. I need my website only to be seen on portrait. Can you tell me suggestions!!!!???? Thanks
I tried right now on android 1.6. It also works there. Just a bit slower, but works. Thanks, I need it for multiple platforms.
@MT – yes it looks like the ’scroll’ event gets automatically triggered after the ‘orientationchange’ event has fired. This instantly adds the ’scrolled’ class, which has just been removed, back to the element.
Afraid I can’t see a way round this unless you comment out Ln.127 “window.addEventListener(”orientationchange”, $i.utils.updateOrientation, false);” which would prevent the address bar being hidden when the orientation changes, so the lack of ’scrolled’ class would in fact be correct.
* Correction : Comment Ln.123: “window.addEventListener(”orientationchange”, $i.utils.hideURLBar, false);”
The problem with the “scrolled” class being re-added can be fixed by adding a constraint so that it’s only added if there has been at least, say, 1 second since the last orientation change.
Below is the modified piece of code.
this.orientChangedAt = new Date().getTime();
// Can’t prevent user from tapping status bar
// So instead, readjust fixed positions
window.addEventListener(”scroll”, function() {
$i.utils.addClass(document.body, “scrolled”);
if (new Date().getTime() – this.orientChangedAt < 1000)
$i.utils.removeClass(document.body, "scrolled");
}, false);
// Remove scroll class on orientation change
window.addEventListener("orientationchange", function() {
$i.utils.removeClass(document.body, "scrolled");
this.orientChangedAt = new Date().getTime();
}, false);
Wonderfull demo ….its very much helped me . am a fresher in Iphone programming.. was confused about how this can do… Demo helped me alot………thanks for sharing…..
Its very good post..thanks for sharing
Thanks for the wonderful script. An issue I am running into is that I load some of my “’s” via ajax. This happens AFTER the page has already been loaded. As soon as I remove the “fixed.js” file everything works correct. What I am assuming is that because I am adjusting the content of the “content” div on the fly the fixed.js file is “confused”.
Is there a way to “refresh” your stuff after the content has been added?
Thanks!
12 Trackbacks/Pingbacks
[...] out the rest of the article here, or the source files for the proof-of-concept here. Submit this page to other blogs: These icons [...]
[...] ã“れを回é¿ã™ã‚‹æ–¹æ³•ãŒä¸‹è¨˜ã«ã‚りã¾ã—ãŸã€‚ Fixed positioning in Mobile Safari [...]
[...] I understand that with the zoomy nature of Mobile Safari, fixed positioning just really work. However, what if it were made to work only when zooming is disabled? That should give web developers the option to make better looking web apps without requiring elaborate workarounds. [...]
[...] all the great webkit CSS and JavaScript features that are available for Mobile Safari. There are a number of limitations though (at least as of OS 2.2) that were very frustrating and surprising. In the end, [...]
[...] n Fixed positioning in Mobile Safari | Doctyper [...]
[...] http://doctyper.com/archives/200808/fixed-positioning-on-mobile-safari/ [...]
[...] i forward you this intresting article – i tried it myself, felt a bit slowbut its working [link] [...]
[...] doctyper [...]
[...] Link: Fixed positioning in Mobile Safari | Doctyper [...]
[...] is explained well by Richard Herrera on his blog @ Doctyper: Imagine a book in front of you. Take a piece of paper, cut a 320×416 square in it, and lay it [...]
[...] Positioning / Context (Referred by Sherif [...]
[...] solution alternative existe cependant ce script très intéressant que j’ai déniché et qui émule les actions de navigation en javascript. Toutefois ce [...]
Post a Comment