检测触摸设备虚拟键盘的打开或关闭

7

我有一个不太优雅的解决方法来应对这个问题,希望其他人已经有更加健壮的解决方案。


在触摸屏上,点击可编辑文本字段将弹出屏幕键盘,这将改变可用的屏幕空间。如果不加处理,这可能会隐藏关键元素或将页脚推出位置。
在笔记本电脑或台式计算机上,打开可编辑文本字段不会创建任何布局更改。
在我的当前项目中,我希望确保某些关键项即使虚拟键盘打开时也可见,因此需要检测是否发生了这种更改。然后我可以向元素添加类,以更改布局以适应键盘的存在。
在搜索现有解决方案时,我发现:
  1. 没有完美的方法知道您的代码是否在移动设备上运行
  2. 有些非移动设备具有触摸屏幕,并且可能也有键盘
  3. 焦点元素可能无法编辑
  4. contentEditable元素将打开屏幕键盘
  5. 地址栏可能会决定重新出现并占据必要的屏幕空间,同时虚拟键盘也会出现,进一步挤压可用空间。
我已经发布了下面我想到的解决方案。它依赖于在键盘焦点改变后一秒内检测窗口高度的变化。我希望你能提出一个更好的解决方案,可以跨平台、跨浏览器和跨设备测试。
我已在GitHub上创建了一个代码库
你可以在这里测试我的解决方案。
在我的测试中,如果用户使用带有触摸屏和键盘鼠标的计算机,并首先使用鼠标(取消)选择可编辑元素,然后立即更改窗口高度,可能会出现误报。如果您在计算机或移动设备上发现其他误报或漏报,请告诉我。
;(function (){

  class Keyboard {
    constructor () {
      this.screenWidth = screen.width        // detect orientation
      this.windowHeight = window.innerHeight // detect keyboard change
      this.listeners = {
        resize: []
      , keyboardchange: []
      , focuschange: []
      }

      this.isTouchScreen = 'ontouchstart' in document.documentElement

      this.focusElement = null
      this.changeFocusTime = new Date().getTime()
      this.focusDelay = 1000 // at least 600 ms is required

      let focuschange = this.focuschange.bind(this)
      document.addEventListener("focus", focuschange, true)
      document.addEventListener("blur", focuschange, true)

      window.onresize = this.resizeWindow.bind(this)
    }

    focuschange(event) {
      let target = event.target
      let elementType = null
      let checkType = false
      let checkEnabled = false
      let checkEditable = true

      if (event.type === "focus") {
        elementType = target.nodeName
        this.focusElement = target

        switch (elementType) {
          case "INPUT":
            checkType = true
          case "TEXTAREA":
            checkEditable = false
            checkEnabled = true
          break
        }

        if (checkType) {
          let type = target.type
          switch (type) {
            case "color":
            case "checkbox":
            case "radio":
            case "date":
            case "file":
            case "month":
            case "time":
              this.focusElement = null
              checkEnabled = false
            default:
              elementType += "[type=" + type +"]"
          }
        }

        if (checkEnabled) {
          if (target.disabled) {
            elementType += " (disabled)"
            this.focusElement = null
          }
        }

        if (checkEditable) {
          if (!target.contentEditable) {
            elementType = null
            this.focusElement = null
          }
        }
      } else {
        this.focusElement = null
      }

      this.changeFocusTime = new Date().getTime()

      this.listeners.focuschange.forEach(listener => {
        listener(this.focusElement, elementType)
      })
    }

    resizeWindow() {
      let screenWidth = screen.width;
      let windowHeight = window.innerHeight
      let dimensions = {
        width: innerWidth
      , height: windowHeight
      }
      let orientation = (screenWidth > screen.height)
                      ? "landscape"
                      : "portrait"

      let focusAge = new Date().getTime() - this.changeFocusTime
      let closed = !this.focusElement
                && (focusAge < this.focusDelay)            
                && (this.windowHeight < windowHeight)
      let opened = this.focusElement 
                && (focusAge < this.focusDelay)
                && (this.windowHeight > windowHeight)

      if ((this.screenWidth === screenWidth) && this.isTouchScreen) {
        // No change of orientation

        // opened or closed can only be true if height has changed.
        // 
        // Edge case
        // * Will give a false positive for keyboard change.
        // * The user has a tablet computer with both screen and
        //   keyboard, and has just clicked into or out of an
        //   editable area, and also changed the window height in
        //   the appropriate direction, all with the mouse.

        if (opened) {
          this.keyboardchange("shown", dimensions)
        } else if (closed) {
          this.keyboardchange("hidden", dimensions)
        } else {
          // Assume this is a desktop touchscreen computer with
          // resizable windows
          this.resize(dimensions, orientation)
        }

      } else {
        // Orientation has changed
        this.resize(dimensions, orientation)
      }

      this.windowHeight = windowHeight
      this.screenWidth = screenWidth
    }

    keyboardchange(change, dimensions) {
      this.listeners.keyboardchange.forEach(listener => {
        listener(change, dimensions)
      })
    }

    resize(dimensions, orientation) {
      this.listeners.resize.forEach(listener => {
        listener(dimensions, orientation)
      })
    }

    addEventListener(eventName, listener) {
      // log("*addEventListener " + eventName)

      let listeners = this.listeners[eventName] || []
      if (listeners.indexOf(listener) < 0) {
        listeners.push(listener)
      }
    }

    removeEventListener(eventName, listener) {
      let listeners = this.listeners[eventName] || []
      let index = listeners.indexOf(listener)

      if (index < 0) {
      } else {       
        listeners.slice(index, 1)
      }
    }
  }

  window.keyboard = new Keyboard()

})()

