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

时间:2017-12-16 02:23:31

标签: javascript android ios keyboard event-handling

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

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

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

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

在线搜索现有解决方案时,我发现:

  1. 没有完美的方法可以知道您的代码是在移动设备上运行
  2. 有非移动设备有触摸屏,也可能有键盘
  3. 焦点元素可能无法编辑
  4. contentEditable元素将打开屏幕键盘
  5. 地址栏可能会决定重新出现并在虚拟键盘出现的同时占用必要的屏幕空间,从而进一步挤压可用空间。
  6. 我已经发布了我在下面提出的解决方案。它依赖于在键盘焦点变化的第二个内检测窗口高度的变化。我希望你可以有一个更好的解决方案来提出跨平台,跨浏览器和跨设备的测试。

    我创建了一个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()
    
    })()
    

3 个答案:

答案 0 :(得分:0)

由于无法直接检测键盘开口,因此只能通过高度和宽度进行检测。 See more

在javascript中screen.availHeightscreen.availWidth可能有帮助。

答案 1 :(得分: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'更少,但你的里程可能会有所不同。

答案 2 :(得分:0)

有一个新的实验性API,该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?");
    }
  });
}

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