25

I am new to mobile web/dev. My app is using jquery-mobile, phonegap and Compass (scss).

I have a problem on my login page :

logo and fields are contained in standard 'div' containers (data-role="content" data-type="vertical"). Background is colored.

when switching focus from login field to password field, the page slides up, which is what I don-t want to occur. I would like my logo and fields to stay in place, just like the Skype iOS App login page.

here is what happens :

bug description

I have tried several tricks, trying to block scroll events, or forcing page to scroll to 0,0, without success.

I am thinking about a new strategy now, maybe using top relative positioning for logo and fields, and catching focus events to scroll the page myself, on keyboard slide up (by animating top relative positioning coordinates).

Though this seems doable, I am wondering if this is the kind of work around used by the Skype iOS App team...

Any advice on technics used in this particular case is welcome!

cheers,

Fred

soleshoe
  • 1,215
  • 2
  • 12
  • 16
  • I'm pretty sure the Skype iOS app is using a native view for the login fields instead of how Phonegap works by using a WebView. Maybe trying to change the body height to 100% or putting the login/password in the upper half when the page opens. – Geek Num 88 Oct 06 '12 at 08:18
  • @GeekNum88 Sure. Soleshoe is looking for a solution to this using WebView. – Zulakis Oct 06 '12 at 08:19
  • @Zulakis - stupid enter key - I wasn't finished typing...sigh – Geek Num 88 Oct 06 '12 at 08:21
  • 3
    @Soleshoe nice representation... – Mr. Alien Oct 06 '12 at 08:22
  • I have noticed that top relative positioned elements do not slide up when the keyboard gets out (the reason why I thought about handling elements slide up myself) – soleshoe Oct 06 '12 at 10:59
  • I have done some research regarding the white band showing up when switching focus and page sliding up, and I have noticed that it is linked to a bad construction of my footer (not displayed on the mockups). I have to reconstruct my footer the right way, using data-tap-toggle="true" and custom css transition. The page scrolling on focus switching remains, but the white band is no more an issue. – soleshoe Oct 07 '12 at 11:21
  • jqm 1.1.1 documentation states this about scrollstart event (so I am afraid there is no solution for the moment): Triggers when a scroll begins. Note that iOS devices freeze DOM manipulation during scroll, queuing them to apply when the scroll finishes. We're currently investigating ways to allow DOM manipulations to apply before a scroll starts. – soleshoe Oct 17 '12 at 14:30
  • no enhancement regarding the scroll event trigger in jqm 1.2.0 – soleshoe Oct 18 '12 at 08:54

6 Answers6

39

I think you want to call focus on the element directly and use the 'preventScroll' option.

el.focus({preventScroll: true});

$('#el').focus((e) => {
  e.preventDefault();
  e.target.focus({preventScroll: true});
})

https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus

feco
  • 4,384
  • 5
  • 27
  • 44
Gabriel Mahan
  • 449
  • 4
  • 4
  • @mohsinulhaq in Firefox 66 (2019) preventScroll also works fine – crayze May 06 '19 at 08:39
  • 1
    UPDATE: I was wrong - it seems in Firefox preventScroll works fine only in horizontal scrolling, vertical scrolling is still applied :/ in Chrome preventScroll applies to both directions – crayze May 06 '19 at 08:47
  • 1
    I had been digging for a solution to a very similar issue for hours, and this worked beautifully. This should be marked as the answer, stat. – DevMike Apr 26 '22 at 09:00
5

I haven't tested this yet, but does e.preventDefault() stop the issue? Generally you use e.preventDefault() to stop default scrolling / dragging behaviour.

$(document).bind('focus', function(e){
  e.preventDefault();
});

or better

$(element).bind('focus', function(e){
  e.preventDefault();
});

or

$(document).bind('touchstart', function(e){
  e.preventDefault();
});

Would an input field work better?

$(":input").live({
  focus: function(e) {
    e.preventDefault();
  }
});
Jonathan Tonge
  • 1,520
  • 19
  • 25
  • The first and second solution do nothing regarding my problem. The third solution prevent the user from switching focus... The fourth solution do prevent the keyboard from sliding up on .focus() called over login field (but the keyboard does slide up when touching a field, and the problem remains the same)... – soleshoe Oct 06 '12 at 17:50
2

I finally did not succeed in finding any coding solution to handle my problem, but I have noticed that a chirurgical positioning allowed me to frieze the screen when switching between fields.

The space available for the whole login screen must not exceed the space available once the keyboard is visible, that is 200 pixels. Further more, to prevent scrolling when switching field, the last input field center position must not exceed 100 pixels max. Using CSS it is possible to play on padding and margin to achieve the desired result.

