之前都是寫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按正常佈局寫到屬於同一文檔流中的頁面最底下。