之前都是写pc,后来须要写h5移动端项目,会遇到一些自适应和兼容性等方面的问题,下面从本身写过的h5项目中稍稍作点总结。css
开启一个移动端项目的基础,首先是想好如何在代码中实现移动端适配。以前没有经验,第一个项目里简单粗暴地采用px写死的方法,以为很差,本项目采用的是像一位优秀同事习得的rem布局方法,它能够自适应不一样屏幕尺寸的设备,简单好用。html
这里咱们要用到两种单位:ios
1vw为视口宽度的1%,100vw为设备的宽度web
好比2rem=2倍的根字体大小浏览器
rem布局很是简单,其基本原理就是根据屏幕不一样的分辨率,动态修改根字体的大小,让全部的用rem单位的元素跟着屏幕尺寸一块儿缩放,从而达到自适应的效果。bash
拿个人项目来举例:咱们的设计稿是按照iphone6来设计的(iphone6实际宽度 375px),而设计稿上的宽度是750px,以前是直接把全部尺寸/2,如今我会这样实现自适应:markdown
html {
font-size: calc(100vw / 7.5);//除以的7.5是根据设计稿的屏幕宽度来定的,这样750px宽度下根元素字体大小则为750px/7.5=100px=1rem
}
复制代码
其中,100vw是设备宽度deviceWidth,这样就实现了不一样设备宽度下,动态修改根字体font-size的大小,好比:app
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 / 7.5 = 55.2px //iphone678 plus
复制代码
因此设计思路就是,根据设计稿将html的font-size设置为100px。好比750的设计稿,就除以7.5。less
这样设计的缘由是:实现适配只要在代码里把宽高直接将设计稿的尺寸除以100便可,换算很方便。iphone
比方设计稿上宽高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了 } 复制代码
以上设计思路的最大优势就是:方便计算。
正巧今天跟同事有在讨论适配问题,他给我提了一个本身历来没有注意到的问题,就是只是像上面这样设计的话,会无限制放大,在大屏上很很差看,评论里也有优秀的童鞋提到。因此我把同事分享的Tingglelaoo 这位大佬写的经过限制最大最小宽度进行优化的方法分享给你们。 其实很简单,就是给根元素字体大小限制最大最小值,以及 body 也增长最大最小宽度限制,这样就能够改善用户体验了。
html { //设置根字体大小单位为vw,页面元素的尺寸单位都设为rem,搭配vw和rem,可实现布局根据视口变化而变化 font-size: calc(100vw / 7.5); // 同时,经过Media Queries 限制根元素字体最大最小值 @media screen and (min-width: 320px) { font-size: 64px; } @media screen and (max-width: 540px) { font-size: 108px; } } // body 也增长最大最小宽度限制,避免默认100%宽度的 block 元素跟随 body 而过大太小 body { max-width: 540px; min-width: 320px; } #app { font-size: initial; } 复制代码
典型应用场景:关键元素高宽和位置都不变,只有容器元素在作伸缩变换。 针对这种需求,记住一个大佬总结好的适配原则就好:文字流式,控件弹性,图片等比缩放。
移动端弹出fixed弹窗的话,若底部背景页面存在滚动条,则滑动弹窗会导底部的背景页面跟着滚动,称为“滚动穿透”。可是这种状况在pc上是不会出现的。
若弹窗下层(背景页面上层)还有fixed定位的一遮罩,此时滑动遮罩背景页面也会跟着一块儿滚动。
打开弹窗时,给背景页面内容超出自身高度的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.滚动位置会丢失,页面会回到顶部 : 不管打开弹窗前页面背景滚动到什么位置,打开弹窗时,页面背景都会回到顶部。所以只能适用触发弹窗出现的按钮位于第一屏中的状况。
要说明的是:这个缺点是在其它大佬的实践过程当中看到的,,可是神奇的是,本身的是没有问题的。。若是有路过的童鞋遇到了,能够尝试下面其它的方法有没有帮助。。。
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.forbidScroll(); } else { this.allowScroll(); } } } 复制代码
缺点:
1.若弹窗内部有滚动,就没法滚动了
优势:
1.解决了上面的问题:弹窗打开时,背景页面处在打开弹窗前滚动到的位置,并非顶点处
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.fixedBody(); } else { this.looseBody(); } } } 复制代码
优势
1.底部背景页面和有滚动条的弹窗均可以滚动
2.能够记录背景页面滚动位置
缺点
1.当弹窗内部滚动到底部or顶部时,再去滑动背景页面,再回头滚动弹窗内部,又没法滚动了。
然而看别人却彷佛没有遇到过这个问题,因此暂时还没找到解决方案……
设计稿上的1px边框,咱们在iphone6上应为0.5px,由于设计稿宽度为750px,iphone6宽度为375px。而简单粗暴的写0.5px在ios8+上支持,但安卓不支持
直接列我常常用的比较完美的方案:
使用伪元素+绝对定位+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 { 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倍
优势: 全机型兼容,并且支持圆角
通常像这种项目里处处会用到的样式,最好把它提取成公共样式,好比在styles文件夹下建一个mixins.less文件,存放这些样式混合集,而后在用的组件里直接引用便可。 另外,还要进行媒体查询,兼容除iphone6觉得的各类机型。 最终以下:
//mixins.less //上边框 .setTopLine(@clolor) { content: " "; position: absolute; left: 0; top: 0; right: 0; height: 1px; background: @clolor; transform-origin: 0 0; /* 1.5倍屏 */ @media (-webkit-min-device-pixel-ratio:1.5){ transform: scaleY(0.6666); } /* 2倍屏 */ @media (-webkit-min-device-pixel-ratio:2){ transform: scaleY(0.5); } /* 3倍屏 */ @media (-webkit-min-device-pixel-ratio:3){ transform: scaleY(0.3333); } } 复制代码
//四条边框 .setAllLine(@clolor,@radius: 0) { content: ""; position: absolute; top: 0; left: 0; transform-origin: 0 0; border: 1px solid @clolor; @media (-webkit-min-device-pixel-ratio: 1.5){ width: 150%; height: 150%; transform: scale(0.66666); border-radius: @radius * 1.5; } @media (-webkit-min-device-pixel-ratio: 2){ width: 200%; height: 200%; transform: scale(0.5); border-radius: @radius * 2; } @media (-webkit-min-device-pixel-ratio: 3){ width: 300%; height: 300%; transform: scale(0.33333); border-radius: @radius * 3; } } 复制代码
//某组件 <style lang="less"> @import "~@/styles/mixins.less"; .operate-wrap { height: .92rem; padding: 0 .32rem; position: relative; &::before { .setTopLine(#ebebf0); } } </style> 复制代码
<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); } } 复制代码
由于键盘收起时输入框会失焦,所以,监听失焦事件便可
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); }); 复制代码
暂时没遇到这个问题
在滚动的容器上加上这句便可
-webkit-overflow-scrolling: touch;
复制代码
缘由: 首先分析一下ios和安卓键盘弹起时的表现
在 IOS 上,输入框(input、textarea 或 富文本)获取焦点,键盘弹起,页面(webview)并无被压缩,或者说高度(height)没有改变,只是页面(webview)总体往上滚了,且滚动高度(scrollTop)为软键盘高度。
一样,在 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很正常,是这样的: 两张图分别为弹出键盘前和点击输入框弹出键盘后:
对于背景页面有滚动,弹窗内部也有滚动区域的状况,点击弹窗里的输入框时,弹起键盘页面高度变小,致使此时可视区域呈现的正好都是弹窗内部滚动区域,所以只能滚动该区域内容,整个弹窗没法滑动显示全
问题图:
在弹起键盘时将遮罩高度变为如今视口的高度,由于高度变小,致使里面内容高度超出,此时手动加入滚动条便可滚动完整的弹窗,而不是只停留在弹窗内部有滚动条的区域
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按正常布局写到属于同一文档流中的页面最底下。