移動端總結

一、移動端的字體

最開始的的方案是:css

body {
  font-family: 'Helvetica Neue', Helvetica, 'microsoft yahei', sans-serif;
 }

升級後的方案:html

body {
  font-family: -apple-system, BlinkMacSystemFont, "PingFang SC","Helvetica Neue",STHeiti,"Microsoft Yahei",Tahoma,Simsun,sans-serif;
 }

之前iOS版本降級使用 Helvetica。 中文字體設置爲華文黑體STHeiTi, iOS 9+ 就開始支持 -apple-system 參數了, Chrome 使用 BlinkMacSystemFont ,兼容 iOS/MacOS。
如今不少設計師的字體都是PingFang,因此這裏作了一個兼容。 順便用"Microsoft Yahei"兼容了一下Window系統。
原生 Android 下中文字體與英文字體都選擇默認的無襯線字體。 可是由於安卓系統能夠去改系統的默認字體,並且每個手機廠家也會內置字體,因此直接讓他去選擇默認的吧。不用單獨去折騰安卓的字體了。前端

二、移動端的適配

移動端的適配方案各個廠商都一套方案,可是如今主流的方案是阿里的flexible,具體能夠去看下這篇文章: 《使用Flexible實現手淘H5頁面的終端適配》
阿里的方案我用過一段時間,可是對於每一個device pixel ratio 都要寫一個樣式,雖然能夠用sass、less的mixmin用法來處理,可是不一樣品尺寸屏幕下所顯示的文字個數不一致的問題(以下圖:商品的標題),每每會致使用戶認爲這是一個bug。
iPhone4的渲染圖html5

rem-9.jpg

iPhone6的渲染圖
rem-10.pngandroid

因此後面的開發就拋棄了這個方案,選中在之前項目中運用到的方案,思路是跟淘寶的思路大致上是同樣的,根據750px的設計稿來換算成rem,1px == 0.01rem;ios

CSS單位rem
在W3C規範中是這樣描述rem的:
font size of the root element.

也就是根節點的字體的大小,簡單的理解,rem就是相對於根元素<html>的font-size來作計算。而咱們的方案中使用rem單位,是能輕易的根據<html>的font-size計算出元素的盒模型大小。css3

具體怎麼換算呢?
把750px的設計稿 1px對應0.01rem便可git

思路:github

var html=document.querySelector("html");
html.style.fontSize=html.offsetWidth/7.5+"px"
window.onresize=function(){
   var a=document.querySelector("html");a.style.fontSize=a.offsetWidth/7.5+"px";
};

注意:並非全部的地方都用rem來處理。
移動端的1px邊框。
在處理移動端1px邊框的時候有兩種方案,其中一種方案就是將initial-scale=0.5還有一種方案就是經過僞類來處理。web

父級 {
    positon: relative;
}
父級:after {
    content: " ";
    width: 200%;
    height: 200%;
    position: absolute;
    top: -1px;//之所要寫成-1px而不是0是由於這個會將整個框下移1px,因此爲了不整個問題將元素上移1px
    left: 0;
    z-index: 1;
    border: 1px solid #ddd;
    border-bottom: 1px solid #ddd;
    -webkit-transform: scale(0.5);
    transform: scale(0.5);
    -webkit-transform-origin: 0 0;
    transform-origin: 0 0;
}

優化後的方案:

