vue 移动端 + Pc 端实现监听软键盘弹出收起 hooks

149 阅读3分钟

image.png

前言

随着移动设备的普及和屏幕尺寸的多样化,开发者需要确保网站或应用在不同设备上都能提供一致且流畅的交互体验。其中一个常被忽视的细节是软键盘的交互——在移动设备上输入时,软键盘的弹出和收起会改变页面的可用空间,影响布局和用户操作。

在构建同时适配移动端和PC端的应用时,监听软键盘的弹出和收起成为了一个不可忽视的需求。这不仅涉及到布局的动态调整,还可能影响到表单的焦点管理、动态表单字段的加载、以及用户输入的即时反馈等。

思路

情况分析

  1. pc端没有软键盘不需要监听
  2. 移动端-Android:软键盘弹起时浏览器窗体高度变小,收起时高度变大
  3. 移动端-IOS:软键盘弹起和收起时分别会触发 focusin和focusout 事件

实现目标

实现一个 hooks ,入参包含 需要进行操作的元素 出参为一个表示软键盘弹出和收起的响应式变量

实现步骤

步骤一 判断当前浏览器环境


// utils/machine.ts

const ua = typeof window === "object" ? window.navigator.userAgent : ""

let _isIOS = -1
let _isAndroid = -1

/**
 * 是否是 IOS
 */
export function isIOS() {
  if (_isIOS === -1) {
    _isIOS = /iPhone|iPod|iPad/i.test(ua) ? 1 : 0
  }
  return _isIOS === 1
}

/**
 * 是否是安卓
 */
export function isAndroid() {
  if (_isAndroid === -1) {
    _isAndroid = /Android/i.test(ua) ? 1 : 0
  }
  return _isAndroid === 1
}

/**
 * 是否是pc
 */
export function isPc() {
  return !isIOS() && !isAndroid()
}

步骤二 Pc端实现

pc端无需实现 直接为 false


/**
* 当前是否打开软键盘
*/
const open = ref(false)

onMounted(() => {
    if (isPc()) {
      open.value = false
    }
})

步骤三 Android端实现

Android端需监听整个屏幕的高度,在高度变化时,动态改变响应式变量 open 的值


/**
* 当前是否打开软键盘
*/
const open = ref(false)

// 从 lodash 中引入方法 获取整个窗口的高度
const { height } = useWindowSize()

watch(height, (newHeight, oldHeight) => {
    if (isAndroid()) {
      if (oldHeight - newHeight > 50) {
        open.value = true
      }
      else {
        open.value = false
      }
    }
})

步骤四 IOS端实现

在生命周期 onMounted 阶段去注册 focusin,与 focusout 方法,在 onUnmounted 内注销。传入el会控制在布局内的某个元素范围内监听

function focusin() {
    open.value = true
}

function focusout() {
    open.value = false
}

// 获取当前需要监听的元素
const getEl = (el?: Listener) => {
    if (!el) {
      return window.document.body
    }
    return isRef(el) ? el.value : isString(el) ? document.querySelector(el) : el
}

onMounted(() => {

    const listener = getEl(listenerRef)
    
    // ios键盘弹出事件注册
    if (isIOS() && listener) {
      listener?.addEventListener("focusin", focusin)
      listener?.addEventListener("focusout", focusout)
    }
})

// 事件注销
onUnmounted(() => {
    const listener = getEl(listenerRef)
    if (isIOS() && listener) {
      listener.removeEventListener("focusin", focusin)
      listener.removeEventListener("focusout", focusout)
    }
})

应用

<script lang="ts" setup>

const containerRef = useTemplateRef<HTMLDivElement>("containerRef")

// open 为响应式变量可直接绑定或监听其变化
const { open } = useSoftKeyboard(containerRef)

</script>

源码展示

import type { ShallowRef } from "vue"
import { isAndroid, isIOS, isPc } from "@/utils"
import { useWindowSize } from "@vueuse/core"
import { isString } from "lodash-es"
import { isRef, onMounted, onUnmounted, ref, watch } from "vue"

export type Listener = ShallowRef<HTMLDivElement | null> | HTMLElement | string

export const useSoftKeyboard = (listenerRef?: Listener) => {
  /**
   * 当前是否打开软键盘
   */
  const open = ref(false)

  const { height } = useWindowSize()

  function focusin() {
    open.value = true
  }

  function focusout() {
    open.value = false
  }

  const getEl = (el?: Listener) => {
    if (!el) {
      return window.document.body
    }
    return isRef(el) ? el.value : isString(el) ? document.querySelector(el) : el
  }

  onMounted(() => {
    if (isPc()) {
      open.value = false
    }

    const listener = getEl(listenerRef)

    if (isIOS() && listener) {
      listener?.addEventListener("focusin", focusin)
      listener?.addEventListener("focusout", focusout)
    }
  })

  onUnmounted(() => {
    const listener = getEl(listenerRef)
    if (isIOS() && listener) {
      listener.removeEventListener("focusin", focusin)
      listener.removeEventListener("focusout", focusout)
    }
  })

  watch(height, (newHeight, oldHeight) => {
    if (isAndroid()) {
      if (oldHeight - newHeight > 50) {
        open.value = true
      }
      else {
        open.value = false
      }
    }
  })

  return { open }
}