It should be possible to get extra vertical space by removing the keyboard "fields navigation bar" but I don't know how to do that neither :/

enter image description here

soleshoe
  • 1,215
  • 2
  • 12
  • 16
2

I had a similar issue (with the page scrolling on click/focus) and it took me a while to find the solution so I'm documenting it here.

I had a custom form field that when clicked would scroll the page to the top. Turns out that the input was being hidden using position: absolute; top: -99999999px; (because if you hide it properly then the focus events don't work). This placed the hidden input at the top of the page. Then when we clicked the label and the input was focussed, the page was scrolled to the top.

I was searching for things like "Scroll to top on focus/click" and all sorts but I couldn't find anything decent. The solution was to use right: -99999999px instead. I avoided using the typical left: -9999999px because apparently that can affect the site when using direction: rtl;

AlexMorley-Finch
  • 6,785
  • 15
  • 68
  • 103
1

There is a preventScroll option for focus():
el.focus({preventScroll: true});

And if you have to support Browsers (IE11, Safari<14?) that do not support focus-options, use this polyfill:

!function(){

    let supported = false;
    document.createElement('i').focus({
        get preventScroll() {
            supported = true;
        },
    });
    if (!supported) {
        let original = HTMLElement.prototype.focus;
        Element.prototype.focus = HTMLElement.prototype.focus = function(options){
            if (options.preventScroll) {
                const map = new Map();
                let p = this;
                while (p = p.parentNode) map.set(p, [p.scrollLeft, p.scrollTop]);
                original.apply(this, arguments);
                map.forEach(function(pos, el){
                    // todo: test: only if changed? does it trigger scroll?
                    // IE flickers
                    el.scrollLeft = pos[0]
                    el.scrollTop  = pos[1]
                });
            } else {
                original.apply(this, arguments);
            }
        }
    }

}();
Tobias Buschor
  • 3,075
  • 1
  • 22
  • 22
0

After searching for a few places, I haven't seen the best solution for this problem. And I tried my own workaround which does work well for preventing scrolling to the focused element, so I'd like to share here.

The idea is I block the entire body with position: fixed when a user focuses on my targeted elements. And then, immediately, I remove that blocker with setTimeout (I'm leveraging Event Loop)

Here is my code example

Javascript version

function blockScroller(event) {
  const body = document.getElementsByTagName("body")[0]
  body.classList.add("scroller-blocker")

  setTimeout(() => {
    body.classList.remove("scroller-blocker")
  })
}
select {
  width: 200px;
  height: 55em;
  overflow: auto;
}

.scroller-blocker {
  position: fixed;
  height: 100%;
}
<br><br><br><br><br><br><br><br><br>
<select multiple onfocus="blockScroller(event)">
  <option selected="">Lorem</option>
  <option selected="">ipsum</option>
  <option selected="">dolor</option>
  <option selected="">sit</option>
  <option selected="">amet</option>
  <option selected="">Lorem</option>
  <option selected="">ipsum</option>
  <option selected="">dolor</option>
  <option selected="">sit</option>
  <option selected="">amet</option>
</select>

jQuery version

function blockScroller(event) {
  const body = $("body")
  body.addClass("scroller-blocker")

  setTimeout(() => {
    body.removeClass("scroller-blocker")
  })
}
select {
  width: 200px;
  height: 55em;
  overflow: auto;
}

.scroller-blocker {
  position: fixed;
  height: 100%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<br><br><br><br><br><br><br><br><br>
<select multiple onfocus="blockScroller(event)">
  <option selected="">Lorem</option>
  <option selected="">ipsum</option>
  <option selected="">dolor</option>
  <option selected="">sit</option>
  <option selected="">amet</option>
  <option selected="">Lorem</option>
  <option selected="">ipsum</option>
  <option selected="">dolor</option>
  <option selected="">sit</option>
  <option selected="">amet</option>
</select>

For the comparison, here is the version without the above workaround

select {
  width: 200px;
  height: 55em;
  overflow: auto;
}

.scroller-blocker {
  position: fixed;
  height: 100%;
}
<br><br><br><br><br><br><br><br><br>
<select multiple>
  <option selected="">Lorem</option>
  <option selected="">ipsum</option>
  <option selected="">dolor</option>
  <option selected="">sit</option>
  <option selected="">amet</option>
  <option selected="">Lorem</option>
  <option selected="">ipsum</option>
  <option selected="">dolor</option>
  <option selected="">sit</option>
  <option selected="">amet</option>
</select>
Nick Vu
  • 14,512
  • 4
  • 21
  • 31