;(function(designWidth, rem2px, fixedWidth) {
    //容錯
   designWidth = designWidth || 750;//傳入設計稿的寬度
   rem2px = rem2px || 100;//rem to px 的關係
   fixedWidth = fixedWidth || 0;//固定最大寬度
   //獲取當前html文檔
   var docEl = document.documentElement;
   //獲取body
   var body = document.querySelector("body");
   //設置固定寬度的大小
   if (!Number(fixedWidth)) {
       //不存在固定值,或者固定值爲0
      body.style.maxWidth = designWidth / rem2px + 'rem';
   } else {
     body.style.maxWidth = fixedWidth / rem2px + 'rem';
   }
   body.style.margin = 'auto';
   //這裏不設置body的position,爲了底部存在可讓positon:absolute的能夠吸在鍵盤上
   //屏幕的寬度
   var tempWidth = window.screen.width;
   var tempHeight = window.screen.height;
   //最小的寬度,以這個寬度來渲染,能夠保證旋轉的時候字體大小不變 爲何不用文檔的寬度, 由於瀏覽器或者默認的app有時候會佔用導航欄,
   //這個時候寬度和高度就會被裁剪一部分,可是這個時候屏幕的寬高是不會由於瀏覽器或者app的導航欄而被裁剪
   var minWidth = tempWidth > tempHeight ? tempHeight : tempWidth;
   //手機方向
   var orientation = window.orientation;
   //獲取當前默認字體的大小,由於安卓能夠設置默認字體的大小來進行計算
   var tempDom = window.document.createElement('div');
   tempDom.style.width = '1rem';
   tempDom.style.display = "none";
   var head = window.document.getElementsByTagName('head')[0];
   head.appendChild(tempDom);
   var defaultFontSize = parseFloat(window.getComputedStyle(tempDom, null).getPropertyValue('width'));
   tempDom.remove();
   //設置字體的大小
   window.onresize = function() {
       //延時是由於屏幕旋轉獲取新的高度須要必定的時間去從新獲取高度和寬度
       setTimeout(function() {
            if (typeof(orientation) == "undefined") {
                //若是不支持orientation 那麼就根據屏幕的寬高來判斷
                var newWidth = window.screen.width;
                if (newWidth != tempWidth) {
                       tempWidth = newWidth
                       //若是屏幕的寬度值改變了
                      ReSetFontSize();
                }
           }
          else {
                 if (orientation != window.orientation) {
                      //設置最新的屏幕方向 爲何要延遲,由於文檔去從新而且渲染高度是有一個時間段的
                      orientation = window.orientation;
                        ReSetFontSize();
           }
      }
    }, 100);
};
function ReSetFontSize() {
            //設置body的高度,body的高度不能直接設置成100%會致使重繪,同時爲了防止fiex的bug(鍵盤彈出)
            body.style.height = docEl.clientHeight + "px";
            //設置字體大小
            docEl.style.fontSize = minWidth / designWidth * rem2px / defaultFontSize * 100 + "%";
}
ReSetFontSize();
document.body.classList.remove('vhidden');
})(750, 100, 750);

三、移動端的line-height

爲何這個要單獨講呢,由於這個問題在移動端出現的概率是100%,寫習慣了PC端頁面的開發者剛開始上手移動端常常會遇到文本垂直居中的問題,明明在谷歌模擬器裏面看到文本是垂直居中的,可是爲何在手機上看到文字向上偏移的,並且安卓的問題比較大。transform雖然能夠實現,可是感受寫起來卻很是的繁瑣。
提供兩種方法,
一、padding

p{
    /*高度爲90px*/
    font-size: .26rem;
    padding: 0.22rem;
}

雖然上面的方法能夠實現,可是用起來的時候常常每次都要去計算高度(padding值等於設計高度減去font-size以後再/2),這樣就比較麻煩,並且,在針對多行的時候還得計算,因而我又採用了下面的方式。
利用 css3 flex佈局的特性。

p{  
    font-size: .26rem;
    height: 0.9rem;
    display: flex;
    display: -webkit-flex;
    -webkit-align-items:center;
    align-items:center;
    box-pack-align: center;
    -webkit-box-pack-align: center;
}

//同時水平居中也能夠用下面的css3屬性

box-pack: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;

四、移動端的佈局

移動端的佈局狀況有點多, 切記移動端最好不要使用postion:fixed,由於這個屬性會在ios下產生不少bug。最終我根據以前的項目經驗簡單作了如下2種分類:

  1. 無固定欄頁面

什麼叫無固定項,所謂的無固定項頁面就是整個網頁從上到下沒有沒有固定在頁面上的按鈕,頭部沒有固定的按鈕,底部沒有固定的按鈕,左右兩側也沒有側邊欄。用戶惟一的操做就是滑動頁面到底部。這一類直接跟寫PC同樣的寫法就好了。

  1. 固定項欄頁面

基本上市面上所看到的移動端的頁面都是固定頭部和底部,少許的會加入側邊工具欄。這裏主要講固定頭部和底部的。
下面的例子主要把頁面分爲頭部,內容,底部三部分。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>test</title>
    <meta name="keywords" content="test">
    <meta name="description" content="test">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
