绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
基于Vue的移动端h5项目总结
2019-12-04 16:20:06

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


一.自适应布局

1.rem布局基础

开启一个移动端项目的基础,首先是想好如何在代码中实现移动端适配。之前没有经验,个参与的项目里简单粗暴地采用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.弹性布局

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


二.遇到的问题

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:;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: ;
        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: ;
        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: ;
        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: ;
        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倍

优点:

全机型兼容,而支持圆角

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);//失焦后强制让页面归位即可
});

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

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

解决方法:

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


(2)ios点击延迟

暂时没遇到这个问题

(3)滚动卡顿

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

-webkit-overflow-scrolling: touch;

2.安卓问题

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

原因:

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

IOS 软键盘弹起表现

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

Android 软键盘弹起表现

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

问题效果如下:

由图可见,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);
    }
}

解决后的效果:

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

(2)当滚动遇到弹窗

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

问题图:

解决方案:

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

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

来源:掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


分享好友

分享这个小栈给你的朋友们,一起进步吧。

星说
创建时间:2019-11-28 12:45:43
作为炎黄子孙,我们有很多知识渊博的祖人,这个祖人指的是在各领域登峰造极的学者论说,我们跟随强大基因的路线,学习,深化自己的知识体系,优化环境。
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

栈主、嘉宾

查看更多
  • unnamed persona
    栈主

小栈成员

查看更多
  • 外星人6
  • supergirlxu
  • unnamed person1
  • daxuesheng
戳我,来吐槽~