likes
comments
collection
share

解决H5在native中键盘弹起影响页面交互

作者站长头像
站长
· 阅读数 19

您好,如果喜欢我的文章,可以关注我的公众号「量子前端」,将不定期关注推送前端好文~

问题描述

native中拉起键盘再收回,滚动列表实际距离发生变化,被键盘一起弹上去了(我这里大约是400px的样子)

这是初始页面:

解决H5在native中键盘弹起影响页面交互

当我聚焦页面中某个Input后,再失去焦点,页面变成了这样:

解决H5在native中键盘弹起影响页面交互

可以看到页面被弹上去了,再次重复步骤页面会继续弹上去,排查了一下发现是在IOS中的问题。

解决方案

我这里想到的一个比较好的方案是监听弹起键盘收起键盘两个事件,在弹起时记录被破坏的页面scrollY,收起时恢复。

记录弹起时的页面scrollY挺简单的,直接这样:

window.addEventListener('focusin', () => {
    console.log('弹起前高度:', window.scrollY);
}, false)

这样思路就很简单了,代码实现一下:

const focusScrollHeight = useRef<number>(0);

  useEffect(() => {
    window.addEventListener('focusin', focusIn, false);
    window.addEventListener('focusout', focusOut, false);
    return () => {
      window.removeEventListener('focusin', focusIn, false);
      window.removeEventListener('focusout', focusOut, false);
    };
  }, []);

  const focusIn = () => {
    focusScrollHeight.current = window.scrollY;
  };

  const focusOut = (e) => {
    window.scrollTo(0, focusScrollHeight.current);
  };

把这段代码放在出现问题的页面里,发现页面会不稳定的回弹到顶部,排查下来应该是antd的组件触发了收起键盘的事件,所以考虑用类名定位的方案来搞定,同时我们直接封装成一个hook,在出现问题的页面中直接使用,就像这样:

useResetScrollY.ts

/**
 * @description: 声明式hook,在页面声明即可自动归位手机端键盘弹起撑开的高度
 * @param {string} listenerClassList 监听的类名
 * @return {*}
 */
const useResetScrollY = (listenerClassList: string[]) => {
  const focusScrollHeight = useRef<number>(0);

  useEffect(() => {
    window.addEventListener('focusin', focusIn, false);
    window.addEventListener('focusout', focusOut, false);
    return () => {
      window.removeEventListener('focusin', focusIn, false);
      window.removeEventListener('focusout', focusOut, false);
    };
  }, []);

  const focusIn = () => {
    focusScrollHeight.current = window.scrollY;
  };

  const focusOut = (e) => {
    const classNameList: string[] = Array.from(e?.target?.classList);
    if (classNameList?.some((item: string) => listenerClassList?.includes(item))) {
      window.scrollTo(0, focusScrollHeight.current);
    }
  };
};

export { useResetScrollY };

这里入参我们给出在页面中需要被监听的组件类名,一般来说都是InputTextarea这类表单组件,然后在这个页面我使用的是antdInput组件,所以使用是这样的:

const Page = () => {
    // ...
    
    if(isIos) {
        useResetScrollY(['adm-input-element']);
    }
    // ...
}

这样子在hook内部触发事件时只需要判断事件对象的classList与入参classList是否存在公共项的情况,就会触发键盘副作用归位的行为。

写在后面

这是笔者在业务中遇到的一个问题,简单总结记录,如果对于这种情况有更好的方案也欢迎留言讨论~

您好,如果喜欢我的文章,可以关注我的公众号「量子前端」,将不定期关注推送前端好文~