</head>
<style>
* {
    margin: 0;
    padding: 0;
    line-height: 1;
    border: 0;
    tap-highlight-color: transparent;
    -webkit-tap-highlight-color: transparent;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
}

body {
    font-size: 0;
    overflow: hidden;
    background-color: #f2f2f2;
}

.vhidden {
    visibility: hidden;
}

.flex-box {
    display: flex;
    display: -webkit-flex;
}

.vertical-center {
    box-pack: center;
    -webkit-box-pack: center;
    -webkit-justify-content: center;
    justify-content: center;
}

.horizontal-center {
    -webkit-box-align: center;
    align-items: center;
}

input {
    display: block;
    height: 0.88rem;
    font-size: 0.26rem;
    border: none;
    width: 100%;
    text-align: center;
}

input:focus {
    border: none;
    outline: none;
}

header {
    height: 1rem;
    position: relative;
    z-index: 1;
    background-color: #fff;
}

header div {
    box-flex: 1;
    -webkit-box-flex: 1;
    font-size: 0.26rem;
    width: 100%;
}

main {
    -webkit-overflow-scrolling: touch;
    height: calc(100% - 2rem);
    overflow-y: scroll;
    overflow-x: hidden;
    position: relative;
    z-index: 1;
}

main::-webkit-scrollbar {
    display: none;
}

main p {
    padding: 0.2rem;
    font-size: 0.26rem;
    color: #333;
}

footer {
    height: 1rem;
    position: relative;
    z-index: 1;
    background-color: #fff;
}

footer div {
    height: 0.88rem;
    font-size: 0.26rem;
}

footer.bottom-input {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 2;
}

.test1 {
    font-size: .26rem;
    padding: 0.22rem;
    text-align: center;
    background-color: #fff;
    margin: 0.1rem auto;
}

.test2 {
    font-size: .26rem;
    height: 0.9rem;
    display: flex;
    display: -webkit-flex;
    -webkit-box-align: center;
    align-items: center;
    box-pack: center;
    -webkit-box-pack: center;
    -webkit-justify-content: center;
    justify-content: center;
    background-color: #fff;
    margin: 0.1rem auto;
}
</style>

<body class="vhidden">
    <header class="flex-box">
        <div class="flex-box vertical-center horizontal-center">導航欄一</div>
        <div class="flex-box vertical-center horizontal-center">導航欄二</div>
    </header>
    <main>
        <div class="test1">
            這是內容部分1
        </div>
        <div class="test2">
            這是內容部分2
        </div>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <input class="flex-box vertical-center horizontal-center" type="text" name="" id="Input" placeholder="輸入點什麼">
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
        <p>這是內容部分</p>
    </main>
    <!-- <footer class="flex-box vertical-center horizontal-center">
        <div class="flex-box vertical-center horizontal-center" id="tt">底部按鈕</div>
    </footer> -->
    <!-- <footer class="flex-box vertical-center horizontal-center bottom-input">
        <input class="flex-box vertical-center horizontal-center" type="text" name="" id="Input" placeholder="輸入點什麼">
    </footer> -->
