46

I am experiencing a very strange and unique issue.

All my pages are using vh and vw CSS units instead of px due to the nature of the project.

Issue: On Android tablets, when you touch the input field the default keyboard pushes the view port which is causing the page and all the elements in the page to shrink.

On ipad this issue does not exist since the keyboard overlaps the screen and does not push the screen.

Looking for any solution to avoid the Android keyboard not to push the viewport of the browser and keep the original size.

Note: The only option i am left with is to avoid keyboard to push viewport, i won't be able to change the CSS units or use xml, manifest. These are web pages which experiencing this issue.

Manu
  • 806
  • 1
  • 9
  • 25

11 Answers11

56

I know this is an old question, but I had the exact same problem in my app. The solution I found was fairly simple. (My app is in Angular so I put this in the app.component's ngOnInit function, but document.ready() or any other "initialization complete" callback should work just fine with the proper experimentation)

setTimeout(function () {
        let viewheight = $(window).height();
        let viewwidth = $(window).width();
        let viewport = document.querySelector("meta[name=viewport]");
        viewport.setAttribute("content", "height=" + viewheight + "px, width=" + viewwidth + "px, initial-scale=1.0");
    }, 300);

This forces the viewport meta to explicitly set viewport height, whereas hardcoding

<meta name="viewport" 
content="width=device-width, height=device-height, initial-scale=1">

doesn't work because the device-width and device-height change when Android's soft keyboard is opened.

Tyler Merle
  • 666
  • 5
  • 7
  • Perfect solution! Worked like a charm. I tried on http://szanata.com which is a landing page split into section, where each have a 100vh height. Thanks! – szanata Sep 26 '17 at 23:03
  • 2
    This Solution works fine, but I discovered a small problem. When working with the chrome developer tools in mobile-device-emulation mode the values of `window.innerHeight` and `window.innerWidth` (or `$(window).height()` and `$(window).width()`) are not set correctly. So if you are working with this tool, you should query `window.visualViewport.height` and `window.visualViewport.width` when your code runs in the development browser. – Merlin Denker Feb 01 '18 at 10:55
  • window stops being scrollable on input if page is less than or equal to viewport – Kudayar Pirimbaev May 26 '18 at 16:14
  • Yup this should be the answer. @madeinstefano I just checked your site, it's weird I can still pinch to scale the site. In order to be friendly, add `maximum-scale=1, minimum-scale=1, user-scalable=no, minimal-ui` in the viewport also. – Joshua Stephen Oct 26 '18 at 13:26
  • 3
    This is not working for me. I can confirm that height and width are set in the meta tag (using the devtools remote devices) but it is still scaled down so that the user cannot see the selected input. – fideldonson Nov 19 '18 at 20:27
  • 3
    you don't need to specify the "px" . – anvd Dec 16 '18 at 17:46
  • The dimensions must also be adjusted when device is rotated. – cdosborn Feb 03 '19 at 21:02
  • Good. This technique can also be used to make opening/closing mobile keyboard less laggy. – jahooma Sep 26 '19 at 06:28
  • this is the right solution, it is strange for me why the OP did not signed this as the correct one yet...anyway: thanx a lot, you saved my day! – qraqatit Jul 18 '20 at 17:47
19

Based on Tyler's question, this is a vanilla version of the script which seems to be a bit cleaner:

addEventListener("load", function() {
    var viewport = document.querySelector("meta[name=viewport]");
    viewport.setAttribute("content", viewport.content + ", height=" + window.innerHeight);
})

If you use it after the meta tag declaration you can even get rid of that addEventListener:

var viewport = document.querySelector("meta[name=viewport]");
viewport.setAttribute("content", viewport.content + ", height=" + window.innerHeight);
Guy
  • 540
  • 5
  • 14
7

Here's a more complete solution that takes screen orientation changes into account:

// Original viewport definition, note the "id" that I use to modify the viewport later on:
<meta name="viewport" id="viewport" content="width=device-width, height=device-height, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>

<script>
// Global boolean variable that holds the current orientation
var pageInPortraitMode;

// Listen for window resizes to detect orientation changes
window.addEventListener("resize", windowSizeChanged);

// Set the global orientation variable as soon as the page loads
addEventListener("load", function() {
  pageInPortraitMode = window.innerHeight > window.innerWidth;
  document.getElementById("viewport").setAttribute("content", "width=" + window.innerWidth + ", height=" + window.innerHeight + ", initial-scale=1.0, maximum-scale=1.0, user-scalable=0");
})

// Adjust viewport values only if orientation has changed (not on every window resize)
function windowSizeChanged() {
  if (((pageInPortraitMode === true) && (window.innerHeight < window.innerWidth)) || ((pageInPortraitMode === false) && (window.innerHeight > window.innerWidth))) {
    pageInPortraitMode = window.innerHeight > window.innerWidth;
    document.getElementById("viewport").setAttribute("content", "width=" + window.innerWidth + ", height=" + window.innerHeight + ", initial-scale=1.0, maximum-scale=1.0, user-scalable=0");
  }
}
</script>
Connor Low
  • 5,900
  • 3
  • 31
  • 52
bLight
  • 803
  • 7
  • 23
3

I faced the same problem recently, and it took me time to find a nice solution. I am designing a cordova app using html5, css3 and angularjs. But moreover, I want my app to fit every screen without writing tones of media queries and finally unreadable css. I started with viewport and vh, but the keyboard broke eveything.

So what you have to use is just a little javascript (here is jquery) like this :

$("html").css({"font-size": ($(window).height()/100)+"px"});

Here you go, now you can use "rem" exactly the same way you are using "vh", except that the viewport will not affect font-size. "Rem" is set on html root font-size only and we just set it with jquery to 1% of the screen height.

Hope this will help!

EDIT 1 : I just faced a new problem, so I give my solution here. Most of the time, smartphones have a minimum limit for font-size. You have to change all your rem value, and divide it by 3 or 4. Then, you have to change the javascript with :

$("html").css({"font-size": ($(window).height()/25)+"px"}); /*(this is if you divide with 4)*/

It is even easier if you are using SASS, you can create a rem function which will do division for you.

Mayous
  • 2,063
  • 3
  • 13
  • 18
1

Nearly a year to late, but I want also to share my solution to this more and more important problem.

I created a small JS function SET which is loaded after the DOM is completed.

Every Element assigned to a special class (In this case ".pheight") doesn't resized when the viewport height decreases. It is only allowed to resize if the viewport height increases or the viewport width changes.

So in my Applications it works perfectly!

  var docwidth = window.innerWidth;
  var docheight = window.innerHeight;
  function pheigt_init() {    
    pheigt_set_prevent_height();
    window.onresize = function() {
        if (docwidth !== window.innerWidth || docheight < window.innerHeight) {
            pheigt_upd_prevent_height();
        }
    };
  }
  function pheigt_set_prevent_height() {
    document.querySelectorAll('.pheight').forEach(function(node) {
     node.style.height = node.offsetHeight + 'px';
    });
  }
  function pheigt_upd_prevent_height() {
    document.querySelectorAll('.pheight').forEach(function(node) {
        node.style.removeProperty('height');
    }); 
    setTimeout(function(){ pheigt_set_prevent_height(); }, 100);
    docheight = window.innerHeight;
    docwidth = window.innerWidth;
  }
  document.addEventListener('DOMContentLoaded', pheigt_init());
MatWer
  • 136
  • 6
0

In Angular 4+

import {Meta} from "@angular/platform-browser";


constructor(private metaService: Meta){}

ngOnInit() {
  this.metaService.updateTag({
  name: 'viewport',
    content: `height=${this.height}px, width=${this.width}px, initial-scale=1.0`
  },
  `name='viewport'`
);
}
Joe Keene
  • 2,175
  • 21
  • 27
0

You could use % which doesn’t seems to be affected by this issue or vw (not affected for obvious reason) even for height.

Ced
  • 142
  • 7
0

Maybe viewport height (or just height) need to be refresh using a function?

const reference = useRef();

function avoidKeyboard(){
    setTimeout(
        ()=>{reference.current.querySelector("#win").style.height = "calc(100vh)"}
    ,2000)
}

return (
    <ref={reference} div>
       <input type="text" id="win" onFocus={avoidKeyboard} onBlur={avoidKeyboard}/>
    </div>
)
Raz Kheli
  • 41
  • 4
0

I'm creating a PWA where I disabled all scrolling and zooming, the problem for me is, that I use viewport units to calculate my layout exactly the way I want. Setting the viewport with events helped, but the window.onresize event is also called when the android keyboard comes up. My solution is to check if the keyboard is used by checking if document.activeElement is a textarea or input field.

the only downside for me is, that when the user changes screen orientation while using the keyboard (which is not very common) the viewport resizing fails. To prevent this the element is blured and thus the keyboard closes, before the layout breaks. Not very user-friendly, but would be considered a rather rare edge-case.

final code:

window.onresize = () => {
    if(document.activeElement.tagName !== 'TEXTAREA' && document.activeElement.tagName !== 'INPUT'){
        setViewportSize();
    }
}

window.onorientationchange = () => {
    // check if keyboard is used
    if(document.activeElement.tagName === 'TEXTAREA' || document.activeElement.tagName === 'INPUT'){
        document.activeElement.blur();
    }
    setViewportSize();
}

function setViewportSize(){
    document.documentElement.style.setProperty('--screen-width', `${window.innerWidth}px`);
    document.documentElement.style.setProperty('--screen-height', `${window.innerHeight}px`);
}

keep in mind that window.onorientationchange is deprecated and may not work at some point

Anton
  • 563
  • 4
  • 13
-1

The same problem put me off my stride. I don't found any css solution or workaround in order to solve the problem.

However, if you can use JQuery (or almost JavaScript), I have some code you can use to resolve it.

var viewportHeight = $(window).height();
var viewportWidth = $(window).width();
$('body,html').css("height",viewportHeight); //if vh used in body/html too
$('.someClass').css("height",viewportHeight*0.12);// or whatever percentage you used in vh
$('#someId').css("width",viewportWidth*0.12);

Well, this is just an example, you can use the percentage, jQuery identifiers you need to.

PD: there's one tip for your info. put the code at the final of your html or inside $(document).ready(function() { }); , and if you need responsive design, put it also inside orientationchange event; this way.

$(window).on("orientationchange",function(event){ window.setTimeout(function(){
var viewportHeight = $(window).height();
var viewportWidth = $(window).width();
$('body,html').css("height",viewportHeight);
$('.someClass').css("height",viewportHeight*0.12);
$('.someClass').css("width",viewportWidth*0.09);
$('#someId').css("height",viewportHeight*0.1);
}}, 450);});

I put a 450ms Timer above because of the delay of devices doing the orientationchange.

Sorry for my english, cheers

MagiK
  • 1
  • 2
-1

just using 'shrink-to-fit=no' in meta like :

<head>
   <meta content="width=device-width, initial-scale=1, shrink-to-fit=no" name="viewport">
 </head>
KING-CODE
  • 39
  • 5