127.移动端常见问题及其解决方案
具体启动参考项目地址的readme文件即可,将 11.153.55.172 改为你电脑的 ipv4 地址即可访问,手机能看到bug效果:http://11.153.55.172:8080/#/index
安卓和IOS日期格式兼容性的问题
如上所示,苹果的系统包括系统浏览器Safari均有此问题
出现原因:
-
安卓:Android端是兼容以下两种日期格式的(
yyyy-MM-dd HH:mm:ss
、yyyy/MM/dd HH:mm:ss
) -
IOS:只兼容(
yyyy/MM/dd HH:mm:ss
)格式ios系统不支持
2020-10-29 00:00:00
格式的时间,只识别2020/10/29 00:00:00
这种格式
带来的影响:一般我们会需要使用后端返回的时间格式来进行大小的比较?诸如判断自选卡是否过期的需求。或者是一些显示上的问题,可能出现NaN。
解决方案一:
- 统一使用
yyyy/MM/dd HH:mm:ss
的格式
解决方案二:
-
对于
yyyy-MM-dd HH:mm:ss
,在IOS端进行单独的替换time = time.replace(/-/g, '/')
用户端的时间是不可靠的,大部分时间最好以服务端的时间为准
比如在美国东部的时间,就不太相同,一些涉及了时间的业务(比如自选卡的领取时间、使用时间的判断都应该用服务端的时间)
用户端的时间是可以随时调整的,不管是电脑的还是手机的,所以涉及业务的时候,最好使用服务端返回的时间。
IOS中input type=number
不生效的解决办法
<input type="number">
在安卓上无问题。
解决方案:
添加pattern:<input type="number" pattern="[0-9]*">
iphone及ipad下输入框默认出现内阴影
解决方案:
-webkit-appearance: none;
安卓line-height不等于我们设置的line-height问题
这个问题挺坑的,之前在商城系统里,我的ios手机看了一直没问题,但是一到安卓手机上就会有问题。。。
网上有很多类似问题的帖子
出现原因:
以下抄自知乎,简而言之,安卓系统问题。
导致这个问题的本质原因可能是Android在排版计算的时候参考了primyfont字体的相关属性(即HHead Ascent、HHead Descent等),而primyfont的查找是看
font-family
里哪个字体在fonts.xml里第一个匹配上,而原生Android下中文字体是没有family name的,导致匹配上的始终不是中文字体,所以解决这个问题就要在font-family
里显式申明中文,或者通过什么方法保证所有字符都fallback到中文字体
作者:周祺链接:https://www.zhihu.com/question/39516424/answer/274374076来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
带来的影响:居中会有问题、多行省略的时候会发现文字出现了一半、and more。
解决方案:
-
使用
flex
布局作字体居中处理(字体大小为偶数,如果字体大小一定要是奇数的,可以通过js判断当前是安卓手机时,适当给偏移的标签加margin,【强行对某些安卓机型处理,如果对所有机型要求很高的话】)display: flex; height: 36px; align-items: center;
移动端调试不方便
用于解决电脑上开发出现了问题,但是在真机上又看不到代码的问题。
可以像我们在chrome调试那样,element、network等。
解决方案:
基本的用法:直接引入vConsole
import VConsole from 'vconsole'
const vConsole = new VConsole()
更好的用法:通过webpack插件的方式引用vConsole
如果你是用了vue-cli3来构建你的项目,可以通过以下方式根据环境动态添加
1、npm install vconsole-webpack-plugin --save-dev
2、新建 vue.config.js
或者在原有的基础上修改
const vConsolePlugin = require('vconsole-webpack-plugin')
module.exports = {
configureWebpack: config => {
// 非生产的时候可用
const debug = process.env.NODE_ENV !== 'production'
console.log('debug', debug)
let pluginsDev = [
new vConsolePlugin({
filter: [],
enable: debug
})
]
config.plugins = [...config.plugins, ...pluginsDev]
}
}
移动端滑动穿透问题
首先,pc的肯定直接就给body设置一个overflow:hidden的属性,就完美解决了。
这没什么好说的,主要是到手机上,大家会发现给父元素添加的属性根本就没有任何作用,遮罩层(全屏弹窗)出现了该怎么滑动还是怎么滑动。
所谓滑动穿透,是指滑动弹出层,却影响到外部页面。
所以出现的问题主要有2种情况:
外部不需要滚动,弹窗层不需要滚动外部不需要滚动,弹出层需要滚动- 外部需要滚动,弹出层不需要滚动
- 外部需要滚动,弹出层需要滚动
这里考虑两种角度解决该问题:
- 1、不传递给外部,外部就不会滚动。通过让弹出层,不传递滚动事件来解决。
- 2、外部临时不具备滚动属性,就不会滚动。弹出层弹出的时候,临时让外部强制变为不需要滚动的页面;弹出层关闭,再变回原样。
举例子1-外部需要滚动,弹出层不需要滚动;虽然顶部不能滚动,可是我们可以发现,滚动的事件被传递给了外部。
效果图如下:
其实看起来影响不是特别大,未必需要作修改。
解决方案:解决起来也较为简单,给弹窗层添加@touchmove.stop.prevent
,防止事件往下传递即可。
举例子2-外部需要滚动,弹出层也需要滚动。
弹出层的内容不可能总和我们预想的那样,只有少部分内容,也许有一天他也要滚动。
效果图如下:
通过上述效果,我们可以发现,双滚动的情况下,是严重影响用户体验的。基本是必须要改。
这里我们无法再通过@touchmove.stop.prevent
来进行解决,因为我们一旦使用了 prevent ,它本身也滑动不了了。
解决方案:所以通过临时改变外部的滚动属性来解决。
1、添加一个css类名,用于将拥有该类名的元素变为不可滚动的。
.dialog-open {
position: fixed;
overflow: hidden;
}
2、添加两个函数,分别在弹窗打开、弹窗关闭的时候调用。用于给指定元素添加、删除上面的类名。
afterDialogOpen() {
this.$refs.scrollEl.classList.add('dialog-open')
},
afterDialogClose() {
this.$refs.scrollEl.classList.remove('dialog-open')
}
3、在打开、关闭弹窗的函数内调用
// 打开弹窗
showPopup() {
this.dialogVisible = true
this.afterDialogOpen()
},
<!-- 点击关闭弹窗 -->
<button @click="dialogVisible = false;afterDialogClose();">关闭</button>
移动端的点击会延时300ms
出现原因:
原因就出在浏览器需要如何判断快速点击上,当用户在屏幕上单击某一个元素时候,例如跳转链接,此处浏览器会先捕获该次单击,但浏览器不能决定用户是单纯要点击链接还是要双击该部分区域进行缩放操作,所以,捕获第一次单击后,浏览器会先Hold一段时间t,如果在t时间区间里用户未进行下一次点击,则浏览器会做单击跳转链接的处理,如果t时间里用户进行了第二次单击操作,则浏览器会禁止跳转,转而进行对该部分区域页面的缩放操作。那么这个时间区间t有多少呢?在IOS safari下,大概为300毫秒。这就是延迟的由来。造成的后果用户纯粹单击页面,页面需要过一段时间才响应,给用户慢体验感觉,对于web开发者来说是,页面js捕获click事件的回调函数处理,需要300ms后才生效,也就间接导致影响其他业务逻辑的处理。
带来的影响:移动端体验不好
解决方案:通用解决方案(几乎都这么用)——引入fastclick。
import FastClick from 'fastclick'
FastClick.attach(document.body);
安卓input输入框弹起,导致固定定位的元素错位
这里的错位主要是底部
fixed
或者absolute
的元素,被强行顶了起来。
出现原因:
安卓的input和textarea触发的虚拟键盘弹起。
软键盘的弹出会被浏览器误认为是页面尺寸改变,并且触发 resize 事件。
解决方案:
使用安卓打开页面的情况下,软键盘弹起的时候隐藏底部的固定元素(比如一些按钮)
methods: {
keyboardEvent() {
if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') {
if (this.$refs.giftFooter.style.display !== 'none') {
this.$refs.giftFooter.style.display = 'none'
} else {
this.$refs.giftFooter.style.display = 'flex'
}
}
}
},
mounted () {
if (this.isAndroid() || true) {
window.addEventListener('resize', this.keyboardEvent);
}
},
deactivated() {
if (this.isAndroid() || true) {
window.removeEventListener('resize', this.keyboardEvent);
}
},