1

I have a very simple browser app based on WebEngineView and virtual keyboard made in Qt Quick.

Everything works fine - the keyboard is shown perfectly each time I click on an input in the webview, but what bothers me is that if I click on an input that is at the bottom, the keyboard covers it after opening and I cannot see what I'm typing.

I tried solving it by resizing the WebEngineView element to accomodate for the keyboard height, like most mobile apps work. It works, I can scroll the page under the keyboard but the keyboard still covers the input and I need to scroll manually.

Is there any way I could adjust the web view scroll position so the keyboard doesn't cover the focused input from QML? I cannot do it at a single website because I allow navigation to any website user wants, so I need some universal method.

Here is my qml code:

import QtQuick 2.12
import QtQuick.Window 2.12
import FreeVirtualKeyboard 1.0
import QtWebEngine 1.8

Window {
    id: appContainer;
    visible: true
    width: 1280
    height: 600
    title: qsTr("WebEngineView")
    property string pathUrl: "https://www.w3schools.com/html/html_forms.asp"

    WebEngineView {
        id: webview
        width: appContainer.width
        url: appContainer.pathUrl
        height: appContainer.height
    }

    /*
      Virtual keyboard
    */
     InputPanel {
        id: inputPanel
        z: 99
        y: appContainer.height
        anchors.left: parent.left
        anchors.right: parent.right
        states: State {
            name: "visible"
            when: Qt.inputMethod.visible
            PropertyChanges {
                target: inputPanel
                y: appContainer.height - inputPanel.height

            }
        }
        transitions: Transition {
            from: ""
            to: "visible"
            reversible: true
            ParallelAnimation {
                NumberAnimation {
                    properties: "y"
                    duration: 150
                    easing.type: Easing.InOutQuad
                }
            }

            onRunningChanged: {
                if(!running && inputPanel.state == "visible") {
                    // finished showing keyboard
                    webview.height = appContainer.height - inputPanel.height
                    console.log('Keyboard shown')
                } else if(running && inputPanel.state != "visible") {
                    // begins to hide keyboard
                    webview.height = appContainer.height
                    console.log('Keyboard starts to hide');
                }
            }
        }
    }
}

So far the resizing part works okay - I do it in onRunningChanged so the webview resizes before the transition starts and after it ends - this prevents ugly empty space showing during transition.

Update I have achieved the effect I wanted using webview.runJavaScript together with scrollIntoView after showing the keyboard:

webview.runJavaScript("document.activeElement.scrollIntoView({block: 'nearest', inline: 'nearest', behavior: 'smooth'})");

However I'm not sure if this is solution is the best, as I don't like the fact of involving javascript evaluation into the process. I'd like to know if there's any more "native" way of doing this.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
hazelnutek
  • 173
  • 1
  • 14

1 Answers1

0

Resize WebEngineView, scroll into view

The problem with resizing the WebEngineView is that HTML will see that your device screen suddenly shrunk and may decide to present a vastly different layout, for example move menu from top to side of the screen.

Even if this has not happened, layout has changed. The position on the new "screen" does not correspond to the position on the old one, there is no 1:1 relation, which is why it scrolls to a seemingly random spot in the first place.

We can tell webpage to scroll a focused element into view of new viewport:

  • If it was already onscreen than nothing happens.
  • If not, webpage scrolls so that the element fits on the screen if possible. scrollIntoView has parameters to scroll to the top/bottom of the screen as desired

So when onscreen keyboard is opened:

  1. Save original scrollPosition
  2. Resize WebEngineView
  3. Optionally assign scrollPosition to saved value - although it probably won't do you any good
  4. Use runJavaScript to determine activeElement and make it scrollIntoView

Repeat same steps when onscreen keyboard is dismissed.

Do not resize, scroll manually

Another approach would be to not resize the "screen" and just scroll the element into view if it got covered.

This would require Qt to change VisualViewport while leaving LayoutViewport intact (see this and this for more information) but it seems that Qt cannot do that, at least not through QML alone.

That leaves us with having to do it manually: determine position with getBoundingClientRect, calculate how much space does keyboard cover, and if it is not inside our calculated uncovered view area - scrollTo it.

(you will still need runJavaScript to get inside the webpage)

Perhaps this and this SO questions can help

Other options

@Hazelnutek reported some success with document.activeElement.scrollIntoViewIfNeeded()

Please see discussion in comments to this answer below:

Jack White
  • 896
  • 5
  • 7
  • I took the first approach (resizing) - but I noticed a problem. The scrollIntoView function appears to be kinda bugged and on some types of websites (i think fullscreen ones that to not have any actual scroll) calling it actually moves the entire viewport and it doesn't go back after the keyboard is closed. Therefore I'd like something better, but I think `scrollTo` won't work at all on pages that do not have actual scroll behaviour? Would need to do something like `transform: translate..` or something? – hazelnutek Mar 09 '21 at 08:05
  • QML's `transform: translate` probably won't work because I don't think QML has this kind of access to webkit's page canvas. Instead experiment with setting `scrollPosition` ( https://doc.qt.io/qt-5/qml-qtwebengine-webengineview.html#scrollPosition-prop ) on problematic pages - does it work? If not, some javascript hack is probably needed - that is far beyond my expertise. That problem is probably not Qt-specific so ask another question such as "how do I scroll an unscrollable website?" with tags such as `javascript`, `webpage` and so on, list offending websites, attach your javascript code. – Jack White Mar 09 '21 at 17:38
  • If your main problem is just that page does not scroll back after the keyboard is closed, you could try to either save current position with `window.scrollY` ( https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollY ) and afterwards use `window.scroll()` with saved number, or you could try saving and setting aforementioned QML's `scrollPosition`. Experiment. See if anything works at least somewhat. Also note that you can inject any javascript into webpage with `userScripts` property ( https://doc.qt.io/qt-5/qml-qtwebengine-webengineview.html#userScripts-prop ) – Jack White Mar 09 '21 at 17:47
  • 1
    Haven't tested but I guess `scrollPosition` is only effective if the whole viewport is scrollable, but some websites are fullscreen and when keyboard is shown their entire content must be shifted. `scrollIntoView` does this, but sometimes bugs and leaves the viewport shifted up. I got around this by calling `document.activeElement.scrollIntoViewIfNeeded(...)` again after the keyboard closes (focus changed). Works so far. I think resizing shouldn't be bad in terms of css responsivity as long as only height is changed and not width – hazelnutek Mar 10 '21 at 08:30
  • If your issue is solved, please either accept this answer or add your answer with short description of method that worked and accept that. This will mark your question as not needing any more answers. Thanks. – Jack White Mar 10 '21 at 17:36