Mobile Safari - Input caret does not scroll along with overflow-scrolling: touch
Asked Answered
M

10

20

I know that Mobile Safari won't fire events while in "momentum" (-webkit-overflow-scrolling: touch;) scrolling. But this is not entirely the same thing, because Safari handles the (blinking) caret of an input internally.

<div id="container">
    <input type="text" />
    <div class="filling"></div>
</div>

#container {
    position: absolute;
    top: 20px;
    bottom: 20px;
    width: 50%;
    -webkit-overflow-scrolling: touch;
    overflow-y: auto;
    border: 1px solid black;
}

input {
    margin-top: 60vh;
}

.filling {
    height: 200vh;
}

Try this fiddle on your device (focus the input and then scroll): https://jsfiddle.net/gabrielmaldi/n5pgedzv

The issue also happens when you keep your finger pressed (i.e. not only when giving it momentum and releasing): the caret fails to scroll.

Obviously I don't want to turn off overflow scrolling, if there's no way to fix the caret so it will scroll correctly, it would be OK to hide it.

Thanks

Mildred answered 23/12, 2013 at 5:32 Comment(1)
See also github.com/twbs/bootstrap/issues/14708Jehius
M
11

This is indeed a WebKit bug and there seems to be no known workaround.

@cvrebert filed the bug:

Mildred answered 30/10, 2014 at 2:51 Comment(1)
Sorry, have to downvote because workarounds are available for some. Unfortunately not a simple and across the board and easy and quick fix I agree but it works for some of us (position fixed, etc.)Vondavonni
F
5

Had this same problem, my fix was altering between

-webkit-overflow-scrolling: touch

and

-webkit-overflow-scrolling: auto

whenever i focus/blur on inputs

Fidelia answered 26/8, 2016 at 10:42 Comment(4)
This is the only one works for me... Thanks! It fixes the caret's out of bounds on ios.Bole
This fix is pretty hardcore and should be used with caution. For me it was causing UIWebView crashes in Cordova application. Might be fine for website use.Whitlow
Seems the best solution for meWastepaper
I can confirm that this approach worked, but it looks like it doesn't work any longer on ios 13. Apple has the following statement in their release notes: "Added support for one-finger accelerated scrolling to all frames and overflow:scroll elements eliminating the need to set -webkit-overflow-scrolling: touch."Motherhood
A
3

It's indeed a bug on newly released iOS 11.I resolved the issue on modal by changing the css:

.modal {
  position:fixed;
  overflow-y: scroll;
  bottom: 0;
  left: 0;
  right: 0;
  top: 0;
  z-index: 99;
}

body {
  height: 100%;
  width:100%;
  overflow: hidden;
  position:fixed;
}
Africanist answered 26/10, 2017 at 11:57 Comment(2)
You changed it from what? What was the part that fixed it?Overcoat
Never mind it's position: fixed on the body that does it.Overcoat
B
2

Only one workaround that I was found - on scrolling event to check if input with type text is focused, set focus on some other element (for example, on button). As result, virtual keyboard and cursor will disappear. This solution is not perfect, but it doesn't look so horrible as with cursors on top of the form. Example:

