The screen.height I am talking about is described in https://www.w3schools.com/jsref/prop_screen_height.asp
I used screen.height < 560 ? true : false to determine whether the screen height is smaller than a threshold, so I can hide some UI elements in this case.
It works fine in Chrome's simulator for mobile devices (the feature highlighted below).
By "works fine", I mean when simulating a mobile device, like setting device to be iPhone X as shown above and displaying in landscape mode, the UI elements are hidden correctly due to screen.height < 560 = true.
However, on real mobile devices like a real iPhone X, the UI elements don't get hidden, which I guess is because that it is always screen.height < 560 = false, even if it is in landscape mode.
I am wondering why is that... Why iPhone X in DevTool has a different height from a real iPhone X?
Is the simulation in Chrome DevTool not accurate? Or is it because screen.height doesn't return the correct value on mobile device?
Any hints would be appreciated!
That's because the simulator takes the screen size according to the dimensions that you are setting there. But in reality, screen.height takes the height size of the whole screen, including elements that are outside of the viewport in the device. You should use window.innerHeight to get an accurate height size.
If you log in your console screen.height and window.innerHeight on the simulator, you will get the same size. If you do this in the normal viewport (deactivating the simulator), you will get different values.
More info: Screen Height - Window InnerHeight
UPDATE
screen.height doesn't update on screen rotation, always has the same value corresponding to the screen height in portrait mode, while window.innerHeight takes the current height of the device window either portrait or landscape. Just make sure to fire this in the event when the rotation happens.
For this, you could use the Window.matchMedia() like so:
// Breakpoints
const breakpoint = window.matchMedia('(max-height: 560px)');
// Breakpoint checker
const breakpointMutations = () => {
if (breakpoint.matches === true) {
// Do something
}
}
// Run breakpoint checker if media changes
breakpoint.onchange = function (event) {
breakpointMutations();
}
// Run breakpoint checker on load
breakpointMutations();
It might be because you are missing the response meta tag. Try adding this to your head tag:
<meta name="viewport" content="width=device-width, initial-scale=1">
How do I get the height of the address bar in JavaScript in the Chrome browser for Android (marked by red rectangle in left picture)? I need to know that as it disappears while scrolling down and I need to react to that because the viewport height is different then.
One solution I already figured out:
Get viewport height at initial state:
var height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
Get viewport height when the address bar has disappeared
Compute difference between both values
Problem is that you have to be in the second state to know that.
Because 100vh will be larger than the visible height when the URL bar is shown. According to this.
You can calculate the height of the URL bar by creating a 0-width element with 100vh height.
<div id="control-height"></div>
#control-height {
height: 100vh;
width: 0;
position: absolute;
}
Then using javascript compare window.innerHeight with the height of this element.
const actualHeight = window.innerHeight;
const elementHeight = document.querySelector('#control-height').clientHeight;
const barHeight = elementHeight - actualHeight;
The thing you're are looking for is url bar resizing. Since Android's chrome v56, it's recommended by David Bokan to use vh unit on mobile. There is a demo in that article, clicks the link to get more informations and how to use it on mobile.
When the user is scrolling down the page, a window.resize event is throwed.
You could update your page by catching this event with an event listener.
More informations : mobile chrome fires resize event on scroll
Best approach for me was to have something like that:
$(document).ready(function(){
var viewportHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
var viewportWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
var isPortrait = viewportHeight > viewportWidth;
$( window ).resize(onresize);
function onresize() {
var newViewportHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
var newViewportWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
var hasOrientationChanged = (newViewportHeight > newViewportWidth) != isPortrait;
var addressbarHeight = 130;
if (!hasOrientationChanged && (newViewportHeight != viewportHeight)) {
addressbarHeight = Math.abs(newViewportHeight - viewportHeight);
if (newViewportHeight < viewportHeight) {
// Android Chrome address bar has appeared
} else {
// Android Chrome address bar has disappeared
}
} else if(hasOrientationChanged) {
// Orientation change
}
viewportHeight = newViewportHeight;
viewportWidth = newViewportWidth;
isPortrait = viewportHeight > viewportWidth;
}
});
Had the same issue today, turns out there is no easy way to figure out the height of the url bar directly. As far as I know, none of the directly accessible variables in javascript can tell you how much the size of "100vh" really is.
On mobile browsers, 100vh may or may not include the height of the url bar, which leaves us in a tricky situation, if we want to size a div to the exact height of the visible content area of the browser during load.
I figured out a workaround though that worked pretty neat for me, here's what I did:
add a dummy property on your html root element with a size of 100vh. In my case, i used the "perspective" attribute, which worked for me
then you can get the address bar size with the following code:
var addressBarSize = parseFloat(getComputedStyle(document.documentElement).perspective) - document.documentElement.clientHeight
I had a container with dynamic content that had to always have at least viewport's full height (and be scrollable if the content doesnt fit on the screen).
So if you need a fixed height, just replace "min-height" with "height" in my solution.
That's how I dealt with it.
// calculate min-height on init
$(".content-container").css("min-height", `${window.innerHeight}px`);
// recalculate the min-height everytime the bar appears or disappears
$(window).resize(() => {
$(".content-container").css("min-height", `${window.innerHeight}px`);
});
It works for android's address bar and also safari's bars (in safari mobile there can be top and the bottom bar aswell).
Then to make the transition smooth, you can apply a css rule:
.content-container{
transition: min-height 500ms ease;
}
I use viewport in my web app, which enables scaling:
<meta name="viewport" content="width=device-width, initial-scale=1.0 ,maximum-scale=2.0" />
After zoom-in and zoom-out, I need to know what pixels are shown on the screen.
for example, when I use initial display I see 0 (div left) - 1024 (div right) pixels of my div (width), but after zoom-in, the area is different, because the div is "wider" and I see only a part of it.
Is there any way to calculate this data?
I tried using the scrollerLeft parameter (after zoom it is not "0"), but it returns 0 (seems like not updating).
you will get the width of the div
JS Fiddle
$(window).resize(function () {
findwidth()
})
findwidth()
function findwidth() {
var a = $('.left').width()
var b = $('.right').width()
var c = $(window).width()
$('.width1').html(a)
$('.width2').html(b)
$('.full').html(c)
}
I'm working on a website that fits within a specific width and height (an 885x610 div with a 1px border and 3px top margin). I would like the user to never have to scroll or zoom in order to see the entire div; it should always be fully visible. Since devices have a wide variety of resolutions and aspect ratios, the idea that came to mind was to set the "viewport" meta tag dynamically with JavaScript. This way, the div will always be the same dimensions, different devices will have to be zoomed differently in order to fit the entire div in their viewport. I tried out my idea and got some strange results.
The following code works on the first page load (tested in Chrome 32.0.1700.99 on Android 4.4.0), but as I refresh, the zoom level changes around. Also, if I comment out the alert, it doesn't work even on the first page load.
Fiddle
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0">
<script type="text/javascript">
function getViewportWidth() {
if (window.innerWidth) {
return window.innerWidth;
}
else if (document.body && document.body.offsetWidth) {
return document.body.offsetWidth;
}
else {
return 0;
}
}
function getViewportHeight() {
if (window.innerHeight) {
return window.innerHeight;
}
else if (document.body && document.body.offsetHeight) {
return document.body.offsetHeight;
}
else {
return 0;
}
}
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)) {
var actual_width = getViewportWidth();
var actual_height = getViewportHeight();
var min_width = 887;
var min_height = 615;
var ratio = Math.min(actual_width / min_width, actual_height / min_height);
if (ratio < 1) {
document.querySelector('meta[name="viewport"]').setAttribute('content', 'initial-scale=' + ratio + ', maximum-scale=' + ratio + ', minimum-scale=' + ratio + ', user-scalable=yes, width=' + actual_width);
}
}
alert(document.querySelector('meta[name="viewport"]').getAttribute('content'));
</script>
<title>Test</title>
<style>
body {
margin: 0;
}
div {
margin: 3px auto 0;
width: 885px;
height: 610px;
border: 1px solid #f00;
background-color: #fdd;
}
</style>
</head>
<body>
<div>
This div is 885x610 (ratio is in between 4:3 and 16:10) with a 1px border and 3px top margin, making a total of 887x615.
</div>
</body>
</html>
What can I do to have this website scale to fit both the width and the height?
It's possible to get a consistent behavior. But it's unfortunately very complex. I am working on a script that detects spoofed agents and dynamically rescale the viewport to desktop or other spoofed agents accordingly. I was also facing the zooming issue with Android/Chrome as well as the iOS emulator...
To get around it, you need to disable zooming and/or set the viewport twice. On the first pass, preferably inline in the <head> as you do now, you set your scale and disable user-scaling temporarily to prevent the zoom issue, using the same fixed value for all 3 scales like:
document.querySelector('meta[name=viewport]').setAttribute('content', 'width='+width+',minimum-scale='+scale+',maximum-scale='+scale+',initial-scale='+scale);
Then to restore zooming you set the viewport again on DOMContentLoaded, with the same scale, except that this time you set normal min/max scale values to restore user-scaling:
document.querySelector('meta[name=viewport]').setAttribute('content', 'width='+width+',minimum-scale=0,maximum-scale=10');
In your context, because the layout is fixed and larger than the viewport, initial-scale='+scale is perhaps needed for a more sound alternative for DOMContentLoaded:
document.querySelector('meta[name=viewport]').setAttribute('content', 'width='+width+',minimum-scale=0,maximum-scale=10,initial-scale='+scale);
That should get the viewport to rescale as you would like in Webkit browsers without zooming problems. I say only in webkit because sadly IE and Firefox do not support changing the viewport as per this Browser Compatibility Table for Viewports, Dimensions and Device Pixel Ratios shows: http://www.quirksmode.org/mobile/tableViewport.html
IE has its own way to change the viewport dynamically which is actually needed for IE snap modes to be responsive.
http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/
So for IEMobile and IE SnapMode (IE10&11) you need to dynamically insert an inline <style> in the <head> with something like.
<script>
var s = document.createElement('style');
s.appendChild(document.createTextNode('#-ms-viewport{width:'+width+'px')+';}'));
document.head.appendChild(s);
</script>
And unfortunately, Firefox has neither: The viewport is set for once and for all as the above compatibility table shows. At the moment, for lack of other methods, using CSS Transform (as #imcg pointed out) is the only way to alter the viewport in FireFox Mobile on Android or Gecko OS. I have just tested it and it works in the context of a fixed size design. (In "Responsive Design context", the layout can be rescaled larger via CSS Transform, say at desktop size on a phone, but Firefox still read the phone size MQs. So that's something to be mindful off in RWD context. /aside from webkit)
Though, I have noticed some random Webkit crashes with CSSTransform on Android so I would recommend the viewport method for Safari/Chrome/Opera as more reliable one.
In addition, in order to get cross-browser reliability for the viewport width, you also have to face/fix the overall inconsistency between browsers for innerWidth (note that documentElement.clientWidth is much more reliable to get the accurate layout pixel width over innerWidth) and you also have to deal with devicePixelRatio discrepancies as indicated on the quirksmode.org table.
Update: Actually after some more thought into a solution for my own problem with Firefox, I just found out a way to override the viewport in Firefox Mobile, using document.write(), applied just once:
document.write('<meta name="viewport" content="width='+width+'px,initial-scale='+scale+'">');
Just tested this successfully in both Webkit and Firefox with no zooming issues. I can't test on Window Phones, so I am not sure itf document.write works for IEMobile...
I know this is two years late, but I spent a lot of time working on this problem, and would like to share my solution. I found the original question to be very helpful, so I feel that posting this answer is my way of giving back. My solution works on an iPhone6 and a 7" Galaxy Tab. I don't know how it fares on other devices, but I'm guessing it should mostly behave.
I separated the viewport logic into an external file so that it is easy to
reuse. First, here's how you would use the code:
<HTML>
<HEAD>
<SCRIPT type="text/javascript" src="AutoViewport.js"></SCRIPT>
</HEAD>
<BODY>
<SCRIPT>
AutoViewport.setDimensions(yourNeededWidth, yourNeededHeight);
</SCRIPT>
<!-- The rest of your HTML goes here -->
</BODY>
</HTML>
In actuality, I padded my desired width and height by a slight amount (15 pixels or so) so that my intended display was framed nicely. Also please note from the usage code that you do not have to specify a viewport tag in your HTML. My library will automatically create one for you if one does not already exist.
Here is AutoViewport.js:
/** Steven Yang, July 2016
Based on http://stackoverflow.com/questions/21419404/setting-the-viewport-to-scale-to-fit-both-width-and-height , this Javascript code allows you to
cause the viewport to auto-adjust based on a desired pixel width and height
that must be visible on the screen.
This code has been tested on an iPhone6 and a 7" Samsung Galaxy Tab.
In my case, I have a game with the exact dimensions of 990 x 660. This
script allows me to make the game render within the screen, regardless
of whether you are in landscape or portrait mode, and it works even
when you hit refresh or rotate your device.
Please use this code freely. Credit is appreciated, but not required!
*/
function AutoViewport() {}
AutoViewport.setDimensions = function(requiredWidth, requiredHeight) {
/* Conditionally adds a default viewport tag if it does not already exist. */
var insertViewport = function () {
// do not create if viewport tag already exists
if (document.querySelector('meta[name="viewport"]'))
return;
var viewPortTag=document.createElement('meta');
viewPortTag.id="viewport";
viewPortTag.name = "viewport";
viewPortTag.content = "width=max-device-width, height=max-device-height,initial-scale=1.0";
document.getElementsByTagName('head')[0].appendChild(viewPortTag);
};
var isPortraitOrientation = function() {
switch(window.orientation) {
case -90:
case 90:
return false;
}
return true;
};
var getDisplayWidth = function() {
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
if (isPortraitOrientation())
return screen.width;
else
return screen.height;
}
return screen.width;
}
var getDisplayHeight = function() {
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
if (isPortraitOrientation())
return screen.height;
else
return screen.width;
}
// I subtract 180 here to compensate for the address bar. This is imperfect, but seems to work for my Android tablet using Chrome.
return screen.height - 180;
}
var adjustViewport = function(requiredWidth, requiredHeight) {
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)){
var actual_height = getDisplayHeight();
var actual_width = getDisplayWidth();
var min_width = requiredWidth;
var min_height = requiredHeight;
var ratio = Math.min(actual_width / min_width, actual_height / min_height);
document.querySelector('meta[name="viewport"]').setAttribute('content', 'initial-scale=' + ratio + ', maximum-scale=' + ratio + ', minimum-scale=' + ratio + ', user-scalable=yes, width=' + actual_width);
}
};
insertViewport();
adjustViewport(requiredWidth, requiredHeight);
window.addEventListener('orientationchange', function() {
adjustViewport(requiredWidth, requiredHeight);
});
};
If you compare my code closely with the original code found in the question, you will notice a few differences. For example, I never rely on the viewport width or height. Instead, I rely on the screen object. This is important because as you refresh your page or rotate your screen, the viewport width and height can change, but screen.width and screen.height never change. The next thing you will notice is that I don't do the check for (ratio<1). When refreshing or rotating the screen, that check was causing inconsistency, so I removed it. Also, I included a handler for screen rotation.
Finally, I'd just like to say thank you to the person who created this question for laying the groundwork, which saved me time!
If you can't get consistent behavior across devices by changing the viewport meta tag, it's possible to zoom without changing the dimensions using CSS3 transforms:
if (ratio < 1) {
var box = document.getElementsByTagName('div')[0];
box.style.webkitTransform = 'scale('+ratio+')';
box.style.webkitTransformOrigin = '0 0';
}
console.log(box.offsetWidth); // always original width
console.log(box.getBoundingClientRect().width); // new width with scaling applied
Note I've omitted any vendor prefixes other than webkit here in order to keep it simple.
To center the scaled div you could use the translate tranforms:
var x = (actual_width - min_width * ratio) / 2;
var y = (actual_height - min_height * ratio) / 2;
box.style.webkitTransform = 'translateX('+x+'px) translateY('+y+'px) scale('+ratio+')';
box.style.webkitTransformOrigin = '0 0';
Replace your viewport with this :
<META NAME="viewport" CONTENT="width=device-width, height=device-height, initial-scale=1, user-scalable=no"/>
The user-scalable=0 here shall do the job for you.
This shall work for you.If it still doesn't work for we will have to extend the viewport so replace your viewport and add this:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, target-densitydpi=medium-dpi, user-scalable=0" />
For your javascript error have a look at this link:
scale fit mobile web content using viewport meta tag
Set the height and width of the div's parent elements (html and body) to 100% and zero out the margin.
html, body {
margin: 0;
height: 100%;
width: 100%;
}
Next, because you want a border on the div you need to make sure that the border width is included when you specify the width / height of the element, to do this use box-sizing: border-box on the div.
Because you want a 3px top margin on the div relative positioning of the div will result in a height that is 3px too tall. To fix this use absolute positioning on the div and set top, left, and bottom to 0.
div {
position: absolute;
top: 3px;
left: 0;
bottom: 0;
box-sizing: border-box;
width: 100%;
border: 1px solid #f00;
background-color: #fdd;
}
Here's a working example.
UPDATE: I think I misunderstood the question. Here's a sample code that adjusts the "zoom" depending on the device's viewport
http://jpanagsagan.com/viewport/index2.html
Take note that I used jquery to append the meta tag as I am having issue using the vanilla append.
One thing I noticed is that if you hard-coded in the HTML and change it via JS, the document won't apply the correction (needs verification). I was able to change it via JS if there is no previous tag in the HTML, thus I used append.
You may play around with the ratio, but in the example I used width of the viewport divided by width of the div.
hope this helps.
UPDATE: I think I misunderstood the question. Here's a sample code that adjusts the "zoom" depending on the device's viewport
http://jpanagsagan.com/viewport/index2.html
Take note that I used jquery to append the meta tag as I am having issue using the vanilla append.
One thing I noticed is that if you hard-coded in the HTML and change it via JS, the document won't apply the correction (needs verification). I was able to change it via JS if there is no previous tag in the HTML, thus I used append.
You may play around with the ratio, but in the example I used width of the viewport divided by width of the div.
hope this helps.
I think Steven's should be the accepted answer.
In case it is helpful someone else, I would add the following 2 things to Steven's AutoViewport.js, in order to center the view within the viewport when the user is in landscape view:
Add "var viewport_margin = 0;" as the first line of code (as it's own line before "function AutoViewport() {}".
Add "viewport_margin = Math.abs(actual_width-(ratio*requiredWidth))/(actual_width*2)*100;" after the line that reads "document.querySelector('meta[name="viewport"]').setAttribute('content', 'initial-scale=' + ratio + ', maximum-scale=' + ratio + ', minimum-scale=' + ratio + ', user-scalable=yes, width=' + actual_width);"
Thanks for all those who posted to bring this solution to light.
UPDATE: In Android, my additions only appear to work with API 24+. Not sure why they aren't working with APIs 19-23. Any ideas?
I am trying to get the screen dimensions on an IPad.
$wnd.screen.width
$wnd.screen.height
When I am in landscape mode, the dimensions return 768x1024. This is not what I expect. How can I get the real dimensions of 1024x768?
Note: I am not changing the orientation, I start in landscape mode and stay in that mode.
As the simplest solution, swap width and height if width is lesser than height so you always get the resolution of landscape mode.
function getResolution(){
return {
width: $wnd.screen.width < $wnd.screen.height
? $wnd.screen.height
: $wnd.scree.width,
height: $wnd.screen.height > $wnd.screen.width
? $wnd.screen.width
: $wnd.scree.height
};
}
The iPad likes to do funny things with scaling when you change orientation.
You can resolve many of those issues with a <meta viewport> tag, e.g.:
<meta name="viewport" content="user-scalable=no,initial-scale=1.0,
minimum-scale=1.0,maximum-scale=1.0">