移动端input开发问题

4,989

作为前端开发,工作中不可避免的要接触input,要基于input做一些定制化开发,但因为安卓ios系统的差异性,宿主app的webview选择(特指ios),app的历史遗留问题等等,会出现大量的兼容性问题

下面,对我开发过程中遇到的问题以及解决方案进行记录,希望能帮到你们

1. ios webview类型

ios下不同的app内置的webview可能不同,导致input行为不一致,所以我们先大致了解一下,目前ios系统下有两种webview:WK和UI

ios8之前,一直使用的是UI,但是从ios8开始,新增了WK类型,相比UI,WK加载速度更快,消耗内存更少,并且支持更多h5的特性,所以各大公司基本都会对此进行升级,但不排除有些app因为各种历史原因还在使用UI,比如我们,目前我们的h5页面运行在多个app内,有UI也有WK,自然在开发的时候做一些兼容判断

判断webview类型

// 暂没发现好的方法来判断webview的类型,除非ios开发告诉你
// 下面这个方式是通过某些只有wk支持的h5新特性来判断
function getIosWebviewType() {
    if (navigator.platform.substr(0, 2) === 'iP') {
        // iOS (iPhone, iPod or iPad)
        const lte9 = /constructor/i.test(window.HTMLElement);
        const nav = window.navigator; const ua = nav.userAgent;
        const idb = !!window.indexedDB;
        if (ua.indexOf('Safari') !== -1 && ua.indexOf('Version') !== -1 && !nav.standalone) {
            return 'Safari'
        } else if ((!idb && lte9) || !window.statusbar.visible) {
            return 'UI'
        } else if ((window.webkit && window.webkit.messageHandlers) || !lte9 || idb) {
            return 'WK'
        }
    }
    return 'unknown'
}

2. autofocus失效

不管是h5规范还是在caniuse上面查,这个属性都是支持的,但你在真机上面测试的时候,很多情况下,并不是你想要的效果

目前我测试情况如下:

  • ios
    • UIWebview,可以聚焦,有光标,能呼起键盘
    • WKWebview,可以聚焦,有光标,但呼不起键盘
  • 安卓
    • 暂时没发现问题(但不绝对)

网上了解到的原因是,苹果希望由用户来触发,输入这个操作是用户的意愿,而不是强制的,所以在新版本系统中禁止了这个属性,老版本的UI上面还没有这个限制

但庆幸的是,不管是安卓还是ios,都支持在touch或者click事件中同步的执行input.focus()来聚焦并呼起键盘,

如果业务场景符合这种情况,那可以通过这种方式来做

onClick = () => {
  this.input.focus()
}

但如果在点击事件中有异步操作时,同autofocus属性,只能聚焦,不能呼起键盘

onClick = () => {
  setTimeout(() => {
    this.input.focus()
  }, 1000)
  
  // 或者
fetch('/api/get').then(() => {
    this.input.focus()
  })
}

降级处理

如果业务场景不符合上面的情况,但又非要这个功能,有一种方案是,聚焦之后,强化一下聚焦的效果(因为原生的光标展示毕竟不明显)

  • 设置一个聚焦的样式,在input上也好,在页面其他元素也好,由自己产品特色以及UE决定
  • 适当的滚动页面,使其到视图中间

可参考

3. 聚焦问题

3.1 点击无法聚焦或者聚焦之后又失焦

大部分情况是移动端300ms问题引起的

一种是:app使用UIWebview但是h5页面没引入fastclick

另一种是:引入了fastclick,但需要对fastclick的focus进行优化,改为

FastClick.prototype.focus = function (targetElement) {
    targetElement.focus();
};

3.2 光标无法聚焦到点击位置

这个大概率也是fastclick导致的,可参考:链接

3.3 已有聚焦input的情况下点击其他input,会跳动

这个大概率也是fastclick导致,可参考上面那个链接

或者这样改fastclick源码,在onTouchEnd中新增判断,是否需要走原生聚焦逻辑

FastClick.prototype.onTouchEnd = function (event) {
    // ...
    if (targetTagName === 'label') {
        // ...
    } else if (this.needsFocus(targetElement)) {
      	// 新增
        if (!this.needsFocusInput()) return false

      	// ...
    }

    // ...
};

// 新增
// 已有聚焦元素的情况下,直接走原生input聚焦逻辑
FastClick.prototype.needsFocusInput = function () {
    const focusInput = document.querySelector('input:focus')
    return !focusInput
}

3.4 建议

上面这几个问题,大部分是fastclick导致的,但fastclick所解决的问题(300ms),目前大部分浏览器已经解决了,所以确认一下,如果你们的app不用UIWebview的话,那可以直接去掉fastclick了

fastclick github说明:

Note: As of late 2015 most mobile browsers - notably Chrome and Safari - no longer have a 300ms touch delay, so fastclick offers no benefit on newer browsers, and risks introducing bugs into your application. Consider carefully whether you really need to use it.

