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

标签 javascript android ios keyboard event-handling

对于这个问题,我有一个不太优雅的解决方法,我希望其他人可能已经有了更强大的解决方案。

<小时/>

在触摸屏上,点击可编辑文本字段将弹出屏幕键盘,这将更改可用的屏幕空间量。如果不加以处理,这可能会隐藏关键元素,或将页脚推到适当的位置。

在笔记本电脑或台式计算机上,打开可编辑文本字段不会造成此类布局更改。

在我当前的项目中,我希望确保即使虚拟键盘打开时某些关键项目也可见,因此我需要检测何时发生此类更改。然后,我可以向 body 元素添加一个类,以更改布局以适应键盘的存在。

在网上搜索现有的解决方案时,我发现:

  1. 没有完美的方法可以知道您的代码是否在移动设备上运行
  2. 有些非移动设备配有触摸屏,也可能配有键盘
  3. 焦点元素可能不可编辑
  4. contentEditable 元素将打开屏幕键盘
  5. 地址栏可能会在虚拟键盘出现的同时决定重新出现并占用必要的屏幕空间,从而进一步挤压可用空间。

我已在下面发布了我提出的解决方案。它依赖于检测键盘焦点变化后一秒内窗口高度的变化。我希望您能提出更好的解决方案,并经过跨平台、跨浏览器和跨设备的测试。

<小时/>

我创建了一个 repository on GitHub .
您可以测试我的解决方案here .

在我的测试中,如果用户使用带有触摸屏、键盘和鼠标的计算机,并且首先使用鼠标(取消)选择可编辑元素,然后立即更改窗口高度,则可能会出现误报。如果您在计算机或移动设备上发现其他误报或漏报,请告诉我。

<小时/>
;(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()

})()

最佳答案

有一个新的实验性 API,旨在准确跟踪由于键盘出现和其他类似的移动怪异而导致的尺寸变化。

window.visualViewport

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

通过监听调整大小事件并将高度与所谓的“布局视口(viewport)”的高度进行比较。看到它发生了很大的变化,比如大约 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?");
    }
  });
}

(上面的代码未经测试,我怀疑缩放可能会触发误报,可能还需要检查缩放更改)

关于javascript - 检测触摸屏设备上虚拟键盘的打开或关闭,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47841986/

相关文章:

android 打开 PackageManager 作为对话框

android - 如何将本地c++共享库.so导入Android studio 1.5?

ios - Swift iOS - 使用 CATransform3D .m34 属性更改 Swift 中的视角时,对变换应用额外的更改

javascript - 如何中止下载音频流

javascript - 确定哪个框架(名称)调用了 JavaScript 中的函数

javascript - 如何在 <br> 之后设置文本样式?

ios - 在 UITableView 中搜索“意外发现 nil”

javascript - 如何过滤与 react 表中给定输入完全相等的值?

android - Proguard 没有删除日志调用

ios - 如何使用 guard 同时解包 Swift 可选类型和强制转换类型?