mobile-viewport-control
Advanced tools
Comparing version 0.3.0 to 0.3.1
74
index.js
// | ||
// Mobile Viewport Control v0.3.0 | ||
// Mobile Viewport Control v0.3.1 | ||
// | ||
@@ -7,3 +7,3 @@ // Copyright (c) 2016 Shaun Williams | ||
// | ||
// GitHub: https://github.com/shaunstripe/mobile-viewport-control | ||
// GitHub: https://github.com/stripe/mobile-viewport-control | ||
// | ||
@@ -39,3 +39,10 @@ | ||
function setScroll(scroll) { | ||
window.scrollTo(scroll.left, scroll.top); | ||
if (window.scrollTo) { | ||
window.scrollTo(scroll.left, scroll.top); | ||
} else { | ||
document.documentElement.scrollTop = scroll.top; | ||
document.documentElement.scrollLeft = scroll.left; | ||
document.body.scrollTop = scroll.top; | ||
document.body.scrollLeft = scroll.left; | ||
} | ||
} | ||
@@ -313,4 +320,3 @@ | ||
function thawIOS(hook, initial, onDone) { | ||
function thawWebkit(hook, initial, onDone) { | ||
// Restore the user's manual zoom. | ||
@@ -342,6 +348,41 @@ hook.setAttribute('content', [ | ||
function thawAndroid(hook, initial, onDone) { | ||
function thawGecko(hook, initial, onDone) { | ||
// Restore the user's manual zoom. | ||
hook.setAttribute('content', [ | ||
'initial-scale='+originalScale, | ||
'minimum-scale='+originalScale, | ||
'maximum-scale='+originalScale | ||
].join(',')); | ||
// Updating the scroll here is too early, | ||
// but it's used to force a refresh of the viewport | ||
// with our current desired scale. | ||
setScroll(originalScroll); | ||
setTimeout(function(){ | ||
// Restore the page's zoom bounds. | ||
hook.setAttribute('content', [ | ||
'user-scalable='+initial['user-scalable'], | ||
'minimum-scale='+initial['minimum-scale'], | ||
'maximum-scale='+initial['maximum-scale'], | ||
(initial.width ? 'width='+initial.width : null) | ||
].filter(Boolean).join(',')); | ||
// Restore the scroll again now that the scale is correct. | ||
setScroll(originalScroll); | ||
// Remove our meta viewport hook. | ||
document.head.removeChild(hook); | ||
if (onDone) | ||
onDone(); | ||
}, refreshDelay); | ||
} | ||
function thawBlink(hook, initial, onDone) { | ||
hook.setAttribute('content', [ | ||
'user-scalable='+initial['user-scalable'], | ||
'initial-scale='+originalScale, | ||
// WebView does not support this: | ||
//'initial-scale='+originalScale | ||
'initial-scale='+initial['initial-scale'], | ||
'minimum-scale='+initial['minimum-scale'], | ||
@@ -378,13 +419,10 @@ 'maximum-scale='+initial['maximum-scale'], | ||
// thaw function defaults to webkit | ||
var thawFunc = thawWebkit; | ||
var os = getMobileOS(); | ||
if (os === 'iOS') { | ||
thawIOS(hook, initial, onDone); | ||
} | ||
else if (os === 'Android') { | ||
thawAndroid(hook, initial, onDone); | ||
} | ||
else { | ||
// For other browsers, we just try Android's method. | ||
thawAndroid(hook, initial, onDone); | ||
} | ||
if (os === 'Android') { thawFunc = isFirefox() ? thawGecko : thawBlink; } | ||
else if (os === 'iOS') { thawFunc = thawWebkit; } | ||
// call appropriate thaw function | ||
thawFunc(hook, initial, onDone); | ||
} | ||
@@ -404,3 +442,3 @@ | ||
// stable | ||
version: '0.3.0', | ||
version: '0.3.1', | ||
freeze: freeze, | ||
@@ -407,0 +445,0 @@ thaw: thaw |
{ | ||
"name": "mobile-viewport-control", | ||
"version": "0.3.0", | ||
"version": "0.3.1", | ||
"description": "Dynamically control the mobile viewport", | ||
@@ -8,6 +8,9 @@ "main": "index.js", | ||
"type": "git", | ||
"url": "git+https://github.com/shaunstripe/mobile-viewport-control.git" | ||
"url": "git+https://github.com/stripe/mobile-viewport-control.git" | ||
}, | ||
"files": [ | ||
"index.js" | ||
], | ||
"author": "Shaun Williams", | ||
"license": "ISC" | ||
} |
209
README.md
@@ -0,23 +1,96 @@ | ||
> This library is under active development. Please [email us](mailto:shaun@stripe.com) if you’re ready to use it in a production environment. | ||
# Mobile Viewport Control | ||
On mobile browsers, Apple set a non-standardized precedent of controlling the | ||
viewport with one or more meta tags: | ||
This JavaScript library attempts to solve the hard problem of creating a | ||
__[full-window modal]__ experience for mobile browsers. The goal is to allow | ||
it to be dropped into any page regardless of its viewport settings. It does | ||
this by attempting to control the mobile viewport and by hiding all elements | ||
except the one you are trying to modalize. | ||
```html | ||
<meta name="viewport" content="name=value,name=value,..."> | ||
``` | ||
[full-window modal]:http://uxmovement.com/mobile/why-you-should-avoid-using-modal-windows-on-mobile/ | ||
> See Quirksmode for more context: [1], [2] | ||
-- | ||
<table> | ||
<tr> | ||
<td>[![scroll][scroll-gif]][scroll-gfy]</td> | ||
<td>[![zoom][zoom-gif]][zoom-gfy]</td> | ||
</tr> | ||
<tr> | ||
<td>__Figure 1.__ Scrolling</td> | ||
<td>__Figure 2.__ Zooming + Scrolling</td> | ||
</tr> | ||
</table> | ||
-- | ||
__Background.__ The _viewport_ is the visible part of the webpage on your | ||
screen (see quirksmode [[1]] [[2]]). It is controlled by two things: _scroll_ | ||
and _zoom_. The user can swipe to scroll and pinch to zoom. A JavaScript | ||
developer can control the scroll programmatically, but there is no officially | ||
supported method for controlling the _zoom_ programmatically. However, we do | ||
have a de facto standard for limiting and initializing the zoom: | ||
[1]:http://www.quirksmode.org/mobile/viewports2.html | ||
[2]:http://www.quirksmode.org/mobile/metaviewport/ | ||
The goal of this project is to see if we can use these tags to dynamically | ||
modify the viewport at runtime. Specifically, we want to freeze the viewport at | ||
some zoom level, then restore the viewport later. | ||
```html | ||
<head> | ||
<meta name="viewport" content="initial-scale=1"> | ||
<meta name="viewport" content="minimum-scale=0"> | ||
<meta name="viewport" content="maximum-scale=10"> | ||
</head> | ||
``` | ||
Generally, a user should be able to pinch-zoom a page. But for things like | ||
popup modals, the user experience is better if the viewport is preset like a | ||
native app. | ||
__Workaround.__ There is an undocumented feature that allows us to modify the | ||
viewport state at runtime by adding and modifying viewport tags. There are | ||
caveats depending on the browser. The goal of this library is to identify and | ||
workaround these caveats until there is a standard for programmatically | ||
controlling zoom. We have researched and tested on multiple platforms, though | ||
we are currently focusing support for Mobile Safari and WebViews on iOS 7, 8, and 9. | ||
(See __[Compatibility Testing](test/)__) | ||
__Modals are the Use-Case.__ Viewport settings vary widely across different | ||
webpages, making it hard to create drop-in modal experiences that work | ||
everywhere (see [Stripe Checkout] and [Auth0 Lock]). For browsers that cannot | ||
create pop-up tabs and for pages where redirection is not an option, this | ||
library can help fill in the gaps. | ||
> UPDATE: See the upcoming _[Payment Request API]_ for native mobile checkout modals for the web. | ||
[Stripe Checkout]:https://stripe.com/checkout | ||
[Auth0 Lock]:https://auth0.com/lock | ||
[Payment Request API]:https://developers.google.com/web/updates/2016/07/payment-request | ||
-- | ||
<table> | ||
<tr> | ||
<td>[![freeze][freeze-gif]][freeze-gfy]</td> | ||
<td>[![isolate][isolate-gif]][isolate-gfy]</td> | ||
</tr> | ||
<tr> | ||
<td>__Figure 3.__ Freezing and Thawing</td> | ||
<td>__Figure 4.__ Isolating the Modal</td> | ||
</tr> | ||
</table> | ||
-- | ||
__The Abstraction.__ This library provides a limited abstraction for | ||
controlling zoom: _freezing_ and _thawing_. (See Figure 3). Freezing will lock | ||
the zoom to a given scale. Thawing will restore the viewport to what it was | ||
before freezing, while also restoring the appropriate zoom limits (i.e. min and | ||
max). Thawing in this way is useful in the context of a modal. | ||
__Isolating the Modal.__ The freeze/thaw functions are designed to be used in | ||
conjunction with a third operation: _isolation_. (See Figure 4). This is done | ||
by passing an optional element ID to the freeze function, which you would | ||
presumably wish to be the full-window modal. This temporarily applies CSS | ||
rules to ensure with high confidence that all elements except the given element | ||
are collapsed (via `display:none`). You can then style your element to fill | ||
the window as a modal. Thawing will restore the page back to what it was | ||
before. | ||
## Usage | ||
@@ -50,106 +123,22 @@ | ||
## Compatibility Testing | ||
## Testing | ||
> __NOTE__: We do not test with Simulators, as iOS Simulator has been shown to | ||
> produce different results than the real environment. | ||
Please see __[Compatibility Testing](test/)__. | ||
Compatibility is measured with a combination of automatic/manual testing: | ||
## License | ||
1. __[Measure Test]__ - verify that we can measure the current viewport scale. | ||
1. __[Freeze Test]__ - verify that we can set and freeze the viewport scale. | ||
1. Manual step - pinch-zoom before starting the test | ||
1. Manual step - verify that you cannot pinch-zoom after the test | ||
1. __[Thaw Test]__ - verify that we can restore the viewport scale and bounds. | ||
1. Manual step - pinch-zoom before starting the test | ||
1. Manual step - verify that you can still pinch-zoom after the test | ||
1. __[Injection Method]__ - verify it works for any webpage by running a bookmarklet test to inject JS into the page | ||
[ISC License](LICENSE) | ||
| Mobile Browser | [Measure Test]\* | [Freeze Test] | [Thaw Test] | [Injection Method] | | ||
|----------------------------|------------------|---------------|--------------------|--------------------------| | ||
| iOS Safari | Y | Y | Y | devtools | | ||
| iOS UIWebView | Y | Fails\*\* | Y if freeze works. | xcode+devtools | | ||
| iOS WKWebView | Y | Y | Y | xcode+devtools | | ||
| iOS SFSafariViewController | Y | Y | Y | | | ||
| iOS Chrome | Y | Y | Y | bookmarklet | | ||
| iOS Firefox | Y | Y | Y | | | ||
| iOS Opera Mini | Y | Fails\*\* | Y if freeze works. | | | ||
| Android Browser (Stock) | ? | ? | ? | | | ||
| Android Chrome | Y | Y | Y | devtools or bookmarklet | | ||
| Android WebView | Y | Y | Y | devtools | | ||
| Android Chrome Custom Tabs | Y | Y | Y | devtools | | ||
| Android Firefox | Y | Fails | Fails | devtools or bookmarklet | | ||
| Android Opera Mini | Fails | Fails | Fails | | | ||
-- | ||
_\* This test fails in the iOS Simulator because `initial-scale` is ignored | ||
there for wide pages for some reason._ | ||
_Source code for the animated figures above: [mobile-viewport-control-viz](https://github.com/shaun-stripe/mobile-viewport-control-viz)_ | ||
_\*\* Fails if user pinch-zooms before freezing. Pinch-zooming causes the | ||
page's scale to change from its specified `initial-scale`. This custom zoom | ||
level is maintained across refreshes. When opening in a new tab, the | ||
`initial-scale` is resumed._ | ||
[Measure Test]:http://shaunstripe.github.io/mobile-viewport-control/test/01-measure.html | ||
[Freeze Test]:http://shaunstripe.github.io/mobile-viewport-control/test/02-freeze.html | ||
[Thaw Test]:http://shaunstripe.github.io/mobile-viewport-control/test/03-thaw.html | ||
[Injection Method]:#injecting-into-existing-pages | ||
### Injecting into Existing Pages | ||
To allow us to quickly gauge compatibility for externally owned webpages | ||
across several browsers, we must be able to inject our library code into | ||
their running pages. | ||
##### With Bookmarklet | ||
Bookmarklets are URLs of the form `javascript:(function(){ ... }())` which some | ||
browsers allow for evaluating JS in the context of the current page. The | ||
mobile browsers compatible are listed in the above table under the Injection | ||
Method column. | ||
The following bookmarklet will freeze the viewport scale, show a custom | ||
isolated element, and allow you to press a button to restore the view. | ||
```js | ||
javascript:(function(){document.body.appendChild(document.createElement('script')).src='https://rawgit.com/shaunstripe/mobile-viewport-control/master/bookmarklet/index.js?'+Math.random();}()) | ||
``` | ||
##### With DevTools | ||
Some mobile browsers allow you to connect to a Desktop DevTools environment | ||
with a JS console for evaluating JS in the context of its page. iOS Safari | ||
supports connecting to Desktop Safari DevTools in this way. Android | ||
Chrome/webview can connect to Desktop Chrome DevTools. Inside DevTools, we can | ||
simply paste the body of the bookmarklet inside the JS console: | ||
```js | ||
document.body.appendChild(document.createElement('script')).src='https://rawgit.com/shaunstripe/mobile-viewport-control/master/bookmarklet/index.js?'+Math.random(); | ||
``` | ||
iOS webviews require an extra step: you must run an webview app from a live | ||
XCode project on your mac. Only then will Desktop Safari DevTools to connect | ||
to its webview. | ||
### Variables | ||
We currently do not test all variables, but the test outcomes depend on the following: | ||
- browser/platform | ||
- page width (modify in `test.css`) | ||
- screen orientation (portrait or landscape) | ||
- refresh delay (for viewport changes to take effect) | ||
- initial zoom, influenced by either of: | ||
- default zoom specified in initial-scale | ||
- manual zoom remembered before page refresh | ||
- manual zoom after page load and before test run | ||
- initial zoom bounds (controlled by page's original viewport meta tags) | ||
### Quirks when Dynamically Modifying Viewport | ||
- iOS UIWebView does not allow scale adjustments after the user has manually adjusted it. | ||
- Android does not register a new viewport meta tag if it is immediately removed after creation. | ||
- Android will modify the scroll some time after a new viewport tag is registered. | ||
- Android (at least in Chrome) will not freeze scale at 1.0, but will at 1.0±ε for some ε>0. | ||
## License | ||
[ISC License](LICENSE) | ||
[scroll-gif]:https://zippy.gfycat.com/EnchantedPowerfulIndri.gif | ||
[scroll-gfy]:https://gfycat.com/EnchantedPowerfulIndri | ||
[zoom-gif]:https://zippy.gfycat.com/InconsequentialImperturbableBuckeyebutterfly.gif | ||
[zoom-gfy]:https://gfycat.com/InconsequentialImperturbableBuckeyebutterfly | ||
[freeze-gif]:https://fat.gfycat.com/YearlyBitesizedBangeltiger.gif | ||
[freeze-gfy]:https://gfycat.com/YearlyBitesizedBangeltiger | ||
[isolate-gif]:https://fat.gfycat.com/MetallicFrighteningApisdorsatalaboriosa.gif | ||
[isolate-gfy]:https://gfycat.com/MetallicFrighteningApisdorsatalaboriosa |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
0
0
18938
4
378
144