你可以使用 screen.availHeightscreen.availWidth 来检测屏幕尺寸的变化。我在这里找到了相关信息 - Ciro Spaciari
提示:向自己的问题添加答案的规范方式是...添加答案。这有助于在我们通常寻找答案的地方找到答案。发布问题时,甚至有一个复选框“回答你自己的问题”。 - tanius
5个回答

6

有一个新的实验性API专门用于跟踪由于键盘出现和其他移动设备诡异行为而导致的尺寸变化。

window.visualViewport

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

通过监听调整大小事件并将其与所谓的“布局视口”的高度进行比较。如果它发生了显着变化,例如可能是30像素。你可能会推断出一些像“键盘正在显示”的东西。

if('visualViewport' in window) {
  window.visualViewport.addEventListener('resize', function(event) {
    if(event.target.height + 30 < document.scrollElement.clientHeight) {
        console.log("keyboard up?");
    } else {
        console.log("keyboard down?");
    }
  });
}

上面的代码未经测试,我怀疑缩放可能会触发误报,可能还需要检查缩放变化。


在移动版Safari中,document.scrollElement未定义,并且似乎该错误被吞噬了。至少在BrowserStack中是这样。 - jcubic
1
另一个问题是这只适用于Safari浏览器。当使用window.innerHeight时,它在Android上无法正常工作,因为event.target.height始终与innerHeight相同。 - jcubic

3

使用 visualViewPort

这个灵感来自于on-screen-keyboard-detector。它可以在Android和iOS上使用。

if ('visualViewport' in window) {
  const VIEWPORT_VS_CLIENT_HEIGHT_RATIO = 0.75;
  window.visualViewport.addEventListener('resize', function (event) {
    if (
      (event.target.height * event.target.scale) / window.screen.height <
      VIEWPORT_VS_CLIENT_HEIGHT_RATIO
    )
      console.log('keyboard is shown');
    else console.log('keyboard is hidden');
  });
}

使用虚拟键盘 (virtualKeyboard)的另一种方法

这个方法可以解决问题,但是iOS目前还不支持

if ('virtualKeyboard' in navigator) {
  // Tell the browser you are taking care of virtual keyboard occlusions yourself.
  navigator.virtualKeyboard.overlaysContent = true;
  navigator.virtualKeyboard.addEventListener('geometrychange', (event) => {
    const { x, y, width, height } = event.target.boundingRect;
    if (height > 0) console.log('keyboard is shown');
    else console.log('keyboard is hidden');
});

来源:https://developer.chrome.com/docs/web-platform/virtual-keyboard/

虚拟键盘是一种 Web 技术,可在触摸屏上模拟物理键盘。它可以通过 HTML 和 CSS 实现,并且支持多种语言和布局。

虚拟键盘 API 可以让开发者控制虚拟键盘的显示、隐藏和属性设置。此外,还可以使用 JavaScript 监听键盘事件。

如果您需要在您的网站或应用程序中实现虚拟键盘,可以使用这些 API 来创建一个自定义的虚拟键盘。


2

由于没有直接检测键盘打开的方法,你只能通过高度和宽度来检测。查看更多

在JavaScript中,screen.availHeightscreen.availWidth可能会有所帮助。


1
我正在检测虚拟键盘的可见性,代码如下:
window.addEventListener('resize', (event) => {
  // if current/available height ratio is small enough, virtual keyboard is probably visible
  const isKeyboardHidden = ((window.innerHeight / window.screen.availHeight) > 0.6);
});

0

这是一个难以解决的问题。您可以尝试在输入元素聚焦时隐藏页脚,并在失焦时显示,但在iOS上并不总是可靠的。偶尔(比如在我的iPhone 4S上十次中有一次),聚焦事件似乎无法触发(或者可能与JQuery Mobile存在竞争条件),页脚就无法隐藏。

经过多次尝试和错误,我想出了这个有趣的解决方案:

<head>
    ...various JS and CSS imports...
    <script type="text/javascript">
        document.write( '<style>#footer{visibility:hidden}@media(min-height:' + ($( window ).height() - 10) + 'px){#footer{visibility:visible}}</style>' );
    </script>
</head>

基本上:使用JavaScript确定设备窗口的高度,然后动态创建一个CSS媒体查询,在窗口高度缩小10个像素时隐藏页脚。由于打开键盘会重新调整浏览器显示大小,因此在iOS上永远不会失败。由于它使用CSS引擎而不是JavaScript,速度更快、更顺畅!
注意:我发现使用“visibility:hidden”比“display:none”或“position:static”更少出现故障,但你可能会有所不同。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接