</body>
<script>
(function(designWidth, rem2px, fixedWidth) {
    //容錯
    designWidth = designWidth || 750;//傳入設計稿的寬度
    rem2px = rem2px || 100;//rem to px 的關係
    fixedWidth = fixedWidth || 0;//是否固定最大寬度,固定寬度

    //若是沒有肯定就默認設計稿的寬度
    //獲取當前html文檔
    var docEl = document.documentElement;
    //獲取body
    var body = document.querySelector("body");
    //設置固定寬度的大小
    if (!Number(fixedWidth)) {
        //不存在固定值,或者固定值爲0
        body.style.maxWidth = designWidth / rem2px + 'rem';
    } else {
        body.style.maxWidth = fixedWidth / rem2px + 'rem';
    }
    body.style.margin = 'auto';
    //這裏不設置body的position,爲了底部存在可讓positon:absolute的能夠吸在鍵盤上
    //屏幕的寬度
    var tempWidth = window.screen.width;
    var tempHeight = window.screen.height;
    //最小的寬度,以這個寬度來渲染,能夠保證旋轉的時候字體大小不變 爲何不用文檔的寬度, 由於瀏覽器或者默認的app有時候會佔用導航欄,
    //這個時候寬度和高度就會被裁剪一部分,可是這個時候屏幕的寬高是不會由於瀏覽器或者app的導航欄而被裁剪
    var minWidth = tempWidth > tempHeight ? tempHeight : tempWidth;
    //手機方向
    var orientation = window.orientation;
    //獲取當前默認字體的大小,由於安卓能夠設置默認字體的大小來進行計算
    var tempDom = window.document.createElement('div');
    tempDom.style.width = '1rem';
    tempDom.style.display = "none";
    var head = window.document.getElementsByTagName('head')[0];
    head.appendChild(tempDom);
    var defaultFontSize = parseFloat(window.getComputedStyle(tempDom, null).getPropertyValue('width'));
    tempDom.remove();
    //設置字體的大小
    window.onresize = function() {
        //延時是由於屏幕旋轉獲取新的高度須要必定的時間去從新獲取高度和寬度
        setTimeout(function() {
            if (typeof(orientation) == "undefined") {
                //若是不支持orientation 那麼就根據屏幕的寬高來判斷
                var newWidth = window.screen.width;
                if (newWidth != tempWidth) {
                    tempWidth = newWidth
                    //若是屏幕的寬度值改變了
                    ReSetFontSize();
                }
            } else {
                if (orientation != window.orientation) {
                    //設置最新的屏幕方向 爲何要延遲,由於文檔去從新而且渲染高度是有一個時間段的
                    orientation = window.orientation;
                    ReSetFontSize();
                }
            }
        }, 100);
    };
    function ReSetFontSize() {
        //設置body的高度,body的高度不能直接設置成100%會致使重繪,同時爲了防止fiex的bug(鍵盤彈出)
        body.style.height = docEl.clientHeight + "px";
        //設置字體大小
        docEl.style.fontSize = minWidth / designWidth * rem2px / defaultFontSize * 100 + "%";
    }
    ReSetFontSize();
    document.body.classList.remove('vhidden');
})(750, 100, 750);
</script>

</html>

Phone手機在滑動overflow-y: scroll的元素上滑動的時候會頓卡,須要加入以下的css代碼就能夠了

-webkit-overflow-scrolling:touch;

上面的demo在中間部門有輸入框而且在底部的時候去點擊輸入框,彈出的鍵盤會把輸入框蓋住,只有在輸入部份內容以後輸入框纔會出如今視窗中。遇到這種狀況須要加入以下代碼。

var element = document.getElementById("box");
element.scrollIntoView();
//element.scrollIntoView(false);
//scrollIntoView 可選參數是 true false,默認是true
//true 元素的頂端將和其所在滾動區的可視區域的頂端對齊。
//false 元素的底端將和其所在滾動區的可視區域的底端對齊。

五、移動端的bfc

這個bfc不是格式化上下文,而是back forward cache(往返緩存),這個特性最先出如今Firefox和Opera瀏覽器上,能夠在用戶使用瀏覽器的「後退」和「前進」按鈕時加快頁面的轉換速度。
原理就是瀏覽器會把以前訪問的頁面緩存到瀏覽器內存裏面,當用在進行「後退」和「前進」操做的時候,瀏覽器會直接從緩存裏面把頁面展示到前臺,從而不去刷新頁面。
可是這樣會致使用戶在子頁面與上一個頁面以前存在管理的時候,操做後返回上個頁面的狀態並無更改。
這個時候咱們就須要去檢測頁面是否是從緩存裏面讀取出來的頁面。

$(window).on('pageshow', function(event) {
    if (event.persisted) {
        location.reload(true);
    }
});

或者

window.addEventListener("pageshow", funtion(event){
    if (event.persisted) {
        location.reload(true);
    }
});

六、移動端與客戶端的交互

如今內嵌H5開發的頁面愈來愈多,前端跟客戶端的交互也就愈來愈多,如今運用得最多的方式是用JSBridge來通訊,其主要原理就是就是經過某種方式觸發一個url(iframe)的改變,原生捕獲到url,進行分析,獲得本身想要的數據信息。
之因此要考慮用JSBridge來通訊是考慮到
Android4.2如下,addJavascriptInterface方式有安全漏掉
iOS7如下,沒法用到ios提供給前端最新的api messageHandlers
由於現有的手機基本上都是4.2以上以及iOS7以上,因此咱們能夠放心大膽使用上面兩個屬性。

