兼容移动端ios,安卓,web端底部软键盘弹出,输入框被遮挡问题

发布于:2025-03-04 ⋅ 阅读:(10) ⋅ 点赞:(0)

思路:

1.监听键盘高度的变化
要监听键盘高度的变化,我们得先看看在键盘展开或收起的时候,分别会触发哪些浏览器事件:

  • iOS 和部分 Android 浏览器 展开:键盘展示时会依次触发 visualViewport resize -> focusin
    -> visualViewport scroll,部分情况下手动调用 input.focus 不触发 focusin 收起:键盘收起时会依次触发 visualViewport resize -> focusout -> visualViewport scroll

    -其他 Android 浏览器 展开:键盘展示的时候会触发一段连续的 window resize,约过 200 毫秒稳定
    收起:键盘收起的时候会触发一段连续的 window resize,约过 200 毫秒稳定,但是部分手机上有些异常的 case:键盘收起时viewport 会先变小,然后变大,最后再变小

if (window.visualViewport) {
  window.visualViewport?.addEventListener("resize", listener);
  window.visualViewport?.addEventListener("scroll", listener);
} else {
  window.addEventListener("resize", listener);
}
​
window.addEventListener("focusin", listener);
window.addEventListener("focusout", listener);

2.获取「键盘顶部距离视口顶部的高度」
键盘顶部距离视口顶部的高度 = 视口当前的高度 + 视口滚动上去高度
3.设置 input 的位置
键盘距离页面顶部高度」再减去「元素高度」,从而获得「当前元素的位移」

代码

import EventEmitter from "eventemitter3";

// 默认屏幕高度
const DEFAULT_HEIGHT = window.innerHeight;
const MIN_KEYBOARD_HEIGHT = 200;

export const KeyboardEvent = {
  Show: "keyboardShow",
  Hide: "keyboardHide",
  PositionChange: "keyboardPositionChange",
};

class KeyboardObserver extends EventEmitter {
  constructor() {
    super();
    this.inited = false;
    this.lastWinHeight = DEFAULT_HEIGHT;
    this.canChangeStatus = true;
    this._unbind = () => { };
  }

  // 键盘初始化
  init() {
    if (this.inited) {
      return;
    }
    const listener = () => this.adjustPos();

    if (window.visualViewport) {
      window.visualViewport.addEventListener("resize", listener);
      window.visualViewport.addEventListener("scroll", listener);
    } else {
      window.addEventListener("resize", listener);
    }

    window.addEventListener("focusin", listener);
    window.addEventListener("focusout", listener);

    this._unbind = () => {
      if (window.visualViewport) {
        window.visualViewport.removeEventListener("resize", listener);
        window.visualViewport.removeEventListener("scroll", listener);
      } else {
        window.removeEventListener("resize", listener);
      }

      window.removeEventListener("focusin", listener);
      window.removeEventListener("focusout", listener);
    };

    this.inited = true;
  }

  unbind() {
    this.inited = false;
    this._unbind();
  }

  // 调整元素位置
  adjustPos() {
    // 获取当前视口高度
    const height = window.visualViewport
      ? window.visualViewport.height
      : window.innerHeight;

    // 获取键盘高度
    const keyboardHeight = DEFAULT_HEIGHT - height;

    // 获取键盘顶部距离视口顶部的距离
    const top = height + (window.visualViewport?.pageTop || 0);

    this.emit(KeyboardEvent.PositionChange, { top });

    const diffHeight = height - this.lastWinHeight;

    this.lastWinHeight = height;

    // 如果高度减少,且减少高度大于 200,则视为键盘弹起
    if (diffHeight < 0 && keyboardHeight > MIN_KEYBOARD_HEIGHT) {
      this.onKeyboardShow({ height: keyboardHeight, top });
    } else if (diffHeight > 0) {
      this.onKeyboardHide({ height: keyboardHeight, top });
    }
  }

  onKeyboardShow(keyboardInfo) {
    if (this.canChangeStatus) {
      this.emit(KeyboardEvent.Show, keyboardInfo);
      this.setCanChangeStatus();
    }
  }

  onKeyboardHide(keyboardInfo) {
    if (this.canChangeStatus) {
      this.emit(KeyboardEvent.Hide, keyboardInfo);
      this.setCanChangeStatus();
    }
  }

  setCanChangeStatus() {
    this.canChangeStatus = false;
    const timer = setTimeout(() => {
      clearTimeout(timer);
      this.canChangeStatus = true;
    }, 200);
  }
}

const keyboardObserver = new KeyboardObserver();

export default keyboardObserver;

组件

<template>
  <div>
    <button @click="focusInput">点击我</button>
    <!-- <div>{{ statusText }}</div> -->
    <input
      ref="elRef"
      class="block"
      :style="{ transform: `translateY(${top}px)` }"
    />
  </div>
</template>

<script>
import keyboardObserver, { KeyboardEvent } from "./keyboard";

export default {
  name: 'App',
  data() {
    return {
      // statusText: '',
      top: window.innerHeight,
    };
  },
  created() {
    this.initKeyboardObserver();
  },
  beforeDestroy() {
    keyboardObserver.unbind();
  },
  methods: {
    initKeyboardObserver() {
      keyboardObserver.init();

      keyboardObserver.on(KeyboardEvent.PositionChange, this.onPositionChange);
      // keyboardObserver.on(KeyboardEvent.Show, this.onKeyboardShow);
      // keyboardObserver.on(KeyboardEvent.Hide, this.onKeyboardHide);
    },
    onPositionChange({ top }) {
      this.top = top - this.$refs.elRef.clientHeight;
    },
    // onKeyboardShow() {
    //   this.statusText = 'show';
    // },
    // onKeyboardHide() {
    //   this.statusText = 'hide';
    // },
    focusInput() {
      this.$refs.elRef.focus();
    },
  },
};
</script>

<style>
html,
body {
  margin: 0;
  padding: 0;
  height: 100%;
  width: 100%;
}

#app {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  width: 100%;
}

.block {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 50px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: 0.2s all;
  background-color: #000;
  color: #fff;
}

</style>

使用
页面初始化时输入框获取焦点,并平移键盘弹起的平移高度
初始化监听initKeyboardObserver

参考:
https://juejin.cn/post/7338335869709385780