Reputation: 9259
I made an HTML page that has an <input>
tag with type="text"
. When I click on it using Safari on iPhone, the page becomes larger (auto zoom). Does anybody know how to disable this?
Upvotes: 925
Views: 852824
Reputation: 87
Using these Meta tag script code work without shrink-to-fit=no
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
OR JQuery Code
if(new RegExp('iPhone|iPad|Macintosh', 'i').test(navigator.userAgent)){
$('meta[name=viewport]').attr('content', 'width=device-width, initial-scale=1, maximum-scale=1'); // shrink-to-fit=no
}
Using these CSS Code does not work.
* {
-webkit-text-size-adjust: none;
text-size-adjust: none;
}
Upvotes: 3
Reputation: 51
In my Nextjs 15.1.0 web app, I have managed to fix this issue by adding the viewport settings below to the root layout. This approach disables the pinch zooming but I don't see it as an issue, as I just checked Stackoverflow does not allow it either. I saw some people suggested setting the viewportFit property at "contain" but somehow "cover" worked for me. That's why I'm sharing my solution.
import { Viewport } from "next";
import { Inter } from "next/font/google";
import { Providers } from "./providers";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
maximumScale: 1,
viewportFit: "cover",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<head />
<body className={inter.className}>
<Providers>{children}</Providers>
</body>
</html>
);
}
Upvotes: 0
Reputation: 3230
This is a JS based solution. Posting my own answer because many here are outdated and depend on focus
event which no longer fires before iOS zooming in logic. My priority is not to disable pinch zoom.
Seems like the top voted post where adding static attribute to meta viewport has edge cases that disable pinch zoom on some devices based on reading the comments and I'm too lazy to test and find out.
CSS solutions such as using the :active pseudo class works but there's a single/couple frames where the input changes height and I think that's a bad experience.
Seems like JS is the solution and this answer from the author who toggles zoom via meta tag on pointer down events, helped me to update and finalize that code.
// REMOVE THIS LINE. Is only needed for SO playground since its iframe doesn't include meta viewport tag
document.querySelector('head').insertAdjacentHTML('beforeend', `<meta name="viewport" content="width=device-width, initial-scale=1.0">`)
const preventPageZoomInOnInputFocus = () => {
// For iOS, if text inputs are smaller than 16px then the page will zoom in to the input element.
// Adding user-scalable of value 0 to meta viewport prevents that.
// Zoom is only disabled on input tap, so for a duration of 150ms.
const viewport = document.querySelector('meta[name=viewport]');
const viewportContent = viewport.getAttribute('content');
const userScalableZero = 'user-scalable=0';
const userScalableRegex = /user-scalable=\d+/;
const extractAndExcludeUserScalableFromContentAttr = (viewportContent) => {
return (viewportContent
?.split(/,\s?/)
.filter((item) => !item.match(userScalableRegex))
.join(', ') ?? '');
};
if (!viewportContent?.match(userScalableRegex)) {
viewport.setAttribute('content', viewportContent ?
`${extractAndExcludeUserScalableFromContentAttr(viewportContent)}, ${userScalableZero}` :
userScalableZero);
}
setTimeout(() => {
const viewportContent = viewport.getAttribute('content');
viewport.setAttribute('content', extractAndExcludeUserScalableFromContentAttr(viewportContent));
// 100ms is the limit, any earlier and the page still zooms in, but add extra in case
}, 150);
};
const inputKeyboardTypes = [
'text',
'week',
'url',
'time',
'tel',
'search',
'password',
'number',
'month',
'email',
'datetime-local',
'date',
];
const inputTypes = inputKeyboardTypes
.map((item) => `input[type="${item}"]`)
.join(',');
const inputSelectors = `${inputTypes},textarea`;
const handleEvent = (event) => {
const target = event.target;
// not expensive like `closest` where it searches ancestor nodes, `matches` only checks that node
if (!target.matches(inputSelectors))
return;
preventPageZoomInOnInputFocus();
};
// Zoom in happens before focus event, but not before pointer down events.
// So mousedown is used since it's the last pointer down event to fire.
// This is to allow intentional pinch zoom if the gesture happens to occur over input element
document.body.addEventListener('mousedown', handleEvent);
// Prevent zoom in when focusing input elements by navigating form inputs via soft keyboard navigation bar
document.body.addEventListener('focusout', handleEvent);
form {
display: grid;
gap: 8px;
}
input {
/* smaller than 16px to trigger zoom */
font-size: 14px;
}
<form>
<div>
<label for="name">Enter your name: </label>
<input type="text" name="name" id="name" required />
</div>
<div>
<label for="email">Enter your email: </label>
<input type="email" name="email" id="email" required />
</div>
</form>
Upvotes: 0
Reputation: 1112
For NextJS 14 this worked for me:
I've added this code to my root layout.tsx
:
export const viewport: Viewport = {
width: "device-width",
initialScale: 1.0,
maximumScale: 1.0,
userScalable: false,
viewportFit: "contain",
};
And it helped. Not sure if it still work in Next 15, but it is a solution for Next 14 for sure.
Upvotes: 4
Reputation: 29
inside head tag in viewport add maximum-scale=1 solve this problem
Upvotes: 1
Reputation: 407
A nextjs 2024 answer that does not disable user triggered zoom on Android or iOS devices is provided below. This method has been already explained in other answers but I want to make clear that
Solution: on a page.tsx or _layout.tsx file (if using App Router), or _app.tsx file if using Pages router, you can add the following code:
import { headers } from 'next/headers'
export async function generateViewport(): Promise<Viewport> {
const userAgent = headers().get('user-agent')
const isiPhone = /iphone/i.test(userAgent ?? '')
return isiPhone
? {
width: 'device-width',
initialScale: 1,
maximumScale: 1, // disables auto-zoom on ios safari
}
: {}
}
Upvotes: 2
Reputation: 233
For android and iOS, the solution is different. If we put maximum-scale=1, then it will stop zoom in capability of android device but on iOS device, it works expected. Add this solution to solve this in react application (In index.js file)
// Detect the device OS
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
// Set viewport properties based on the device OS
const viewportContent = isIOS
? 'width=device-width, initial-scale=1, maximum-scale=1'
: 'width=device-width, initial-scale=1, shrink-to-fit=no';
// Create a meta tag and append it to the document head
const metaTag = document.createElement('meta');
metaTag.name = 'viewport';
metaTag.content = viewportContent;
document.head.appendChild(metaTag);
Upvotes: 2
Reputation: 1259
Recently ran into this using Bootstrap 5.3 .input-group-sm
containing .form-control
. Resolved using only the following CSS (no viewport or script hacks)...
input, textarea {
font-size: 1rem !important;
}
@media (min-width: 768px) {
input, textarea {
font-size: inherit !important;
}
}
That should make the font large enough to avoid auto-zoom on small devices. Increase the min-width
media breakpoint as needed for tablets (none were available for testing).
Upvotes: 1
Reputation: 419
The Simple solution to this problem is:
@media screen and (max-width: 599px) {
input, select, textarea {
font-size: 16px;
}
}
Note: max-width can be customized according to your requirements.
Upvotes: 3
Reputation: 29511
Using (hover: none) and (pointer: coarse)
to target all touchscreen devices:
A number of answers here resort to deploying JavaScript or jQuery.
But it ought to be (and it is) entirely possible to control concerns like conditional font-presentation with CSS.
Safari Mobile requires (with good reason) that any form element, when being interacted with, must have a minimum font-size of 16px
(or the visual equivalent).
Let's commit to making that well-thought-out UX apply to all touchscreen browsers.
Then we can adopt the following:
@media only screen and (hover: none) and (pointer: coarse) {
input,
select,
textarea {
font-size: 11px;
}
input:focus,
select:focus,
textarea:focus {
font-size: 16px;
}
}
When using a touchscreen device, when any of the interactive form elements above are focused on, the font-size
of that form element is temporarily set to 16px
.
This, in turn, disables iOS Safari Mobile auto-zoom
.
User-initated pinch-zoom remains unaffected on all devices and is never disabled.
Upvotes: 14
Reputation: 27972
2021 solution...
OK, I've read through all the old answers but none of them worked for me. After many hours of trying different things the solution seemed simple in the end.
input{
transform: scale(0.875);
transform-origin: left center;
margin-right: -14.28%;
}
Tested on iOS/Android/Chrome on PC
This allows you to use a 14px font, if you need a different size then the scaling factor is 14/16 = 0.875 and the negative margin is (1 - 0.875) / 0.875 * 100
My input has a parent set to "display:flex" and it grows to fit the parent because it has "flex: 1 1 auto". You may or may not need this but I'm including it for completeness.
Upvotes: 9
Reputation: 4323
This worked for me on iOS Safari and Chrome. For the input selector could be set the class or id to enclose the current.
@supports (-webkit-overflow-scrolling: touch) {
input {
font-size: 16px;
}
}
Upvotes: 17
Reputation: 22166
Amazing, there are dozens of answers here with javascript and viewports, and only one other mentions text-size-adjust
which is what I believe is the best solution.
You can just set this to none
.
Add the following CSS:
* {
-webkit-text-size-adjust: none;
text-size-adjust: none;
}
Upvotes: 2
Reputation: 2059
Proper way to fix this issue is to change meta viewport to:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
Important: do not set minimum-scale
! This keeps the page manually zoomable.
Upvotes: 115
Reputation: 339
I have looked through multiple answers.\
maximum-scale=1
in meta
tag works fine on iOS devices but disables the pinch to zoom functionality on Android devices.font-size: 16px;
onfocus
is too hacky for me.So I wrote a JS function to dynamically change meta
tag.
var iOS = navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform);
if (iOS)
document.head.querySelector('meta[name="viewport"]').content = "width=device-width, initial-scale=1, maximum-scale=1";
else
document.head.querySelector('meta[name="viewport"]').content = "width=device-width, initial-scale=1";
Upvotes: 11
Reputation: 36614
Inspired by @jirikuchta 's answer, I solved this problem by adding this bit of CSS:
#myTextArea:active {
font-size: 16px; /* `16px` is safer I assume, although `1rem` works too */
}
No JS, and I don't notice any flash or anything.
It's worth noting that a viewport
with maximum-scale=1
also works, but not when the page is loaded as an iframe, or if you have some other script modifying the viewport
, etc.
Upvotes: 25
Reputation: 2761
As many other answers have already pointed out, this can be achieved by adding maximum-scale
to the meta viewport
tag. However, this has the negative consequence of disabling user zoom on Android devices. (It does not disable user zoom on iOS devices since v10.)
We can use JavaScript to dynamically add maximum-scale
to the meta viewport
when the device is iOS. This achieves the best of both worlds: we allow the user to zoom and prevent iOS from zooming into text fields on focus.
| maximum-scale | iOS: can zoom | iOS: no text field zoom | Android: can zoom |
| ------------------------- | ------------- | ----------------------- | ----------------- |
| yes | yes | yes | no |
| no | yes | no | yes |
| yes on iOS, no on Android | yes | yes | yes |
Code:
const addMaximumScaleToMetaViewport = () => {
const el = document.querySelector('meta[name=viewport]');
if (el !== null) {
let content = el.getAttribute('content');
let re = /maximum\-scale=[0-9\.]+/g;
if (re.test(content)) {
content = content.replace(re, 'maximum-scale=1.0');
} else {
content = [content, 'maximum-scale=1.0'].join(', ')
}
el.setAttribute('content', content);
}
};
const disableIosTextFieldZoom = addMaximumScaleToMetaViewport;
// https://stackoverflow.com/questions/9038625/detect-if-device-is-ios/9039885#9039885
const checkIsIOS = () =>
/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
if (checkIsIOS()) {
disableIosTextFieldZoom();
}
Upvotes: 84
Reputation: 165
Even with these answers it took me three days to figure out what was going on and I may need the solution again in the future.
My situation was slightly different from the one described.
In mine, I had some contenteditable text in a div on the page. When the user clicked on a DIFFERENT div, a button of sorts, I automatically selected some text in the contenteditable div (a selection range that had previously been saved and cleared), ran a rich text execCommand on that selection, and cleared it again.
This enabled me to invisibly change text colors based on user interactions with color divs elsewhere on the page, while keeping the selection normally hidden to let them see the colors in the proper context.
Well, on iPad's Safari, clicking the color div resulted in the on-screen keyboard coming up, and nothing I did would prevent it.
I finally figured out how the iPad's doing this.
It listens for a touchstart and touchend sequence that triggers a selection of editable text.
When that combination happens, it shows the on-screen keyboard.
Actually, it does a dolly zoom where it expands the underlying page while zooming in on the editable text. It took me a day just to understand what I was seeing.
So the solution I used was to intercept both touchstart and touchend on those particular color divs. In both handlers I stop propagation and bubbling and return false. But in the touchend event I trigger the same behavior that click triggered.
So, before, Safari was triggering what I think was "touchstart", "mousedown", "touchend", "mouseup", "click", and because of my code, a text selection, in that order.
The new sequence because of the intercepts is simply the text selection. Everything else gets intercepted before Safari can process it and do its keyboard stuff. The touchstart and touchend intercepts prevent the mouse events from triggering as well, and in context this is totally fine.
I don't know an easier way to describe this but I think it's important to have it here because I found this thread within an hour of first encountering the issue.
I'm 98% sure the same fix will work with input boxes and anything else. Intercept the touch events and process them separately without letting them propagate or bubble, and consider doing any selections after a tiny timeout just to make sure Safari doesn't recognize the sequence as the keyboard trigger.
Upvotes: 5
Reputation: 177
Pseudo elements like :focus
don't work as they used to. From iOS 11, a simple reset declaration can be added before your main styles (providing you don't override them with a smaller font size).
/* Prevent zoom */
select, input, textarea {
font-size: 16px;
}
It's worth mentioning that for CSS libraries such as Tachyons.css then it's easy to accidentally override your font size.
For example class: f5
is equivalent to: fontSize: 1rem
, which is fine if you have kept the body font scale at the default.
However: if you choose font size class: f6
this will be equivalent to fontSize: .875rem
on a small display upwards. In that instance you'll need to be more specific about your reset declarations:
/* Prevent zoom */
select, input, textarea {
font-size: 16px!important;
}
@media screen and (min-width: 30em) {
/* not small */
}
Upvotes: 7
Reputation: 125
Please do not use Javascript or hacks to make it work. That will affect your project score on the web.
This will do the trick:
input, input:active, input:focus, input:focus-within, input:hover, input:visited {
font-size: 16px!important;
}
Upvotes: -3
Reputation: 921
In Angular you can use directives to prevent zooming on focus on IOS devices. No meta tag to preserve accessibility.
import { Directive, ElementRef, HostListener } from '@angular/core';
const MINIMAL_FONT_SIZE_BEFORE_ZOOMING_IN_PX = 16;
@Directive({ selector: '[noZoomiOS]' })
export class NoZoomiOSDirective {
constructor(private el: ElementRef) {}
@HostListener('focus')
onFocus() {
this.setFontSize('');
}
@HostListener('mousedown')
onMouseDown() {
this.setFontSize(`${MINIMAL_FONT_SIZE_BEFORE_ZOOMING_IN_PX}px`);
}
private setFontSize(size: string) {
const { fontSize: currentInputFontSize } = window.getComputedStyle(this.el.nativeElement, null);
if (MINIMAL_FONT_SIZE_BEFORE_ZOOMING_IN_PX <= +currentInputFontSize.match(/\d+/)) {
return;
}
const iOS = navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform);
iOS
&& (this.el.nativeElement.style.fontSize = size);
}
}
You can use it like this <input noZoomiOS >
after you declare it in your *.module.ts
Upvotes: 3
Reputation: 11936
Instead of simply setting the font size to 16px, you can:
scale()
CSS transform and negative margins to shrink the input field down to the correct size.For example, suppose your input field is originally styled with:
input[type="text"] {
border-radius: 5px;
font-size: 12px;
line-height: 20px;
padding: 5px;
width: 100%;
}
If you enlarge the field by increasing all dimensions by 16 / 12 = 133.33%, then reduce using scale()
by 12 / 16 = 75%, the input field will have the correct visual size (and font size), and there will be no zoom on focus.
As scale()
only affects the visual size, you will also need to add negative margins to reduce the field's logical size.
With this CSS:
input[type="text"] {
/* enlarge by 16/12 = 133.33% */
border-radius: 6.666666667px;
font-size: 16px;
line-height: 26.666666667px;
padding: 6.666666667px;
width: 133.333333333%;
/* scale down by 12/16 = 75% */
transform: scale(0.75);
transform-origin: left top;
/* remove extra white space */
margin-bottom: -10px;
margin-right: -33.333333333%;
}
the input field will have a logical font size of 16px while appearing to have 12px text.
I have a blog post where I go into slightly more detail, and have this example as viewable HTML:
No input zoom in Safari on iPhone, the pixel perfect way
Upvotes: 20
Reputation: 9641
You can prevent Safari from automatically zooming in on text fields during user input without disabling the user’s ability to pinch zoom. Just add maximum-scale=1
but leave out the user-scale attribute suggested in other answers.
It is a worthwhile option if you have a form in a layer that “floats” around if zoomed, which can cause important UI elements to move off screen.
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
Upvotes: 925
Reputation: 660
Here is a hack I used on one of my projects:
select {
font-size: 2.6rem; // 1rem = 10px
...
transform-origin: ... ...;
transform: scale(0.5) ...;
}
Ended up with the initial styles and scale I wanted but no zoom on focus.
Upvotes: 0
Reputation: 380
I've had to "fix" the auto zoom into form controls issue for a Dutch University website (which used 15px in form controls). I came up with the following set of requirements:
This is what I came up with so far:
/*
NOTE: This code overrides the viewport settings, an improvement would be
to take the original value and only add or change the user-scalable value
*/
// optionally only activate for iOS (done because I havn't tested the effect under other OS/devices combinations such as Android)
var iOS = navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform)
if (iOS)
preventZoomOnFocus();
function preventZoomOnFocus()
{
document.documentElement.addEventListener("touchstart", onTouchStart);
document.documentElement.addEventListener("focusin", onFocusIn);
}
let dont_disable_for = ["checkbox", "radio", "file", "button", "image", "submit", "reset", "hidden"];
//let disable_for = ["text", "search", "password", "email", "tel", "url", "number", "date", "datetime-local", "month", "year", "color"];
function onTouchStart(evt)
{
let tn = evt.target.tagName;
// No need to do anything if the initial target isn't a known element
// which will cause a zoom upon receiving focus
if ( tn != "SELECT"
&& tn != "TEXTAREA"
&& (tn != "INPUT" || dont_disable_for.indexOf(evt.target.getAttribute("type")) > -1)
)
return;
// disable zoom
setViewport("width=device-width, initial-scale=1.0, user-scalable=0");
}
// NOTE: for now assuming this focusIn is caused by user interaction
function onFocusIn(evt)
{
// reenable zoom
setViewport("width=device-width, initial-scale=1.0, user-scalable=1");
}
// add or update the <meta name="viewport"> element
function setViewport(newvalue)
{
let vpnode = document.documentElement.querySelector('head meta[name="viewport"]');
if (vpnode)
vpnode.setAttribute("content",newvalue);
else
{
vpnode = document.createElement("meta");
vpnode.setAttribute("name", "viewport");
vpnode.setAttribute("content", newvalue);
}
}
Some notes:
Upvotes: 4
Reputation: 7003
The browser will zoom if the font-size is less than 16px
and the default font-size for form elements is 11px
(at least in Chrome and Safari).
Additionally, the select
element needs to have the focus
pseudo-class attached.
input[type="color"],
input[type="date"],
input[type="datetime"],
input[type="datetime-local"],
input[type="email"],
input[type="month"],
input[type="number"],
input[type="password"],
input[type="search"],
input[type="tel"],
input[type="text"],
input[type="time"],
input[type="url"],
input[type="week"],
select:focus,
textarea {
font-size: 16px;
}
It's not necessary to use all the above, you can just style the elements you need, eg: just text
, number
, and textarea
:
input[type='text'],
input[type='number'],
textarea {
font-size: 16px;
}
Alternate solution to have the input elements inherit from a parent style:
body {
font-size: 16px;
}
input[type="text"] {
font-size: inherit;
}
Upvotes: 685
Reputation: 725
By the way, if you use Bootstrap, you can just use this variant:
.form-control {
font-size: 16px;
}
Upvotes: 2
Reputation: 5921
In summary the answer is: set the font size of the form elements to at least 16px
Upvotes: 106
Reputation: 91
After reading almost every single line here and testing the various solutions, this is, thanks to all who shared their solutions, what I came up with, tested and working for me on iPhone 7 iOS 10.x :
@media screen and (-webkit-min-device-pixel-ratio:0) {
input[type="email"]:hover,
input[type="number"]:hover,
input[type="search"]:hover,
input[type="text"]:hover,
input[type="tel"]:hover,
input[type="url"]:hover,
input[type="password"]:hover,
textarea:hover,
select:hover{font-size: initial;}
}
@media (min-width: 768px) {
input[type="email"]:hover,
input[type="number"]:hover,
input[type="search"]:hover,
input[type="text"]:hover,
input[type="tel"]:hover,
input[type="url"]:hover,
input[type="password"]:hover,
textarea:hover,
select:hover{font-size: inherit;}
}
It has some cons, though, noticeably a "jump" as result of the quick font size change occuring between the "hover"ed and "focus"ed states - and the redraw impact on performance
Upvotes: 7
Reputation: 338
After a while of while trying I came up with this solution
// set font-size to 16px to prevent zoom
input.addEventListener("mousedown", function (e) {
e.target.style.fontSize = "16px";
});
// change font-size back to its initial value so the design will not break
input.addEventListener("focus", function (e) {
e.target.style.fontSize = "";
});
On "mousedown" it sets font-size of input to 16px. This will prevent the zooming. On focus event it changes font-size back to initial value.
Unlike solutions posted before, this will let you set the font-size of the input to whatever you want.
Upvotes: 8