var ua = navigator.userAgent.toLowerCase();
window.isAndroid = /android/.test(ua);
window.HtmlWebviewCallNative = function(par) {
    if (/客戶端ua標識/.test(ua)) {
 //判斷是否在客戶端打開的頁面
        if (isAndroid) {
            //Android這個是安卓向瀏覽器注入的對象,這個看安卓客戶端給的是什麼
            Android.HTMLCallNative(JSON.stringify(par));
        } else {
            window.webkit.messageHandlers.HTMLCallNative.postMessage(par);
        }
    } else {
        console.log(JSON.stringify(par))
    }
};

//調用方法eg

HTMLCallNative({
   functionName: 'callPhone',
   params: ['13883785984', '18323270482'],
   callback: 'callbackFunctionName'
});

原理以及參數說明
1.經過向window註冊一個名字爲HTMLCallNative的對象,之後每次向這個函數傳遞要通訊的函數名和函數所需的參數便可;安卓是經過addJavascriptInterface直接注入頁面,ios是經過WKWebView的新特性MessageHandler來這個對象來實現JS調用原生方法。
2.約定HTMLCallNative這個方法名爲app中默認用來接受新交互規則的入口函數,安卓和ios分別拿到HTMLCallNative傳過來的function名字和參數。
3.客戶端經過反射機制,查找字符串函數名對應的函數並執行函數,此時通訊成功。
4.functionName: 必爲字符串,駝峯式命名,這個字符串爲真正調用的方法,須要前端跟客戶端共同來定義。
5.params:方法須要的參數,無需對參數進行encodeURIencodeURIComponent, 支持字符串,arrayobject
6.callback: 有回調函數時,傳入這個參數,只須要傳入函數名稱便可,若回調函數須要傳入參數,app端在調用的時候傳入便可,跟當時業務相關,這裏就不約定格式了。

相比JSBridge的優勢:
1.在JS中寫起來簡單,不用再用建立iframe而後觸發URL的方式那麼麻煩了。
2.JS傳遞參數更方便。使用攔截URL的方式傳遞參數,只能把參數拼接在後面,若是遇到要傳遞的參數中有特殊字符,如&、=、?等,必須得轉換,不然參數解析確定會出錯。
例如傳遞的url是這樣的:
http://www.baidu.com/share/op...
使用攔截URL 的JS調用方式

loadURL("firstClick://shareClick?title=分享的標題&content=分享的內容&url=連接地址&imagePath=圖片地址"); }

將上面的url 放入連接地址這裏後,根本沒法區分share_uuid是其餘參數,仍是url裏附帶的參數。
可是使用MessageHandler 就能夠避免特殊字符引發的問題。

七、移動端喚起手機app

首先,咱們看下安卓的配置文件和Scheme

<activity android:name = ".MainActivity">
    <intent-filter>
        <action android:name = "android.intent.action.MAIN" />
        <category android:name = "android.intent.category.LAUNCHER" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        <data android:host="xxx.com" android:scheme="abraham"/>
    </intent-filter>
</activity>

重點關<data android:host="xxx.com" android:scheme="abraham"/>,前端就須要根據這個字來喚起app

<a href="abraham:/xxx.com/?pid=1">打開app</a>

schema拼接協議的格式:[scheme]://[host]/[path]?[query]
固然ios的也有本身的協議,一般在寫喚起app以前須要跟客戶端的同事進行對接一下,拿到他們的協議。

注意schema協議要小寫,不然會有不能響應的異常!

固然咱們能夠整合一下代碼,把ios的也加進來:

var u = navigator.userAgent;var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1; //android終端或者uc瀏覽器var isiOS2 = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios終端if (isAndroid) {
    window.location.href = "安卓提供協議url";
    /***打開app的協議,有安卓同事提供***/
    window.setTimeout(function() {
        window.location.href = '下載的地址';
    }, 2000);
} else if (isiOS2) {
    window.location.href = "IOS提供協議url";
    /***打開app的協議,有ios同事提供***/
    window.setTimeout(function() {
        window.location.href = '下載的地址';
    }, 2000);
} else {
    window.location.href = '下載的地址';
}

簡單的喚起方法沒有解決在其餘應用喚起的bug,能夠經過下面的喚起 [https://github.com/sunhaikuo/js-arouse-app][4]

相關文章
相關標籤/搜索