摘要:移動(dòng)端的字體最開(kāi)始的的方案是升級(jí)后的方案以前版本降級(jí)使用。在處理移動(dòng)端邊框的時(shí)候有兩種方案,其中一種方案就是將還有一種方案就是通過(guò)偽類(lèi)來(lái)處理。固定項(xiàng)欄頁(yè)面基本上市面上所看到的移動(dòng)端的頁(yè)面都是固定頭部和底部,少量的會(huì)加入側(cè)邊工具欄。
1、移動(dòng)端的字體
最開(kāi)始的的方案是:
body {
font-family: "Helvetica Neue", Helvetica, "microsoft yahei", sans-serif;
}
升級(jí)后的方案:
body {
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC","Helvetica Neue",STHeiti,"Microsoft Yahei",Tahoma,Simsun,sans-serif;
}
以前iOS版本降級(jí)使用 Helvetica。 中文字體設(shè)置為華文黑體STHeiTi, iOS 9+ 就開(kāi)始支持 -apple-system 參數(shù)了, Chrome 使用 BlinkMacSystemFont ,兼容 iOS/MacOS。 現(xiàn)在很多設(shè)計(jì)師的字體都是PingFang,所以這里做了一個(gè)兼容。 順便用"Microsoft Yahei"兼容了一下Window系統(tǒng)。 原生 Android 下中文字體與英文字體都選擇默認(rèn)的無(wú)襯線(xiàn)字體。 但是因?yàn)榘沧肯到y(tǒng)可以去改系統(tǒng)的默認(rèn)字體,而且每一個(gè)手機(jī)廠(chǎng)家也會(huì)內(nèi)置字體,所以直接讓他去選擇默認(rèn)的吧。不用多帶帶去折騰安卓的字體了。
2、移動(dòng)端的適配
移動(dòng)端的適配方案各個(gè)廠(chǎng)商都一套方案,但是現(xiàn)在主流的方案是阿里的flexible,具體可以去看下這篇文章: 《使用Flexible實(shí)現(xiàn)手淘H5頁(yè)面的終端適配》。 阿里的方案我用過(guò)一段時(shí)間,但是對(duì)于每個(gè)device pixel ratio 都要寫(xiě)一個(gè)樣式,雖然可以用sass、less的mixmin用法來(lái)處理,但是不同品尺寸屏幕下所顯示的文字個(gè)數(shù)不一致的問(wèn)題(如下圖:商品的標(biāo)題),往往會(huì)導(dǎo)致用戶(hù)認(rèn)為這是一個(gè)bug。 iPhone4的渲染圖
iPhone6的渲染圖
所以后面的開(kāi)發(fā)就拋棄了這個(gè)方案,選中在以前項(xiàng)目中運(yùn)用到的方案,思路是跟淘寶的思路大體上是一樣的,根據(jù)750px的設(shè)計(jì)稿來(lái)?yè)Q算成rem,1px == 0.01rem;
CSS單位rem
在W3C規(guī)范中是這樣描述rem的:
font size of the root element.
也就是根節(jié)點(diǎn)的字體的大小,簡(jiǎn)單的理解,rem就是相對(duì)于根元素的font-size來(lái)做計(jì)算。而我們的方案中使用rem單位,是能輕易的根據(jù)的font-size計(jì)算出元素的盒模型大小。
具體怎么換算呢? 把750px的設(shè)計(jì)稿 1px對(duì)應(yīng)0.01rem即可
思路:
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來(lái)處理。 移動(dòng)端的1px邊框。 在處理移動(dòng)端1px邊框的時(shí)候有兩種方案,其中一種方案就是將initial-scale=0.5還有一種方案就是通過(guò)偽類(lèi)來(lái)處理。
父級(jí) {
positon: relative;
}
父級(jí):after {
content: " ";
width: 200%;
height: 200%;
position: absolute;
top: -1px;//之所要寫(xiě)成-1px而不是0是因?yàn)檫@個(gè)會(huì)將整個(gè)框下移1px,所以為了避免整個(gè)問(wèn)題將元素上移1px
left: 0;
z-index: 1;
border: 1px solid #ffffd;
border-bottom: 1px solid #ffffd;
-webkit-transform: scale(0.5);
transform: scale(0.5);
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
}
優(yōu)化后的方案:
;(function(designWidth, rem2px, fixedWidth) {
//容錯(cuò)
designWidth = designWidth || 750;//傳入設(shè)計(jì)稿的寬度
rem2px = rem2px || 100;//rem to px 的關(guān)系
fixedWidth = fixedWidth || 0;//固定最大寬度
//獲取當(dāng)前html文檔
var docEl = document.documentElement;
//獲取body
var body = document.querySelector("body");
//設(shè)置固定寬度的大小
if (!Number(fixedWidth)) {
//不存在固定值,或者固定值為0
body.style.maxWidth = designWidth / rem2px + "rem";
} else {
body.style.maxWidth = fixedWidth / rem2px + "rem";
}
body.style.margin = "auto";
//這里不設(shè)置body的position,為了底部存在可以讓positon:absolute的可以吸在鍵盤(pán)上
//屏幕的寬度
var tempWidth = window.screen.width;
var tempHeight = window.screen.height;
//最小的寬度,以這個(gè)寬度來(lái)渲染,可以保證旋轉(zhuǎn)的時(shí)候字體大小不變 為什么不用文檔的寬度, 因?yàn)闉g覽器或者默認(rèn)的app有時(shí)候會(huì)占用導(dǎo)航欄,
//這個(gè)時(shí)候?qū)挾群透叨染蜁?huì)被裁剪一部分,但是這個(gè)時(shí)候屏幕的寬高是不會(huì)因?yàn)闉g覽器或者app的導(dǎo)航欄而被裁剪
var minWidth = tempWidth > tempHeight ? tempHeight : tempWidth;
//手機(jī)方向
var orientation = window.orientation;
//獲取當(dāng)前默認(rèn)字體的大小,因?yàn)榘沧靠梢栽O(shè)置默認(rèn)字體的大小來(lái)進(jìn)行計(jì)算
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();
//設(shè)置字體的大小
window.onresize = function() {
//延時(shí)是因?yàn)槠聊恍D(zhuǎn)獲取新的高度需要一定的時(shí)間去重新獲取高度和寬度
setTimeout(function() {
if (typeof(orientation) == "undefined") {
//如果不支持orientation 那么就根據(jù)屏幕的寬高來(lái)判斷
var newWidth = window.screen.width;
if (newWidth != tempWidth) {
tempWidth = newWidth
//如果屏幕的寬度值改變了
ReSetFontSize();
}
}
else {
if (orientation != window.orientation) {
//設(shè)置最新的屏幕方向 為什么要延遲,因?yàn)槲臋n去重新并且渲染高度是有一個(gè)時(shí)間段的
orientation = window.orientation;
ReSetFontSize();
}
}
}, 100);
};
function ReSetFontSize() {
//設(shè)置body的高度,body的高度不能直接設(shè)置成100%會(huì)導(dǎo)致重繪,同時(shí)為了防止fiex的bug(鍵盤(pán)彈出)
body.style.height = docEl.clientHeight + "px";
//設(shè)置字體大小
docEl.style.fontSize = minWidth / designWidth * rem2px / defaultFontSize * 100 + "%";
}
ReSetFontSize();
document.body.classList.remove("vhidden");
})(750, 100, 750);
3、移動(dòng)端的line-height
為什么這個(gè)要多帶帶講呢,因?yàn)檫@個(gè)問(wèn)題在移動(dòng)端出現(xiàn)的幾率是100%,寫(xiě)習(xí)慣了PC端頁(yè)面的開(kāi)發(fā)者剛開(kāi)始上手移動(dòng)端經(jīng)常會(huì)遇到文本垂直居中的問(wèn)題,明明在谷歌模擬器里面看到文本是垂直居中的,但是為什么在手機(jī)上看到文字向上偏移的,而且安卓的問(wèn)題比較大。transform雖然可以實(shí)現(xiàn),但是感覺(jué)寫(xiě)起來(lái)卻非常的繁瑣。 提供兩種方法, 1、padding
p{
/*高度為90px*/
font-size: .26rem;
padding: 0.22rem;
}
雖然上面的方法可以實(shí)現(xiàn),但是用起來(lái)的時(shí)候經(jīng)常每次都要去計(jì)算高度(padding值等于設(shè)計(jì)高度減去font-size之后再/2),這樣就比較麻煩,而且,在針對(duì)多行的時(shí)候還得計(jì)算,于是我又采用了下面的方式。 利用 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;
}
//同時(shí)水平居中也可以用下面的css3屬性
box-pack: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
4、移動(dòng)端的布局
移動(dòng)端的布局情況有點(diǎn)多, 切記移動(dòng)端最好不要使用postion:fixed,因?yàn)檫@個(gè)屬性會(huì)在ios下產(chǎn)生很多bug。最終我根據(jù)之前的項(xiàng)目經(jīng)驗(yàn)簡(jiǎn)單做了以下2種分類(lèi):
無(wú)固定欄頁(yè)面
什么叫無(wú)固定項(xiàng),所謂的無(wú)固定項(xiàng)頁(yè)面就是整個(gè)網(wǎng)頁(yè)從上到下沒(méi)有沒(méi)有固定在頁(yè)面上的按鈕,頭部沒(méi)有固定的按鈕,底部沒(méi)有固定的按鈕,左右兩側(cè)也沒(méi)有側(cè)邊欄。用戶(hù)唯一的操作就是滑動(dòng)頁(yè)面到底部。這一類(lèi)直接跟寫(xiě)PC一樣的寫(xiě)法就行了。
固定項(xiàng)欄頁(yè)面
基本上市面上所看到的移動(dòng)端的頁(yè)面都是固定頭部和底部,少量的會(huì)加入側(cè)邊工具欄。這里主要講固定頭部和底部的。 下面的例子主要把頁(yè)面分為頭部,內(nèi)容,底部三部分。
test
這是內(nèi)容部分1
這是內(nèi)容部分2
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
這是內(nèi)容部分
Phone手機(jī)在滑動(dòng)overflow-y: scroll的元素上滑動(dòng)的時(shí)候會(huì)頓卡,需要加入如下的css代碼就可以了
-webkit-overflow-scrolling:touch;
上面的demo在中間部門(mén)有輸入框并且在底部的時(shí)候去點(diǎn)擊輸入框,彈出的鍵盤(pán)會(huì)把輸入框蓋住,只有在輸入部分內(nèi)容之后輸入框才會(huì)出現(xiàn)在視窗中。遇到這種情況需要加入如下代碼。
var element = document.getElementById("box");
element.scrollIntoView();
//element.scrollIntoView(false);
//scrollIntoView 可選參數(shù)是 true false,默認(rèn)是true
//true 元素的頂端將和其所在滾動(dòng)區(qū)的可視區(qū)域的頂端對(duì)齊。
//false 元素的底端將和其所在滾動(dòng)區(qū)的可視區(qū)域的底端對(duì)齊。
5、移動(dòng)端的bfc
這個(gè)bfc不是格式化上下文,而是back forward cache(往返緩存),這個(gè)特性最早出現(xiàn)在Firefox和Opera瀏覽器上,可以在用戶(hù)使用瀏覽器的“后退”和“前進(jìn)”按鈕時(shí)加快頁(yè)面的轉(zhuǎn)換速度。 原理就是瀏覽器會(huì)把之前訪(fǎng)問(wèn)的頁(yè)面緩存到瀏覽器內(nèi)存里面,當(dāng)用在進(jìn)行“后退”和“前進(jìn)”操作的時(shí)候,瀏覽器會(huì)直接從緩存里面把頁(yè)面展現(xiàn)到前臺(tái),從而不去刷新頁(yè)面。 但是這樣會(huì)導(dǎo)致用戶(hù)在子頁(yè)面與上一個(gè)頁(yè)面之前存在管理的時(shí)候,操作后返回上個(gè)頁(yè)面的狀態(tài)并沒(méi)有更改。 這個(gè)時(shí)候我們就需要去檢測(cè)頁(yè)面是不是從緩存里面讀取出來(lái)的頁(yè)面。
$(window).on("pageshow", function(event) {
if (event.persisted) {
location.reload(true);
}
});
或者
window.addEventListener("pageshow", funtion(event){
if (event.persisted) {
location.reload(true);
}
});
6、移動(dòng)端與客戶(hù)端的交互
現(xiàn)在內(nèi)嵌H5開(kāi)發(fā)的頁(yè)面越來(lái)越多,前端跟客戶(hù)端的交互也就越來(lái)越多,現(xiàn)在運(yùn)用得最多的方式是用JSBridge 來(lái)通信,其主要原理就是就是通過(guò)某種方式觸發(fā)一個(gè)url(iframe) 的改變,原生捕獲到url,進(jìn)行分析,得到自己想要的數(shù)據(jù)信息。 之所以要考慮用JSBridge 來(lái)通信是考慮到Android4.2 以下,addJavascriptInterface 方式有安全漏掉 iOS7以下,無(wú)法用到ios提供給前端最新的api messageHandlers 因?yàn)楝F(xiàn)有的手機(jī)基本上都是4.2以上以及iOS7以上,所以我們可以放心大膽使用上面兩個(gè)屬性。
var ua = navigator.userAgent.toLowerCase();
window.isAndroid = /android/.test(ua);
window.HtmlWebviewCallNative = function(par) {
if (/客戶(hù)端ua標(biāo)識(shí)/.test(ua)) {
//判斷是否在客戶(hù)端打開(kāi)的頁(yè)面
if (isAndroid) {
//Android這個(gè)是安卓向?yàn)g覽器注入的對(duì)象,這個(gè)看安卓客戶(hù)端給的是什么
Android.HTMLCallNative(JSON.stringify(par));
} else {
window.webkit.messageHandlers.HTMLCallNative.postMessage(par);
}
} else {
console.log(JSON.stringify(par))
}
};
//調(diào)用方法eg
HTMLCallNative({
functionName: "callPhone",
params: ["13883785984", "18323270482"],
callback: "callbackFunctionName"
});
原理以及參數(shù)說(shuō)明 1.通過(guò)向window 注冊(cè)一個(gè)名字為HTMLCallNative 的對(duì)象,以后每次向這個(gè)函數(shù)傳遞要通信的函數(shù)名和函數(shù)所需的參數(shù)即可;安卓是通過(guò)addJavascriptInterface 直接注入頁(yè)面,ios是通過(guò)WKWebView 的新特性MessageHandler 來(lái)這個(gè)對(duì)象來(lái)實(shí)現(xiàn)JS調(diào)用原生方法。 2.約定HTMLCallNative 這個(gè)方法名為app中默認(rèn)用來(lái)接受新交互規(guī)則的入口函數(shù),安卓和ios分別拿到HTMLCallNative 傳過(guò)來(lái)的function 名字和參數(shù)。 3.客戶(hù)端通過(guò)反射機(jī)制,查找字符串函數(shù)名對(duì)應(yīng)的函數(shù)并執(zhí)行函數(shù),此時(shí)通信成功。 4.functionName : 必為字符串,駝峰式命名,這個(gè)字符串為真正調(diào)用的方法,需要前端跟客戶(hù)端共同來(lái)定義。 5.params :方法需要的參數(shù),無(wú)需對(duì)參數(shù)進(jìn)行encodeURI 和encodeURIComponent , 支持字符串,array ,object 。 6.callback : 有回調(diào)函數(shù)時(shí),傳入這個(gè)參數(shù),只需要傳入函數(shù)名稱(chēng)即可,若回調(diào)函數(shù)需要傳入?yún)?shù),app端在調(diào)用的時(shí)候傳入即可,跟當(dāng)時(shí)業(yè)務(wù)相關(guān),這里就不約定格式了。
相比JSBridge 的優(yōu)點(diǎn): 1.在JS中寫(xiě)起來(lái)簡(jiǎn)單,不用再用創(chuàng)建iframe 然后觸發(fā)URL的方式那么麻煩了。 2.JS傳遞參數(shù)更方便。使用攔截URL的方式傳遞參數(shù),只能把參數(shù)拼接在后面,如果遇到要傳遞的參數(shù)中有特殊字符,如&、=、?等,必須得轉(zhuǎn)換,否則參數(shù)解析肯定會(huì)出錯(cuò)。 例如傳遞的url是這樣的: http://www.baidu.com/share/op... 使用攔截URL 的JS調(diào)用方式
loadURL("firstClick://shareClick?title=分享的標(biāo)題&content=分享的內(nèi)容&url=鏈接地址&imagePath=圖片地址"); }
將上面的url 放入鏈接地址這里后,根本無(wú)法區(qū)分share_uuid 是其他參數(shù),還是url里附帶的參數(shù)。 但是使用MessageHandler 就可以避免特殊字符引起的問(wèn)題。
7、移動(dòng)端喚起手機(jī)app
首先,我們看下安卓的配置文件和Scheme
重點(diǎn)關(guān) ,前端就需要根據(jù)這個(gè)字來(lái)喚起app
打開(kāi)app
schema拼接協(xié)議的格式:[scheme]://[host]/[path]?[query] 當(dāng)然ios的也有自己的協(xié)議,通常在寫(xiě)喚起app之前需要跟客戶(hù)端的同事進(jìn)行對(duì)接一下,拿到他們的協(xié)議。
注意schema協(xié)議要小寫(xiě),否則會(huì)有不能響應(yīng)的異常!
當(dāng)然我們可以整合一下代碼,把ios的也加進(jìn)來(lái):
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 = "安卓提供協(xié)議url";
/***打開(kāi)app的協(xié)議,有安卓同事提供***/
window.setTimeout(function() {
window.location.href = "下載的地址";
}, 2000);
} else if (isiOS2) {
window.location.href = "IOS提供協(xié)議url";
/***打開(kāi)app的協(xié)議,有ios同事提供***/
window.setTimeout(function() {
window.location.href = "下載的地址";
}, 2000);
} else {
window.location.href = "下載的地址";
}
簡(jiǎn)單的喚起方法沒(méi)有解決在其他應(yīng)用喚起的bug,可以通過(guò)下面的喚起 [https://github.com/sunhaikuo/js-arouse-app][4]