Mobiscroll positioning



Mobiscroll started out as Date & Time picker simulating native UI controls (like datepicker, timepicker, spinning list etc.) of an OS, which were not possible to invoke in web apps, at least not on every platform.
For some time it supported only one display mode, a modal popup, which appeared in the center of the viewport. But native controls are positioned differently on different platforms:

Timepicker on Android 2.2

Spinning list on iPhone

Datepicker on iPad
So quickly the most requested feature was to be able to position Mobiscroll in different ways.
The first improvement we introduced was the ‘inline’ display mode, giving the possibility to developers to position the scroller where and how they want it (v2.0). But we wanted more. We wanted Mobiscroll to be extremely easy to use and take the burden off the poor developers.
So we decided to implement the following 3 new modes: ‘bottom’, ‘top’ and ‘bubble’. Position ‘top’ and ‘bottom’ imitates the iPhone’s behavior, while ‘bubble’ imitates the iPad’s behavior, where the picker appears in a bubble pointing at the triggering element – this is very useful on larger screens, like tablets or desktops, because you don’t have to move your hand or cursor in a different part of the screen, remaining close to the triggering touch or click.
How it’s made
In the following section I’ll explain how the positioning it’s done in a small tutorial.
The HTML
We will create a few divs and some buttons. #elm is the positioned element, #anchor is the relative element for ‘bubble’ positioning, #controls is a container for the triggering buttons.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"> <title>Positioning</title> <link href="style.css" rel="stylesheet" type="text/css" /> <script src="http://code.jquery.com/jquery-1.9.0.min.js" type="text/javascript"></script> <script src="pos.js" type="text/javascript"></script> </head> <body> <div id="elm"> <button id="hide">Hide</button> </div> <div id="controls"> <button id="top" class="pos">Top</button> <button id="bottom" class="pos">Bottom</button> <button id="center" class="pos">Center</button> <button id="bubble" class="pos">Bubble</button> </div> <div id="anchor"></div> </body> </html> |
The CSS
The body has a large, 2000px height, so we can test our positioning on a scrollable page.
The control container has fixed positioning aligned to left, so it is always visible, no matter where the page is currently scrolled.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | body { height: 2000px; padding: 0; margin: 0; font-family: arial, verdana, sans-serif; font-size: 12px; } #elm { position: absolute; z-index: 10; display: none; width: 300px; height: 100px; padding: 20px 0; background: #ddd; text-align: center; } #controls { position: fixed; top: 50%; left: 0; height: 138px; margin-top: -69px; background: #aaa; } #controls button { display: block; width: 60px; margin: 10px; } #anchor { width: 150px; height: 30px; background: #aaa; position: absolute; top: 500px; right: 40px; } |
Calculating the position
The positioning is done by clicking on the buttons in the controls container. The buttons have the same .pos css class, we will use that to attach the click handler. We will use the clicked button’s id, to decide where to position the element.
We also need to calculate the dimensions of the different elements (see the figures below). Note that we’re using the outerWidth and outerHeight jquery functions, which also includes the padding and borders into the dimensions.
At the end of the handler we apply the calculated values to the element using jquery’s css function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | $('.pos').click(function() { var top, left, width = '', elm = $('#elm').css({ width: '' }).show(), // Reset the width, and show the element anchor = $('#anchor'), scrollTop = $(window).scrollTop(), viewPortWidth = $(window).width(), viewPortHeight = $(window).height(), scrollerWidth = elm.outerWidth(), scrollerHeight = elm.outerHeight(), anchorWidth = anchor.outerWidth(), anchorHeight = anchor.outerHeight(), anchorOffsetTop = anchor.offset().top, anchorOffsetLeft = anchor.offset().left; switch (this.id) { case 'center': // ... // See calculations in the next section break; case 'top': // ... // See calculations in the next section break; case 'bottom': // ... // See calculations in the next section break; case 'bubble': // ... // See calculations in the next section break; } // Apply the calculated values to the element elm.css({ width: width, top: top, left: left }); }); |
1. Centered
The element is positioned at the center of the viewport.
1 2 | top = scrollTop + (viewPortHeight - scrollerHeight) / 2; left = (viewPortWidth - scrollerWidth) / 2; |
2. Top
The element is displayed at the top of the viewport in full width.
1 2 3 | width = '100%'; top = scrollTop; left = 0; |
3. Bottom
The element is displayed at the bottom of the viewport in full width.
1 2 3 4 | width = '100%'; top = scrollTop + viewPortHeight - scrollerHeight; top = top < 0 ? 0 : top // Don't go out of screen left = 0; |
4. Bubble
The element is positioned relative to an anchor element (by default the input element on which the scroller was called), with an arrow pointing at this element. Positioning decides to display the element above or below the anchor element.
1 2 3 4 5 6 7 8 9 10 11 | top = anchorOffsetTop - scrollerHeight - 3; // above anchor element left = anchorOffsetLeft - (scrollerWidth - anchorWidth) / 2; // If there’s not enough space above the anchor element, or anchor element is out of screen, position below anchor element: if ((top < scrollTop) || (anchorOffsetTop > scrollTop + viewPortHeight)) { top = anchorOffsetTop + anchorHeight + 3; } // Keep a minimum 20px margin from left and right: left = left > (viewPortWidth - scrollerWidth - 20) ? (viewPortWidth - scrollerWidth - 20) : left; left = left < 20 ? 20 : left; |
Hiding the popup
The positioned element contains a #hide button, so we need to attach a click event handler to the button, to hide the element on button click.
1 2 3 | $('#hide').click(function() { $('#elm').hide(); }); |
Summary
Download the Source Files
Play with it on jsFiddle
The actual code that is used for positioning in Mobiscroll can be found in the position function of mobiscroll.core.js
More eye-candy
On the top of that we created the animation module, to make it more awesome. Beside the slide up / slide down animations known from iOS, we also added pop and fade, and the cool 3d flip and swing effects.