Mobile Safari: Javascript focus() method on inputfield only works with click?
Actually, guys, there is a way. I struggled mightily to figure this out for [LINK REMOVED] (try it on an iPhone or iPad).
Basically, Safari on touchscreen devices is stingy when it comes to focus()
ing textboxes. Even some desktop browsers do better if you do click().focus()
. But the designers of Safari on touchscreen devices realized it's annoying to users when the keyboard keeps coming up, so they made the focus appear only on the following conditions:
1) The user clicked somewhere and focus()
was called while executing the click event. If you are doing an AJAX call, then you must do it synchronously, such as with the deprecated (but still available) $.ajax({async:false})
option in jQuery.
2) Furthermore -- and this one kept me busy for a while -- focus()
still doesn't seem to work if some other textbox is focused at the time. I had a "Go" button which did the AJAX, so I tried blurring the textbox on the touchstart
event of the Go button, but that just made the keyboard disappear and moved the viewport before I had a chance to complete the click on the Go button. Finally I tried blurring the textbox on the touchend
event of the Go button, and this worked like a charm!
When you put #1 and #2 together, you get a magical result that will set your login forms apart from all the crappy web login forms, by placing the focus in your password fields, and make them feel more native. Enjoy! :)
A native javascript implementation of WunderBart's answer.
function onClick() { // create invisible dummy input to receive the focus first const fakeInput = document.createElement('input') fakeInput.setAttribute('type', 'text') fakeInput.style.position = 'absolute' fakeInput.style.opacity = 0 fakeInput.style.height = 0 fakeInput.style.fontSize = '16px' // disable auto zoom // you may need to append to another element depending on the browser's auto // zoom/scroll behavior document.body.prepend(fakeInput) // focus so that subsequent async focus will work fakeInput.focus() setTimeout(() => { // now we can focus on the target input targetInput.focus() // cleanup fakeInput.remove() }, 1000)}
Other References: Disable Auto Zoom in Input "Text" tag - Safari on iPhone
I faced the same issue recently. I found a solution that apparently works for all devices. You can't do async focus programmatically but you can switch focus to your target input when some other input is already focused. So what you need to do is create, hide, append to DOM & focus a fake input on trigger event and, when the async action completes, just call focus again on the target input. Here's an example snippet - run it on your mobile.
edit:
Here's a fiddle with the same code. Apparently you can't run attached snippets on mobiles (or I'm doing something wrong).
var $triggerCheckbox = $("#trigger-checkbox");var $targetInput = $("#target-input");// Create fake & invisible inputvar $fakeInput = $("<input type='text' />") .css({ position: "absolute", width: $targetInput.outerWidth(), // zoom properly (iOS) height: 0, // hide cursor (font-size: 0 will zoom to quarks level) (iOS) opacity: 0, // make input transparent :] });var delay = 2000; // That's crazy long, but good as an example$triggerCheckbox.on("change", function(event) { // Disable input when unchecking trigger checkbox (presentational purpose) if (!event.target.checked) { return $targetInput .attr("disabled", true) .attr("placeholder", "I'm disabled"); } // Prepend to target input container and focus fake input $fakeInput.prependTo("#container").focus(); // Update placeholder (presentational purpose) $targetInput.attr("placeholder", "Wait for it..."); // setTimeout, fetch or any async action will work setTimeout(function() { // Shift focus to target input $targetInput .attr("disabled", false) .attr("placeholder", "I'm alive!") .focus(); // Remove fake input - no need to keep it in DOM $fakeInput.remove(); }, delay);});
label { display: block; margin-top: 20px;}input { box-sizing: border-box; font-size: inherit;}#container { position: relative;}#target-input { width: 250px; padding: 10px;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script><div id="container"> <input type="text" id="target-input" placeholder="I'm disabled" /> <label> <input type="checkbox" id="trigger-checkbox" /> focus with setTimetout </label></div>