之前都是寫pc,後來須要寫h5移動端項目,會遇到一些自適應和兼容性等方面的問題,下面從本身寫過的h5項目中稍稍作點總結。css
開啓一個移動端項目的基礎,首先是想好如何在代碼中實現移動端適配。以前沒有經驗,第一個項目裏簡單粗暴地採用px寫死的方法,以爲很差,本項目採用的是像一位優秀同事習得的rem佈局方法,它能夠自適應不一樣屏幕尺寸的設備,簡單好用。html
這裏咱們要用到兩種單位:ios
1vw爲視口寬度的1%,100vw爲設備的寬度web
好比2rem=2倍的根字體大小瀏覽器
rem佈局很是簡單,其基本原理就是根據屏幕不一樣的分辨率,動態修改根字體的大小,讓全部的用rem單位的元素跟着屏幕尺寸一塊兒縮放,從而達到自適應的效果。bash
拿個人項目來舉例:咱們的設計稿是按照iphone6來設計的(iphone6實際寬度 375px),而設計稿上的寬度是750px,以前是直接把全部尺寸/2,如今我會這樣實現自適應:app
html {
font-size: calc(100vw / 7.5);//除以的7.5是根據設計稿的屏幕寬度來定的,這樣750px寬度下根元素字體大小則爲750px/7.5=100px=1rem
}
複製代碼
其中,100vw是設備寬度deviceWidth,這樣就實現了不一樣設備寬度下,動態修改根字體font-size的大小,好比:less
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。iphone
這樣設計的緣由是:實現適配只要在代碼裏把寬高直接將設計稿的尺寸除以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了
}
複製代碼
以上設計思路的最大優勢就是:方便計算。
正巧今天跟同事有在討論適配問題,他給我提了一個本身歷來沒有注意到的問題,就是隻是像上面這樣設計的話,會無限制放大,在大屏上很很差看,評論裏也有優秀的童鞋提到。因此我把同事分享的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;
}
複製代碼
典型應用場景:關鍵元素高寬和位置都不變,只有容器元素在作伸縮變換。 針對這種需求,記住一個大佬總結好的適配原則就好:文字流式,控件彈性,圖片等比縮放。
移動端彈出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.fixedBody();
} else {
this.looseBody();
}
}
}
複製代碼
缺點:
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.forbidScroll();
} else {
this.allowScroll();
}
}
}
複製代碼
優勢
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 {
//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倍
優勢: 全機型兼容,並且支持圓角
通常像這種項目裏處處會用到的樣式,最好把它提取成公共樣式,好比在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>
複製代碼
<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按正常佈局寫到屬於同一文檔流中的頁面最底下。