$(".scrollContainer").scroll(function () {
  var selected = $("input[type='text']:focus");
  if (selected.length > 0) {
      $('#someButton').focus();
  }
}
Bodily answered 19/12, 2014 at 13:9 Comment(3)
Nice, thank you. Worked better for me by just doing $("input[type='text']:focus").blur();. Also wrapped in a quick mobile-safari only condition.Pirandello
Igor and @Pirandello your solution make the scroll behavior weirdly..It does not scroll smoothly.Setaceous
This is the only solution in my case.Hegelianism
H
2

I spent a lot of time trying to figure this out, and did not have success with the other ideas mentioned here.

One thing I noticed is that even though the cursor would float outside the input, once you start typing on the on-screen keyboard, the cursor does go back into the correct position.

This gave me the idea - perhaps by using some JS code I could change the value of the input, then quickly change it back to the current value. Perhaps this would get the cursor to align itself just like it does when you do manual typing.

I tested it out and it worked. Here's what the code looks like:

       myIScroll.scrollToElement(element, scrollTime); // any scroll method call
       var scrollTime = 400;
       if (element.type && element.type == 'text') {
          var currentValue = $(element).val();
          $timeout(function(){
            $(element).val(currentValue + 'a').val(currentValue);
          }, scrollTime);
        }
Hamite answered 30/9, 2016 at 17:37 Comment(1)
genius! Remember, that most scroll methods have a callback for when they complete, so the timeout call is not needed in most cases.Muster
A
1

You can fix the problem by removing the selection and setting it again. Using jQuery here is the Javascript to do so. I add the event handler when entering edit mode:

        $(document).on('scroll.inline-edit', function(event) {
            var selection = window.getSelection();
            if (selection.rangeCount) {
                var range = selection.getRangeAt(0);
                selection.removeAllRanges();
                selection.addRange(range);
            }
        });

When I exit edit mode I remove the event handler:

        $(document).off('scroll.inline-edit');

This will probably also work if the event handler is always enabled.

Arson answered 19/10, 2016 at 18:24 Comment(0)
A
0

I'm using jQuery.animate to scroll the window and I'm not sure if this will work if you aren't using jQuery.animate, but it worked for me. I'm just triggering the "blur" handlers on the element which doesn't actually cause the element to lose focus, it just triggers the handlers as if they had been naturally triggered by user interaction. It seems :

$content.animate(
    {
        scrollTop: $(this).data('originalTop')
    }, 
    {
        duration: 100,
        easing: 'swing',
        always: function(){
            var $t = $(this);
            $t.trigger('blur');
        }
    }
);

Due to other weirdness with iOS I'm having to save the element's offset().top value as originalTop when my form loads. $content is simply a scrollable div containing my form -- eg: $('div#content').

Aton answered 20/3, 2017 at 20:43 Comment(0)
P
0

This still seems to be plaguing webkit forms in iOS with -webkit-overflow-scrolling:touch, also in iOS 11. Based on answers above, and since it takes focusing an input or textearea element for the caret to appear out of place, here's my own approach "correcting" for it

$('input').on("focus", function(){
    var scrollTopVal =  $(elementSelector).scrollTop();
    $(elementSelector).scrollTop(scrollTopVal + 1);
    $(elementSelector).scrollTop(scrollTopVal);
})

where elementSelector points to the container element for the input elements.

Parthenon answered 16/10, 2017 at 22:35 Comment(0)
S
0

This was a while ago and I think it was fixed on IOS11.x, of course we still need to support older versions, the suggestions above gave me a hint but none of them worked 4 my setup. I used onFocus to trigger a delayed function that adds/deletes a char to the current focused field. I'm using a flat angularJS/iOS hybrid.

on my html side

... setting up my form
<div ng-repeat="item in curReading.items"  >
      <label>{{item.lbl}}</label>
      <input type="text"
       ng-model="item.value"
       class="form-control"   
       onFocus="if(tweak4IOS == 1) setTimeout(pirouette_4_IOS, 1000);"
           placeholder="Enter Title"
          />
   </div>

on my JS side the relevant code is

function pirouette_4_IOS()
{
  try
  {

    document.activeElement.value += 'x';
    document.activeElement.value = document.activeElement.value.slice(0,-1);
  }
  catch(err)
  {
    alert(err.message);
  }
  finally
  {
    tweak4IOS = 0;
  }
}  // --- end of pirouette_4_IOS---
...
var tweak4IOS = 0; // init the flag

Finally in the Obj-C I set the var in the keyboard pop

- (void)keyboardDidShow:(NSNotification *)sender {
CGRect frame = [sender.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];

homeWeb.frame = CGRectMake(homeWeb.frame.origin.x, homeWeb.frame.origin.y,
                           homeWeb.frame.size.width , homeWeb.frame.size.height - frame.size.height     );

self.pinBottom2.constant = frame.origin.y;

// set the JS var so it is done only when keyboard pops initially
[homeWeb stringByEvaluatingJavaScriptFromString:@"tweak4IOS=1;"];
Sustain answered 9/2, 2018 at 15:17 Comment(0)
V
0

Banged my head against the wall with this one in a pretty old ionic / cordova application.

On iOS, caret was all over the place and input fields / forms where barely usable.

We decided to go for WKWebView instead of the default UIWebView deprecated since iOS 8. And bang! Working straight away. Don't forget to update your keyboard plugin as well.

As of today, for our ionic v1 application we're using :

[email protected]
[email protected]
Vondavonni answered 10/10, 2018 at 16:13 Comment(2)
Regrettably, WKWebView is not a definite solution either (at least as of iOS 12 developer beta 9). See bugs.webkit.org/show_bug.cgi?id=138201#c34 and the linked YouTube videoMildred
agreed. There is no simple fix working for everybody. Trade-offs are to be made. I posted here the trade off I made for my own context. Other solutions here worked but not as good as this one : a close to no code change drop-in replacement for the deprecated UIWebView.Vondavonni

© 2022 - 2024 — McMap. All rights reserved.