1. 首页

基于Vue的移动端h5项目总结

以前都是写pc,后来需要写h5移动端项目,会遇到一些自适应和兼容性等方面的问题,下面从自己写过的h5项目中稍稍做点总结。

一.自适应布局

1.1 vw与rem相结合实现适配

1.原理

开启一个移动端项目的基础,首先是想好如何在代码中实现移动端适配。之前没有经验,第一个项目里简单粗暴地采用px写死的方法,觉得不好,本项目采用的是像一位优秀同事习得的rem布局方法,它可以自适应不同屏幕尺寸的设备,简单好用。

这里我们要用到两种单位:

  • vw: viewport width,相对于视口的宽度

1vw为视口宽度的1%,100vw为设备的宽度

  • rem: 相对于根元素html字体大小的单位

比如2rem=2倍的根字体大小

rem布局非常简单,其基本原理就是根据屏幕不同的分辨率,动态修改根字体的大小,让所有的用rem单位的元素跟着屏幕尺寸一起缩放,从而达到自适应的效果

拿我的项目来举例:我们的设计稿是按照iphone6来设计的(iphone6实际宽度 375px),而设计稿上的宽度是750px,之前是直接把所有尺寸/2,现在我会这样实现自适应:


html { font-size: calc(100vw / 7.5);//除以的7.5是根据设计稿的屏幕宽度来定的,这样750px宽度下根元素字体大小则为750px/7.5=100px=1rem }

其中,100vw是设备宽度deviceWidth,这样就实现了不同设备宽度下,动态修改根字体font-size的大小,比如:


deviceWidth = 320,font-size = 320 / 7.5 = 42.6667px //iphone5 deviceWidth = 375,font-size = 375 / 7.5 = 50px //iphone678 X deviceWidth = 414,font-size = 414 / 6.4 = 55.2px //iphone678 plus

所以设计思路就是,根据设计稿将html的font-size设置为100px。比如750的设计稿,就除以7.5

这样设计的原因是:实现适配只要在代码里把宽高直接将设计稿的尺寸除以100即可,换算很方便

比方设计稿上宽高300px、96px的元素,就可以在代码中这么设置宽高


.test { width: 3rem height: .96rem } //反过来验证下,iphone6,显示宽度为3*50px=150px ok

但是我们又不能改变默认字体大小的展示,因此还要加一句#app的字体大小重置


html { font-size: calc(100vw / 7.5); } #app { font-size: initial; //重置页面字体大小恢复为浏览器默认16px,否则就显示成50px了 }

以上设计思路的最大优点就是:方便计算

2.优化

正巧今天跟同事有在讨论适配问题,他给我提了一个自己从来没有注意到的问题,就是只是像上面这样设计的话,会无限制放大,在大屏上很不好看,评论里也有优秀的童鞋提到。所以我把同事分享的Tingglelaoo 这位大佬写的通过限制最大最小宽度进行优化的方法分享给大家。 其实很简单,就是给根元素字体大小限制最大最小值,以及 body 也增加最大最小宽度限制,这样就可以改善用户体验了。


html { //设置根字体大小单位为vw,页面元素的尺寸单位都设为rem,搭配vw和rem,可实现布局根据视口变化而变化 font-size: calc(100vw / 7.5); // 同时,通过Media Queries 限制根元素字体最大最小值 @media screen and (max-width: 320px) { font-size: 64px; } @media screen and (min-width: 540px) { font-size: 108px; } } // body 也增加最大最小宽度限制,避免默认100%宽度的 block 元素跟随 body 而过大过小 body { max-width: 540px; min-width: 320px; } #app { font-size: initial; }

2.2 弹性布局

典型应用场景:关键元素高宽和位置都不变,只有容器元素在做伸缩变换。 针对这种需求,记住一个大佬总结好的适配原则就好:文字流式,控件弹性,图片等比缩放

image

二.遇到的问题

2.1 弹窗遇到滚动穿透(高频问题)

1.什么是滚动穿透?

移动端弹出fixed弹窗的话,若底部背景页面存在滚动条,则滑动弹窗会导底部的背景页面跟着滚动,称为“滚动穿透”。但是这种情况在pc上是不会出现的。

若弹窗下层(背景页面上层)还有fixed定位的一遮罩,此时滑动遮罩背景页面也会跟着一起滚动。

2.解决方案

(1)添加样式:overflow: hidden;

打开弹窗时,给背景页面内容超出自身高度的div添加样式:


overflow:hidden

关闭弹窗时,移除样式,或设


overflow:auto

e.g.


watch: { 'showModal'(val) { let ele = document.querySelector('内容超出自身高度的div'); if(val) { ele.style.overflow = 'hidden'; } else { ele.style.overflow = 'auto'; } } }

本项目目前用的就是这个方案

缺点:

1.滚动位置会丢失,页面会回到顶部 : 无论打开弹窗前页面背景滚动到什么位置,打开弹窗时,页面背景都会回到顶部。因此只能适用触发弹窗出现的按钮位于第一屏中的情况。

要说明的是:这个缺点是在其它大佬的实践过程中看到的,,但是神奇的是,自己的是没有问题的。。如果有路过的童鞋遇到了,可以尝试下面其它的方法有没有帮助。。。

(2)阻止移动端的touchmove事件

methods:{ preventDefault:function(e){e.preventDefault();}, //禁止背景页面滚动 forbidScroll(){ document.body.addEventListener('touchmove', this.preventDefault,{passive:false});//阻止默认事件 }, //解除背景页面禁止滚动 allowScroll(){ document.body.removeEventListener('touchmove', this.preventDefault,{passive:false});//打开默认事件 }, }, watch: { 'showModal'(val) { if(val) { this.fixedBody(); } else { this.looseBody(); } } }

缺点:

1.若弹窗内部有滚动,就无法滚动了

优点:

1.解决了上面的问题:弹窗打开时,背景页面处在打开弹窗前滚动到的位置,并不是顶点处

(3)body position:fixed定位,并记录背景页面滚动位置,关闭弹窗时还原滚动位置

methods:{ //body fixe定位,把当前的滚动位置赋值给css的top属性 fixedBody () { let scrollTop = document.body.scrollTop || document.documentElement.scrollTop document.body.style.cssText += 'position:fixed;width:100%;top:-' + scrollTop + 'px;' }, //清除fixed固定定位和top值;并恢复打开弹窗前滚动位置 looseBody () { let body = document.body body.style.position = 'static' let top = body.style.top document.body.scrollTop = document.documentElement.scrollTop = -parseInt(top) body.style.top = '' } }, watch: { 'showModal'(val) { if(val) { this.forbidScroll(); } else { this.allowScroll(); } } }

优点

1.底部背景页面和有滚动条的弹窗都可以滚动

2.可以记录背景页面滚动位置

缺点

1.当弹窗内部滚动到底部or顶部时,再去滑动背景页面,再回头滚动弹窗内部,又无法滚动了。

然而看别人却似乎没有遇到过这个问题,所以暂时还没找到解决方案……

2.2 移动端的1px问题

1.1px问题

设计稿上的1px边框,我们在iphone6上应为0.5px,因为设计稿宽度为750px,iphone6宽度为375px。而简单粗暴的写0.5px在ios8+上支持,但安卓不支持

2.解决方案

直接列我经常用的比较完美的方案:

使用伪元素+绝对定位+transform


<div class="wrap"> 内容区域 </div>

(1)设置border-top


.wrap { width: 100%; height: .8rem; padding: .24rem .32rem; position: relative; &::after { content: " "; position: absolute; left: 0; top: 0; right: 0; height: 1px; background: #ebebf0; transform-origin: 0 0; transform: scaleY(.5); } }

(2)设置border-bottom


.wrap { width: 100%; height: .8rem; padding: .24rem .32rem; //div相对定位 position: relative; //伪元素绝对定位 &::after { content: " "; position: absolute; left: 0; bottom: 0; right: 0; height: 1px; background: #ebebf0; transform-origin: 0 0; transform: scaleY(.5); } }

(3)设置border-left


.wrap { width: 100%; height: .8rem; padding: .24rem .32rem; margin-left: .32rem; position: relative; &::after { content: " "; position: absolute; left: 0; top: 0; bottom: 0; width: 1px; background: #ebebf0; transform-origin: 0 0; transform: scaleX(.5); } }

(4)四周的边框都设置


.wrap { //width: 100%; height: .8rem; padding: .24rem .32rem; margin-left: .32rem; margin-right: .32rem; position: relative; &::after { content: ""; position: absolute; top: 0; left: 0; width: 200%; height: 200%; transform-origin: 0 0; transform: scale(.5); border: 1px solid #ebebf0; } }

原理:将伪元素的长和宽先放大2倍,然后再设置一个边框,以左上角为中心,缩放到原来的0.5倍

image

优点: 全机型兼容,而且支持圆角

一般像这种项目里到处会用到的样式,最好把它提取成公共样式,比如在styles文件夹下建一个mixins.less文件,存放这些样式混合集,然后在用的组件里直接引用即可,像这样:


//mixins.less .setTopLine(@clolor) { content: " "; position: absolute; left: 0; top: 0; right: 0; height: 1px; background: @clolor; transform-origin: 0 0; transform: scaleY(.5); }

//某组件 <style lang="less"> @import "~@/styles/mixins.less"; .operate-wrap { height: .92rem; padding: 0 .32rem; position: relative; &::before { .setTopLine(#ebebf0); } } </style>

2.3 弹出数字键盘,只能输入数字


<input type="number" pattern="[0-9]*" @input="onInput($event.target.value)"v-model="number" />

若还需要限制数字位数,只能自己手动js控制,因为maxlength属性在type为number情况下不生效


onInput(val) { if (val >= 999) { this.number = val.slice(0,3); } }

2.4 各种兼容性问题:

1.ios问题

(1)ios12以上,收起键盘页面卡死,造成点击错乱

因为键盘收起时输入框会失焦,因此,监听失焦事件即可


document.body.addEventListener('focusout', () => { window.scroll(0, 0);//失焦后强制让页面归位即可 });

这种方法适用于只有一个输入框的场景,当有多个输入框时,会遇到输入框之间切换的情况。此时,每当切换,上一个聚焦元素会失焦,就会执行失焦事件处理函数,因为弹出键盘会让页面整体往上滚一点,执行了函数就会让页面归位掉下来,因此我们还需要去判断是输入框之间的切换,还是收起键盘

每次切换输入框,页面掉下来的问题效果图:

image

解决方法:


let isReset = true;//是否归位 document.body.addEventListener('focusin', () => { isReset = false; //聚焦时键盘弹出,焦点在输入框之间切换时,会先触发上一个输入框的失焦事件,再触发下一个输入框的聚焦事件 }); document.body.addEventListener('focusout', () => { isReset = true; setTimeout(() => { //当焦点在弹出层的输入框之间切换时先不归位 if (isReset) { window.scroll(0, 0);//确定延时后没有聚焦下一元素,是由收起键盘引起的失焦,则强制让页面归位 } }, 300); });

参考文章H5页面 ios 键盘收起后弹出层焦点错位

(2)ios点击延迟

暂时没遇到这个问题

(3)滚动卡顿

在滚动的容器上加上这句即可


-webkit-overflow-scrolling: touch;

2.安卓问题

(1)当输入框在可视区域偏下位置时,弹出键盘,输入框部分会被遮挡,且编辑过程无法向下滑动

原因: 首先分析一下ios和安卓键盘弹起时的表现

  • IOS 软键盘弹起表现

在 IOS 上,输入框(input、textarea 或 富文本)获取焦点,键盘弹起,页面(webview)并没有被压缩,或者说高度(height)没有改变,只是页面(webview)整体往上滚了,且滚动高度(scrollTop)为软键盘高度。

  • Android 软键盘弹起表现

同样,在 Android上,输入框获取焦点,键盘弹起,但是页面(webview)高度会发生改变,一般来说,可视区高度会减小(原高度减去软键盘高度),除了因为页面内容被撑开可以产生滚动,webview 本身不能滚动。

问题效果如下:

image

由图可见,fixed定位的底部footer正好遮住textarea的底部。

解决方法:


//window.onresize 监听页面大小变化,该方法只会在安卓执行 window.onresize = function () { if (document.activeElement.tagName === "INPUT" || document.activeElement.tagName === "TEXTAREA") { let ele = document.activeElement; setTimeout(()=>{ ele.scrollIntoView();//焦点元素滚到可视区域的问题 },0); } }

解决后的效果:

image

ios很正常,是这样的: 两张图分别为弹出键盘前和点击输入框弹出键盘后:

image

image

(2)当滚动遇到弹窗

对于背景页面有滚动,弹窗内部也有滚动区域的情况,点击弹窗里的输入框时,弹起键盘页面高度变小,导致此时可视区域呈现的正好都是弹窗内部滚动区域,因此只能滚动该区域内容,整个弹窗无法滑动显示全

问题图:

image

解决方案:

在弹起键盘时将遮罩高度变为现在视口的高度,因为高度变小,导致里面内容高度超出,此时手动加入滚动条即可滚动完整的弹窗,而不是只停留在弹窗内部有滚动条的区域


let originHeight = document.documentElement.clientHeight || document.body.clientHeight; //ios不会触发resize事件 window.onresize = function () { let resizeHeight = document.documentElement.clientHeight || document.body.clientHeight; if (resizeHeight < originHeight) { //键盘弹起 setTimeout(() => { document.querySelector('弹窗遮罩').style.height = document.body.offsetHeight + 'px'; document.querySelector('弹窗遮罩').style.overflow = 'scroll'; },0); } else { //键盘收起 document.querySelector('弹窗遮罩').style.height = 517 + 'px'; //517为弹窗原先高度 } }

另外还要备注一条:

之前弹窗底部按钮的footer部分用的是fixed定位,bottom为0,导致键盘弹出后,视口高度变小,footer改为相对现在的视口底部(也就是键盘顶部)fixed定位,会遮挡住弹窗内容一部分。 因此,还要同时取消使用fixed定位,改为将footer按正常布局写到属于同一文档流中的页面最底下。

作者:Wowoy
链接:https://juejin.im/post/5de72b1f51882512360d3910

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:基于Vue的移动端h5项目总结

链接:https://www.javascriptc.com/3068.html

« 【进阶15期】Object.assign 原理及其实现
推荐一波2018年好文汇总~»
Flutter 中文教程资源

相关推荐

QR code