4. 失焦问题

4.1 希望保持聚焦

有时候,希望点击页面其他地方的时候,input保持聚焦状态,但浏览器默认行为是将input失焦

解决方案:在点击事件中,阻止默认行为

function onClick(e) {
	// 你的事件处理代码
	...
	
	e.preventDefault();
	// iphone有的机型下,没有阻止掉默认行为,主动再聚焦一下
	input.focus();
}

4.2 希望主动失焦

安卓某些机型有以下问题

  • 聚焦情况下,点击页面其他地方,不主动失焦
  • 某些情况下,主动隐藏键盘之后,input并没有失焦,但是用户触摸页面其他地方时,因为input还处于聚焦状态,会呼起键盘

针对性的解决方案也分两种

第一种:监听用户行为,主动失焦

const autoBlur = (e) => {
  const target = e.target
  const { tagName, className } = target
  // 点击非input区域
  if (tagName.toUpperCase() !== 'INPUT') {
    this.input.blur()
  }
}
document.body.addEventListener('touchstart', autoBlur)

第二种:监听键盘高度变化,主动失焦

const onKeyboardChange = (resize) => {
  // 有时候,比如number变成text,或者系统自动在键盘上面加一些装饰,键盘并没有隐藏,但是触发了resize
  // 测试大部分机型,所有的键盘肯定大于120高度了,所以加一个限制
  if (Math.abs(resize) < 120) return

  const show = resize > 0
  if (!show) {
    this.input.blur()
  }
}

function getClientHeight() {
    return document.documentElement.clientHeight || document.body.clientHeight;
}

// 记录原始高度
let originHeight = getClientHeight()

// 监听键盘变化
window.addEventListener('resize', () => {
  const resizeHeight = getClientHeight()

  const resize = originHeight - resizeHeight
  onKeyboardChange(resize);
  originHeight = resizeHeight;
}, false)

5. 聚焦之后滚动到视图内

大部分情况下,系统会帮你将input滚动到视图内,少数情况下,需要我们自己设置

  • 一种方案是,直接调用scrollIntoViewIfNeeded api

  • 另一种是,计算聚焦前后文档高度的变化差值,然后将body高度相应的调大,并且设置scrollTop,这种情况应该是出现在UIWebview下

// 正常处理
input.addEventListener('focus', () => {
  setTimeout(() => {
    this.input.scrollIntoViewIfNeeded();
  }, 300)
})
// UIWebview下的处理
function getClientHeight() {
    return document.documentElement.clientHeight || document.body.clientHeight;
}
const bodyHeight = getClientHeight()
const bodyOverflow = getComputedStyle(document.body).overflow

input.addEventListener('focus', () => {
  document.body.style.overflow = 'auto'
  setTimeout(() => {
    // alert(getClientHeight())
    let height = bodyHeight - getClientHeight()
    if (height < 0) height = height * -2
    document.body.style.height = `${bodyHeight + height}px`;
    document.body.scrollTop = height;
  }, 300)
})

// 如果设置了高度,在blur时要设置回来
input.addEventListener('blur', () => {
  document.body.style.height = `${bodyHeight}px`;
  document.body.style.overflow = bodyOverflow
})


加setTimeout是因为唤起键盘会有一个动画,我们需要在动画结束之后再滚动页面,否则计算出的高度不准

大部分情况下,键盘动画时间小于300

6. 强制光标到末尾

input.addEventListener('touchend', e => {
  const length = e.target.value.length
  e.target.setSelectionRange(length, length);
  e.preventDefault()
  e.target.focus()
})

7. 其他的一些问题

7.1 光标隐藏

某些场景下,需要自定义输入框,自定义光标等

安卓可以通过opacity: 0; color: transparent等将原生光标隐藏

但ios无法隐藏,只能将input框移动到视图外,先将input宽度设置足够大,然后左移:

input {
  width: 1000px;
  margin-left: -200px;
}

7.2 复制粘贴

安卓和ios都支持长按复制粘贴,但ios的前提是,光标必须在视图内,像上面那样将input左移之后,粘贴按钮不在视图内,无法粘贴

7.3 短信验证码(或其他数字)展示在系统键盘

新版ios(应该是12+,不确定)系统收到短信以后会直接将短信展示在键盘上面,用户一点就可以输入到input,但老版不支持,需要用户主动去短信复制,然后再粘贴到input,如果input是自定义的,需要注意上面问题

部分安卓机器(比如小米6+)收到短信,可直接点通知栏进行复制,同样会出现在键盘上,其他机型需要用户长按粘贴

8. 结语

上面基本是我开发过程中遇到的问题了,随着系统的迭代升级,很多问题会得到官方的解决,但是我们无法决定app是否升级,无法决定用户是否升级

所以,如果你们也遇到这些问题,希望可以给予你们启发,帮助