mobile-viewport-control
Advanced tools
Comparing version 0.2.0 to 0.3.0
# Changes | ||
## 0.3.0 | ||
- iOS - zoom bounds restored on thaw | ||
- UIWebView - partial freeze support (double-tap or pinch-zoom auto-zooms) | ||
- Android Firefox - scroll fixed on thaw | ||
- Android Firefox - page width fixed on freeze | ||
- Android Chrome/WebView - scroll/scale fixed on thaw | ||
## 0.2.0 | ||
@@ -4,0 +12,0 @@ |
209
index.js
// | ||
// Mobile Viewport Control v0.2.0 | ||
// Mobile Viewport Control v0.3.0 | ||
// | ||
@@ -32,4 +32,4 @@ // Copyright (c) 2016 Shaun Williams | ||
return { | ||
left: document.body.scrollLeft, | ||
top: document.body.scrollTop, | ||
top: window.pageYOffset || document.documentElement.scrollTop, | ||
left: window.pageXOffset || document.documentElement.scrollLeft | ||
}; | ||
@@ -39,4 +39,3 @@ } | ||
function setScroll(scroll) { | ||
document.body.scrollLeft = scroll.left; | ||
document.body.scrollTop = scroll.top; | ||
window.scrollTo(scroll.left, scroll.top); | ||
} | ||
@@ -49,9 +48,14 @@ | ||
function getInitialViewport() { | ||
// These seem to be the defaults | ||
var viewport = { | ||
'user-scalable': 'yes', | ||
'minimum-scale': '0', | ||
'maximum-scale': '10' | ||
}; | ||
function getInitialViewport(withDefaults) { | ||
var viewport = {}; | ||
if (withDefaults) { | ||
// These seem to be the defaults | ||
viewport = { | ||
'user-scalable': 'yes', | ||
'minimum-scale': '0', | ||
'maximum-scale': '10' | ||
}; | ||
} | ||
var tags = document.querySelectorAll('meta[name=viewport]'); | ||
@@ -75,2 +79,21 @@ var i,j,tag,content,keyvals,keyval; | ||
function getPrettyInitialViewport() { | ||
var initial = getInitialViewport(); | ||
var keyvals = []; | ||
for (var prop in initial) { | ||
if (initial.hasOwnProperty(prop)) { | ||
keyvals.push({key:prop, val:initial[prop]}); | ||
} | ||
} | ||
return ( | ||
keyvals.sort(function(a,b) { | ||
if (a.key < b.key) return -1; | ||
if (a.key > b.key) return 1; | ||
return 0; | ||
}).map(function(kv) { | ||
return kv.key + '=' + kv.val; | ||
}).join(',\n') | ||
); | ||
} | ||
//--------------------------------------------------------------------------- | ||
@@ -104,3 +127,26 @@ // Calculating current viewport scale | ||
//--------------------------------------------------------------------------- | ||
// Get mobile OS | ||
// from: http://stackoverflow.com/a/21742107 | ||
//--------------------------------------------------------------------------- | ||
function getMobileOS() { | ||
var userAgent = navigator.userAgent || navigator.vendor || window.opera; | ||
if (userAgent.match(/iPad/i) || | ||
userAgent.match(/iPhone/i) || | ||
userAgent.match(/iPod/i)) { | ||
return 'iOS'; | ||
} | ||
else if (userAgent.match(/Android/i)) { | ||
return 'Android'; | ||
} | ||
} | ||
function isFirefox() { | ||
var userAgent = navigator.userAgent || navigator.vendor || window.opera; | ||
return userAgent.match(/Firefox/i) ? true : false; | ||
} | ||
//--------------------------------------------------------------------------- | ||
// State and Constants | ||
@@ -201,3 +247,3 @@ //--------------------------------------------------------------------------- | ||
//--------------------------------------------------------------------------- | ||
// Freezing and Thawing | ||
// Freezing | ||
//--------------------------------------------------------------------------- | ||
@@ -220,12 +266,21 @@ | ||
// save original viewport state | ||
originalScroll = getScroll(); | ||
originalScale = getScale(); | ||
// isolate element if needed | ||
if (isolateID) { | ||
isolate(isolateID); | ||
setScroll({x:0,y:0}); | ||
} | ||
// freeze viewport | ||
// validate scale | ||
// (we cannot freeze scale at 1.0 on Android) | ||
if (scale === 1) { | ||
scale = 1.002; | ||
} | ||
// add our new meta viewport tag | ||
var hook = document.getElementById(hookID); | ||
if (!hook) { | ||
originalScale = getScale(); | ||
originalScroll = getScroll(); | ||
hook = document.createElement('meta'); | ||
@@ -236,8 +291,19 @@ hook.id = hookID; | ||
} | ||
// When freezing the viewport, we still enable | ||
// user-scalability and allow a tight zooming | ||
// margin. Without this, UIWebView would simply | ||
// ignore attempts to set the scale. But with this | ||
// solution, the next time the user pinch-zooms | ||
// in this state, the viewport will auto-snap | ||
// to our scale. | ||
var includeWidth = (getMobileOS() === 'Android' && isFirefox()); | ||
hook.setAttribute('content', [ | ||
'user-scalable=no', | ||
'user-scalable=yes', | ||
'initial-scale='+scale, | ||
'minimum-scale='+scale, | ||
'maximum-scale='+scale | ||
].join(',')); | ||
'maximum-scale='+(scale+0.004), | ||
(includeWidth ? 'width=device-width' : null) | ||
].filter(Boolean).join(',')); | ||
@@ -249,19 +315,8 @@ if (onDone) { | ||
// Thaw the viewport, restoring the scale and scroll to what it | ||
// was before freezing. | ||
function thaw(onDone, testEvts) { | ||
// restore body visibility | ||
var style = document.getElementById(styleID); | ||
if (style) { | ||
undoIsolate(); | ||
} | ||
//--------------------------------------------------------------------------- | ||
// Thawing | ||
//--------------------------------------------------------------------------- | ||
// exit if there is nothing to thaw | ||
var hook = document.getElementById(hookID); | ||
if (!hook) { | ||
return; | ||
} | ||
function thawIOS(hook, initial, onDone) { | ||
var initial = getInitialViewport(); | ||
// Restore the user's manual zoom. | ||
@@ -271,37 +326,70 @@ hook.setAttribute('content', [ | ||
'minimum-scale='+originalScale, | ||
'maximum-scale='+originalScale, | ||
'maximum-scale='+originalScale | ||
].join(',')); | ||
// 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(',')); | ||
// Remove our meta viewport hook. | ||
document.head.removeChild(hook); | ||
setScroll(originalScroll); | ||
setTimeout(function() { | ||
if (testEvts && testEvts.onRestoreScale) | ||
testEvts.onRestoreScale(); | ||
if (onDone) | ||
onDone(); | ||
}, refreshDelay); | ||
} | ||
// Restore the page's zoom bounds. | ||
hook.setAttribute('content', [ | ||
(initial.width ? ('width=' + initial.width) : null), | ||
'user-scalable='+initial['user-scalable'], | ||
'initial-scale='+originalScale, | ||
'minimum-scale='+initial['minimum-scale'], | ||
'maximum-scale='+initial['maximum-scale'] | ||
].filter(Boolean).join(',')); | ||
function thawAndroid(hook, initial, onDone) { | ||
hook.setAttribute('content', [ | ||
'user-scalable='+initial['user-scalable'], | ||
'initial-scale='+originalScale, | ||
'minimum-scale='+initial['minimum-scale'], | ||
'maximum-scale='+initial['maximum-scale'], | ||
(initial.width ? 'width='+initial.width : null) | ||
].filter(Boolean).join(',')); | ||
setTimeout(function(){ | ||
if (testEvts && testEvts.onRestoreBounds) | ||
testEvts.onRestoreBounds(); | ||
setScroll(originalScroll); | ||
// Remove our meta viewport hook. | ||
document.head.removeChild(hook); | ||
setTimeout(function(){ | ||
document.head.removeChild(hook); | ||
if (onDone) | ||
onDone(); | ||
}, refreshDelay); | ||
} | ||
// Updating the viewport can change scroll, | ||
// so we have to do this last. | ||
setScroll(originalScroll); | ||
// Thaw the viewport, restoring the scale and scroll to what it | ||
// was before freezing. | ||
function thaw(onDone) { | ||
// restore body visibility | ||
var style = document.getElementById(styleID); | ||
if (style) { | ||
undoIsolate(); | ||
} | ||
if (testEvts && testEvts.onRestoreScroll) | ||
testEvts.onRestoreScroll(); | ||
// exit if there is nothing to thaw | ||
var hook = document.getElementById(hookID); | ||
if (!hook) { | ||
return; | ||
} | ||
if (onDone) | ||
onDone(); | ||
var initial = getInitialViewport(true); | ||
}, refreshDelay); | ||
}, refreshDelay); | ||
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); | ||
} | ||
} | ||
@@ -315,2 +403,3 @@ | ||
getInitialViewport: getInitialViewport, | ||
getPrettyInitialViewport: getPrettyInitialViewport, | ||
getScale: getScale, | ||
@@ -321,3 +410,3 @@ isolate: isolate, | ||
// stable | ||
version: '0.2.0', | ||
version: '0.3.0', | ||
freeze: freeze, | ||
@@ -324,0 +413,0 @@ thaw: thaw |
{ | ||
"name": "mobile-viewport-control", | ||
"version": "0.2.0", | ||
"version": "0.3.0", | ||
"description": "Dynamically control the mobile viewport", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -50,14 +50,7 @@ # Mobile Viewport Control | ||
## Try it on Existing Web Pages | ||
## Compatibility Testing | ||
Test on arbitrary web pages with the following bookmarklet. It will freeze | ||
the viewport scale, show a custom isolated element, and allow you to press | ||
a button to restore the view. | ||
> __NOTE__: We do not test with Simulators, as iOS Simulator has been shown to | ||
> produce different results than the real environment. | ||
``` | ||
javascript:(function(){document.body.appendChild(document.createElement('script')).src='https://cdn.rawgit.com/shaunstripe/mobile-viewport-control/master/index.js?'+Math.random();document.body.appendChild(document.createElement('script')).src='https://cdn.rawgit.com/shaunstripe/mobile-viewport-control/master/test/bookmarklet.js?'+Math.random();}()) | ||
``` | ||
## Compatibility | ||
Compatibility is measured with a combination of automatic/manual testing: | ||
@@ -72,18 +65,19 @@ | ||
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 | ||
| Mobile Browser | [Measure Test]\* | [Freeze Test] | [Thaw Test] | | ||
|----------------------------|------------------|-----------------------------------------------|-----------------------| | ||
| iOS Safari | Y | Y | Y | | ||
| iOS UIWebView | Y | Fails if user pinch-zooms before freezing\*\* | Y if freeze succeeds. | | ||
| iOS WKWebView | Y | Y | Y | | ||
| iOS SFSafariViewController | Y | Y | Y | | ||
| iOS Chrome | Y | Y | Y | | ||
| iOS Firefox | Y | Y | Y | | ||
| iOS Opera Mini | Y | Fails if user pinch-zooms before freezing\*\* | Y if freeze succeeds. | | ||
| Android Browser (Stock) | ? | ? | ? | | ||
| Android Chrome | Y | Y | Y | | ||
| Android WebView | Y | Y | Y | | ||
| Android Chrome Custom Tabs | Y | Y | Y | | ||
| Android Firefox | Y | Fails | Fails | | ||
| Android Opera Mini | Fails | Fails | Fails | | ||
| 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 | | | ||
@@ -93,5 +87,6 @@ _\* This test fails in the iOS Simulator because `initial-scale` is ignored | ||
_\*\* 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._ | ||
_\*\* 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._ | ||
@@ -101,3 +96,40 @@ [Measure Test]:http://shaunstripe.github.io/mobile-viewport-control/test/01-measure.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 | ||
@@ -117,4 +149,11 @@ | ||
### 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) |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
46360
19
830
155
2
1