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.




16 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.
2 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 […]
Post a Comment