本文是记录一些自己在工作中实际遇到和解决的一些兼容性问题。
【safari】window.open无效
window.open被广告商滥用,严重影响用户的使用,Safari安全机制将其默认拦截。
解决方案
- window.location.assign() 新开页面(add一个 history)
- window.location.replace(或改变 href) 替换当前页
【Android】物理返回键 H5监听拦截
拦截场景
网页本身有返回按钮,有特殊返回逻辑。
用户为了方便,会使用安卓手机自带的物理返回键,页面就会按照你浏览器history栈存储的路径来一层层返回。
网页本身设计期望的返回逻辑没有执行,被破坏。
pushState方法
window.history.back():移动到上一个访问页面,等同于浏览器的后退键。
window.history.forward():移动到下一个访问页面,等同于浏览器的前进键。
window.history.go(num):接受一个整数作为参数,移动到该整数指定的页面,比如go(1)相当于forward(),go(-1)相当于back()。
window.history.pushState():HTML5新增,在页面中创建一个 history 实体。直接添加到历史记录中。
window.history.replaceState():HTML5新增,用来在浏览历史中修改记录。
window.history.pushState(state, title, utl)
state:一个与指定网址相关的状态对象,popstate事件触发时,该对象会传入回调函数。如果不需要这个对象,此处可以填null。
title:新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null。
url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。
注:pushState方法不会触发页面刷新,只是导致history对象发生变化,地址栏会有反应。
popstate事件
- 当活动历史记录条目更改时,将触发popstate事件。
- 调用history.pushState()或history.replaceState()不会触发popstate事件。只有在做出浏览器动作时,才会触发该事件,如用户点击浏览器的回退按钮(或者在JS代码中调用history.back()或者history.forward()方法)。
- 不同的浏览器在加载页面时处理popstate事件的形式存在差异。页面加载时Chrome和Safari通常会触发(emit)popstate事件,但Firefox则不会。
拦截原理:
利用pushState方法和他不会触发popstate事件的特点(安卓物理返回键会)。
- 页面A,先调用pushState(),创建一个历史(B1),页面不会刷新。
- 监听popstate事件。
- 物理返回,回到A(拦截目的达到)。再次返回就会到A的上一个页面,所以需要4如下。
- 此时触发popstate事件,再处理方法中再次调用pushState(),创建一个历史(B2)。
- 下次物理返回,总是在A页面,如此循环
window.history.pushState(null, null, "#");
window.addEventListener("popstate", function(e) {
window.history.pushState(null, null, "#");
// 拦截了物理返回 ,同时也拦截了浏览器返回,history(back forward go)返回
})
缺陷
- 如果项目本身使用了pushState,则历史记录会有瑕疵(多了一个历史)
- 浏览器的后退按钮点击以及调用history.back()也会被当成按下了返回键
【iOS】页面返回不刷新
场景-转盘抽奖
- 页面A点击抽奖,转盘转动,同时调用抽奖接口。
- 接口返回401,跳页面B登录,登录成功,或点击返回按钮,返回页面A。
- 页面A转盘依然在转动,期望是已经停止转动。
解决方案一
页面在非激活状态(hidden)的时候,触发visibilitychange事件,注入停转逻辑。
const hiddenProperty = 'hidden' in document ? 'hidden' :
'webkitHidden' in document ? 'webkitHidden' :
'mozHidden' in document ? 'mozHidden' :
null;
const visibilityChangeEvent = hiddenProperty.replace(/hidden/i, 'visibilitychange');
const onVisibilityChange = function(){
if (document[hiddenProperty]) {
console.log('页面非激活');
// 转盘停止
if (that.turning) {
that.stopTurning()
}
}
}
document.addEventListener(visibilityChangeEvent, onVisibilityChange);
解决方案二(来自网友,未亲自验证)
window.onpageshow事件 :在每次加载页面时都会触发,类似于 onload 事件,但是onload 事件在页面第一次加载时触发,在页面从浏览器缓存中读取时不触发。
...
isIOS &&
window.onpageshow = function(event) {
if (event.persisted) {
window.location.reload()
}
};
【Wkwebview】虚拟键盘将页面顶出视窗,收起后页面未下移
- 页面无滚动条的情况存在该问题,有滚动条正常
- input/textarea触发软键盘,输入完成失焦后出现
解决方案:基类或者AppContainer中监听全局blur事件
isIOS &&
document.addEventListener('blur', event => {
// 当页面没出现滚动条时才执行,因为有滚动条时,不会出现这问题
// input textarea 标签才执行,因为 a 等标签也会触发 blur 事件
if (
document.documentElement.offsetHeight <= document.documentElement.clientHeight
&& ['input', 'textarea'].includes(event.target.localName)
) {
document.body.scrollIntoView
? document.body.scrollIntoView()
: window.scrollTo(0,0) // 回顶部
}
}, true ) // blur事件不冒泡,切记在捕获阶段执行
【Wkwebview】软键盘遮挡input
出现场景
弹出Dialog(fixed在页面底部),input密码输入框自动聚焦,调出软键盘,Dialog未上移被软件盘遮挡。
历史代码,UIwebview正常。APP升级WKwebview出现的兼容性问题。
Dialog 核心精简代码:
// 封装的滑入动画ui组件,css3(animation transform)
import Transform from '../Transform/Transform'
import Mask from '../Mask/Mask' // 遮罩蒙层ui组件
...
return (
<div
className={classNames('ActionDialog', className)}
>
<Mask className={this.state.maskStatus} />
<Transform {...this.props}>
<div className='body'>
<div className='title line-bottom'>
<i className='iconfont iconClose stat_closeTransform' onClick={this.onClose} />
{title}
</div>
<div className='content'>
<NumberInput
labelName="please input password "
showBotton={true}
className="password-input"
inputChangeCallback={(text, flag) => {
...
}}
/>
</div>
</div>
</Transform>
</div>
)
NumberInput 核心精简代码:
...
componentDidMount() {
this.tradingPwdHideInput.focus()
}
onFocus() {
this.setState({
focus: true
})
}
onBlur() {
this.setState({
focus: false
})
}
...
render() {
const { focus } = this.state
const { labelName, errMsg } = this.props
let arryDigits = [...'123456']
return (
<div className='NumberInput'>
{labelName}
<input
type='tel'
id='tradingPwdHideInput'
ref={ref => {
this.tradingPwdHideInput = ref
}}
onClick={() => {
this.tradingPwdHideInput.focus()
}}
onChange={this.tradingPwdChange.bind(this)}
onBlur={() => {
this.onBlur()
}}
onFocus={() => {
this.onFocus()
}}
/>
<ul className={classNames('numberbox', focus ? 'focus' : null)}>
{arryDigits.map((value, index) => {
return (
<li className={classNames({ border: focus })} key={index}>
<i className='passWord'>{this.tradingpwd.charAt(index)}</i>
</li>
)
})}
</ul>
<div className="input-bottom">
<div className="err-msg">{errMsg}</div>
</div>
</div>
)
}
主要css:
/*--------ActionDialog--------------*/
.ActionDialog {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 100vw;
height: 100vh; /*注意这里*/
z-index: 11;
.animationEnd{
position: absolute;
width: 100%;
bottom: 0;
height: 60%;
z-index: 12;
}
.content {
width: 100%;
height: 100%;
}
.body {
position: absolute;
z-index: 99;
width: 100%;
height: 100%;
}
}
/*--------Transform--------------*/
@keyframes down-in {
from {transform: translateY(-100%);}
to {transform: translateY(0%);}
}
@keyframes down-out {
from {transform: translateY(0%);}
to {transform: translateY(100%);}
}
.down-in {
animation:down-in .4s
}
.down-out {
animation:down-out .4s;
}
/*---------------Mask--------------------*/
.Mask {
position: fixed;
z-index: 11;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
/*---------------NumberInput--------------------*/
.NumberInput {
overflow: hidden;
position: relative;
padding-top: 40px;
padding-bottom: 1px; /*no*/
width: 100%;
background: #fff;
input {
position: absolute;
left: 0; /*no*/
z-index: 1;
width: 80%;
display: block;
overflow: hidden;
padding: 0 !important;
background-clip: padding-box;
font-family: Courier, monospace;
opacity: 0.01;
border: 0 none !important;
box-sizing: content-box !important;
outline: none;
-webkit-appearance: none;
/*解决ios光标问题 */
color: transparent;
text-indent: -100px;
-webkit-transform: scale(2);
}
.numberbox {
display: -webkit-box !important;
display: -ms-flexbox !important;
display: flex !important;
padding: 0 !important;
box-sizing: border-box;
display: block;
width: 100%; /*no*/
height: 50px;/*no*/
font-size: 24px; /*no*/
border: 1px solid $color-input-border; /*no*/
background-color: $color-bg;
background-clip: padding-box;
overflow: hidden;
li {
-webkit-box-flex: 1;
-ms-flex: 1;
box-flex: 1;
position: relative;
width: 17%;
line-height: 50px;/*no*/
margin-right: -1px; /*no*/
border-right: 1px solid $color-input-border; /*no*/
overflow: hidden;
text-align: center;
}
.tradingInputshow {
visibility: visible;
}
.tradingInputhide {
visibility: hidden;
}
.numberboxItem {
display: inline-block;
width: 14px; /*no*/
line-height: 50px;/*no*/
font-style: normal;
text-align: center;
overflow: hidden;
}
.passWord {
line-height: 44px; /*no*/
font-style: normal;
}
.numberboxItem:empty {
width: 12px; /*no*/
height: 12px; /*no*/
border-radius: 12px; /*no*/
background-clip: padding-box;
background-color: $color-input-text;
}
}
.focus {
border: 1px solid $color-input-border-focus; /*no*/
}
.border {
border-right: 1px solid $color-input-border-focus !important;/*no*/
}
}
开始怀疑是Dialog弹出后自动聚焦,虚拟键盘弹出,由于Dialog有一个CSS3滑入动画(0.4s)导致。但发现手动点击input触发也有同样的问题,此时Dialog已经完全展示。
后续解决办法如下:
解决办法一
Iphone7 iOS 13.1.2
表现为Dialog
不上移被遮挡Iphone8 iOS 11.1.0
iphone xs 12.4.1
表现为Dialog
上移250+,超出可视区域。
why scrollingElementonFocus() { if (Platform.getOS().name === 'iOS') { // 200其实是虚拟键盘的高度,网上建议用ScrollHeight,但是Dialog上移会太厉害 document.scrollingElement.scrollTop = 200; } ... } onBlur() { if (Platform.getOS().name === 'iOS') { // 失焦以后必须设为0,否则input会停留在上移以后的位置下不来 // 副作用:Dialog关闭以后,页面也回到顶部 // 如果要保留原来滚动位置,需要在Dialog前后增加scrollTop存储赋值逻辑 document.scrollingElement.scrollTop = 0; } ... }
解决办法二(iphoneX ios11.1.1)
- iphoneX ios11.1.1机器,上述解决办法一无效,发现根本无法设置document.documentElement.scrollTop
- 发现原来的Dialog顶层DIV设置了 height:100vh 样式, 删除后正常。
[IOS] 页面重定向 哈希丢失
- 场景:iOS手机( 包括微信,safari浏览器.chrome浏览器) 都出现。安卓手机都正常。
- 表象:访问公司短链,重定向到对应页面的长链的时候,丢失哈希。应该到详情页,结果到列表页(默认路由)。
- 原因:生成短链的长链URl使用了http,短链是https。页面重定向的时候会出现二次重定向(短链 > http 长链 > https 长链)。第二次重定向,哈希会丢失。
- 解决办法:
长链URl
使用https
重新生成短链。网上看到别人解决该办法是通过哈希前面加/
。自己测试,不成功。 - 根本原因:
ios
的HSTS
安全机制?
http://{domain}/path/knowledge/#/detail?id=101 🔴
http://{domain}/path/knowledge?locale=en_us/#/detail?id=101 🔴
https://{domain}/path/knowledge?locale=en_us#/detail?id=101 ✅
[Wkwebview] a链接无法跳转
- 原因:
target='_blank'
- 解决:删除
target
window.open(url)
同样无法跳转,改用window.location.assign(url)