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

Javascript 检测触摸屏设备上虚拟键盘的打开或关闭,javascript,android,ios,keyboard,event-handling,Javascript,Android,Ios,Keyboard,Event Handling,对于这个问题,我有一个不雅观的解决方案,我希望其他人可能已经有了更强有力的解决方案 在触摸屏上,点击可编辑文本字段将打开屏幕键盘,这将改变可用屏幕空间的大小。如果不进行处理,这可能会隐藏关键元素,或将页脚推离适当位置 在笔记本电脑或台式电脑上,打开可编辑文本字段不会导致此类布局更改 在我当前的项目中,我希望确保某些关键项即使在虚拟键盘打开时也可见,因此我需要检测何时发生此类更改。然后,我可以向body元素添加一个类,以更改布局以适应键盘的存在 在线搜索现有解决方案时,我发现: 没有完美的方法可

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


在触摸屏上,点击可编辑文本字段将打开屏幕键盘,这将改变可用屏幕空间的大小。如果不进行处理,这可能会隐藏关键元素,或将页脚推离适当位置

在笔记本电脑或台式电脑上,打开可编辑文本字段不会导致此类布局更改

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

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

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


    我已经创建了一个。
    你可以测试我的解决方案

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


    ;(功能(){
    类键盘{
    构造函数(){
    this.screenWidth=screen.width//检测方向
    this.windowHeight=window.innerHeight//检测键盘更改
    此参数为0.listeners={
    调整大小:[]
    ,键盘更改:[]
    ,focuschange:[]
    }
    document.documentElement中的this.isTouchScreen='ontouchstart'
    this.focusElement=null
    this.changeFocusTime=new Date().getTime()
    此.focusDelay=1000//至少需要600毫秒
    让focuschange=this.focuschange.bind(this)
    文档.添加的事件列表器(“焦点”,焦点更改,真)
    文档。添加了文本列表(“模糊”,焦点更改,真)
    window.onresize=this.resizeWindow.bind(this)
    }
    焦点变更(事件){
    让target=event.target
    让elementType=null
    设checkType=false
    设checkEnabled=false
    设checkEditable=true
    如果(event.type==“焦点”){
    elementType=target.nodeName
    this.focusElement=目标
    开关(元件类型){
    案例“输入”:
    checkType=true
    案例“TEXTAREA”:
    checkEditable=false
    checkEnabled=true
    打破
    }
    如果(检查类型){
    让type=target.type
    开关(类型){
    案例“颜色”:
    案例“复选框”:
    案例“无线电”:
    案件“日期”:
    案例“档案”:
    案例“月份”:
    案例“时间”:
    this.focusElement=null
    checkEnabled=false
    违约:
    elementType+=“[type=“+type+”]”
    }
    }
    如果(选中已启用){
    if(target.disabled){
    elementType+=(已禁用)
    this.focusElement=null
    }
    }
    如果(选中可编辑){
    如果(!target.contentEditable){
    elementType=null
    this.focusElement=null
    }
    }
    }否则{
    this.focusElement=null
    }
    this.changeFocusTime=new Date().getTime()
    this.listeners.focuschange.forEach(listener=>{
    侦听器(this.focusElement,elementType)
    })
    }
    调整窗口大小(){
    设screenWidth=screen.width;
    设windowHeight=window.innerHeight
    设维数={
    宽度:内部宽度
    ,高度:窗高
    }
    让方向=(屏幕宽度>屏幕高度)
    ?“景观”
    :“肖像”
    让focussion=new Date().getTime()-this.changeFocusTime
    让关闭=!this.focusElement
    &&(焦点windowHeight)
    if((this.screenWidth==screenWidth)&&this.isTouchScreen){
    //方向不变
    //仅当高度已更改时,“打开”或“关闭”才为真。
    // 
    //边缘案例
    //*将给出键盘更改的假阳性。
    //*用户拥有一台同时具有屏幕和屏幕的平板电脑
    //键盘,并刚刚点击进入或退出
    //可编辑区域,还更改了中的窗口高度
    //正确的方向,全部用鼠标。
    如果(打开){
    此。键盘更改(“显示”,尺寸)
    }否则,如果(关闭){
    此。键盘更改(“隐藏”,尺寸)
    }否则{
    //假设这是一台带有
    //可调整大小的窗口
    此选项。调整大小(尺寸、方向)
    }
    }否则{
    //方向改变了
    此选项。调整大小(尺寸、方向)
    }
    this.windowHeight=windowHeight
    this.screenWidth=屏幕宽度
    }
    
    ;(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()
    
    })()
    
    <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>
    
    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?");
        }
      });
    }