本文原文發表於2016年個人github,可是直到如今爲止還有不少童鞋問我相關概念,因而整理下再分享一下。css
原文連接:github.com/sunmaobin/s…html
在瞭解如何作H5頁面適配前,你們都應該把移動端涉及的一些概念搞明白,好比:dpr
是什麼意思?vue
移動端H5解惑-概念術語(一)css3
由於在PC端,因爲瀏覽器種類太多啦,好比幾個經常使用的:IE、火狐、Chrome、Safari等。同時,因爲歷史緣由,不一樣瀏覽器在不一樣時期針對當時的WEB標準有一些不同的處理(雖然大部分同樣),好比:IE六、IE八、IE10+等對於一些JS事件處理、CSS樣式渲染有所不一樣。git
而偏偏又有一些人在使用着不一樣類型的瀏覽器,以及不一樣瀏覽器的不一樣版本。因此,爲了能讓你的網站讓這些不一樣的人看到效果一致,你就不得不作兼容,除非這些人不是你的目標用戶。github
在移動端雖然總體來講大部分瀏覽器內核都是webkit,並且大部分都支持CSS3的全部語法。可是,因爲手機屏幕尺寸不同,分辨率不同,或者你須要考慮橫豎屏的問題,這時候你也就不得不解決在不一樣手機上,不一樣狀況下的展現效果了。web
另一點,UI通常輸出的視覺稿只有一份,好比淘寶就會輸出:750px
寬度的(高度是動態的通常不考慮)(詳情),這時候開發人員就不得不針對這一份設計稿,讓其在不一樣屏幕寬度下顯示 一致
。瀏覽器
一致
是什麼意思?就是下面提到的幾個主要解決的問題。sass
舉個栗子:app
在 1080px
的視覺稿中,左上角有個logo,寬度是 180px
(高度問題同理可得)。
那麼logo在不一樣的手機屏幕上等比例顯示應該多大尺寸呢?
其實按照比例換算,咱們大體能夠獲得以下的結果:
375px
的手機上,應該顯示多大呢?結果是:375px * 180 / 1080 = 62.5px
360px
的手機上,應該顯示多大呢?結果是:360px * 180 / 1080 = 60px
320px
的手機上,應該顯示多大呢?結果是:320px * 180 / 1080 = 53.3333px
如下就是一些實現思路:
@media
@media only screen and (min-width: 375px) {
.logo {
width : 62.5px;
}
}
@media only screen and (min-width: 360px) {
.logo {
width : 60px;
}
}
@media only screen and (min-width: 320px) {
.logo {
width : 53.3333px;
}
}
複製代碼
這個方案有2個比較突出的問題:
@media
查詢塊;@media
中定義一遍不一樣的尺寸,這個代價有點高。rem
單位注意咱們的推導公式:
375px
的手機上,應該顯示多大呢?結果是:375px * 180 / 1080 = 62.5px
360px
的手機上,應該顯示多大呢?結果是:360px * 180 / 1080 = 60px
320px
的手機上,應該顯示多大呢?結果是:320px * 180 / 1080 = 53.3333px
@media only screen and (min-width: 375px) {
html {
font-size : 375px;
}
}
@media only screen and (min-width: 360px) {
html {
font-size : 360px;
}
}
@media only screen and (min-width: 320px) {
html {
font-size : 320px;
}
}
.logo{
width : 180rem / 1080;
}
複製代碼
方案2有效的解決了方案1中,同一個元素須要在多個 media 中寫的問題,這裏只須要多定義幾個 media 就能夠大體解決問題了。
可是本方案仍然有如下問題:
@media
查詢語句,多一種機型就須要多寫一套查詢語句,並且隨着如今手機的層出不窮,這個頁面頗有可能在一些新出的機型上有問題。針對除以1080咱們能不能直接放到html的font-size上,變成這樣:
@media only screen and (min-width: 375px) {
html {
font-size : 375px / 1080;
}
}
@media only screen and (min-width: 360px) {
html {
font-size : 360px / 1080;
}
}
@media only screen and (min-width: 320px) {
html {
font-size : 320px / 1080;
}
}
.logo{
width : 180rem;
}
複製代碼
若是變成這樣,那麼 .logo
的css中就只須要按設計稿的尺寸大小寫就能夠了。到底可不能夠呢?
答案是:不能夠。
主要緣由是,瀏覽器有最小字體限制:
font-size=12
font-size=8
若是小於最小字體,那麼字體默認就是最小字體。
再來看上面的css,好比:
@media only screen and (min-width: 375px) {
html {
font-size : 375px / 1080; //0.347222px
}
}
.logo{
width : 180rem; //指望結果:375px / 1080 * 180 = 62.5px
}
複製代碼
因此當你這麼設置font-size時,在手機上因爲小於最小字體8px,因此頁面會按照默認字體算。
因此,最終就至關於你是這麼設置的:
@media only screen and (min-width: 375px) {
html {
font-size : 8px;
}
}
.logo{
width : 180rem; //實際結果:1440px
}
複製代碼
因此,你們在設置html的font-size的時候必定要保證最小等於8px!
於是爲了解決這個問題,建議你們使用Sass這種Css開發語言,能夠定義公式的,這樣寫css就方便了。
最終使用Sass的代碼以下:
@media only screen and (min-width: 375px) {
html {
font-size : 375px;
}
}
@media only screen and (min-width: 360px) {
html {
font-size : 360px;
}
}
@media only screen and (min-width: 320px) {
html {
font-size : 320px;
}
}
//定義方法:calc
@function calc($val){
@return $val / 1080;
}
.logo{
width : calc(180rem);
}
複製代碼
以上方案雖然解決了問題,但任然有如下缺陷:
@media
calc()
,這個也挺麻煩的。因爲方案2最主要的問題就是須要針對不一樣的屏幕尺寸,定義多個 @media
,因此咱們先將這個字體設置改成動態設置。
注意咱們的推導公式:
375px
的手機上,應該顯示多大呢?結果是:375px * 180 / 1080 = 62.5px
360px
的手機上,應該顯示多大呢?結果是:360px * 180 / 1080 = 60px
320px
的手機上,應該顯示多大呢?結果是:320px * 180 / 1080 = 53.3333px
//獲取手機屏幕寬度
var deviceWidth = document.documentElement.clientWidth;
//將方案二中的media中的設置,在這裏動態設置
//這裏設置的就是html的font-size
document.documentElement.style.fontSize = deviceWidth + 'px';
複製代碼
須要注意:
document.documentElement.clientWidth 這個語句能獲取到的準確的手機尺寸的前提是創建在html中設置了以下標籤:
<meta name="viewport" content="width=device-width"> 複製代碼
要否則獲取到的結果將始終是:980(查看緣由)
而後Sass中就能夠按照設計稿的尺寸大小去寫就好了:
//定義方法:calc
@function calc($val){
@return $val / 1080;
}
.logo{
width : calc(180rem);
}
複製代碼
若是不考慮別的因素,只是頁面元素大致適配的話,該方案基本就知足要求了,可是現實中咱們其實還有不少問題,因此咱們的方案還須要繼續優化。
文字也採用rem的單位主要有什麼問題呢?
23.335px
這樣的奇葩的字體大小,可能還會所以出現鋸齒、模糊不清等問題;3.5 橫豎屏顯示問題
會仔細講)。對於以上問題,我我的的建議:
若是必定要解決這個問題,那麼字體就不要使用rem方案了,而是繼續使用px單位。
咱們上面提到 大屏
小屏
其實隱含的意思並非手機屏幕大,而是手機的屏幕分辨率不同,其實就是dpr不同,因此咱們針對不一樣的dpr設置具體的字體就能夠了。
好比,咱們針對頁面的標題的字體大小就能夠以下設置:
.title {
font-size: 12px;
}
[data-dpr="2"] .title {
font-size: 24px;
}
[data-dpr="3"] .title {
font-size: 36px;
}
複製代碼
先來看看 這裏 這篇文章,有講解了爲何在有些屏幕上要使用 @2x
@3x
的高清圖。
再來看看 這裏 講解了具體的高清圖的解決方案。
我這裏簡單概括下。
<img>
標籤引入的圖片高清解決方案一、使用 srcset
標籤
<img src="http://g.ald.alicdn.com/bao/uploaded/i1/TB1d6QqGpXXXXbKXXXXXXXXXXXX_!!0-item_pic.jpg_160x160q90.jpg" srcset="http://img01.taobaocdn.com/imgextra/i1/803091114/TB2XhAPaVXXXXXmXXXXXXXXXXXX_!!803091114.jpg 2x, http://gtms04.alicdn.com/tps/i4/TB1wDjWGXXXXXbtXVXX6Cwu2XXX-398-510.jpg_q75.jpg 3x">
複製代碼
關於
srcset
的說明:猛戳這裏
二、使用js自帶的 Image
異步加載圖片
<img id="img" data-src1x="xxx@1x.jpg" data-src2x="xxx@2x.jpg" data-src3x="xxx@3x.jpg"/>
複製代碼
var dpr = window.devicePixelRatio;
if(dpr > 3){
dpr = 3;
};
var imgSrc = $('#img').data('src'+dpr+'x');
var img = new Image();
img.src = imgSrc;
img.onload = function(imgObj){
$('#img').remove().prepend(imgObj);//替換img對象
};
複製代碼
一、使用 media query
來處理
/* 普通顯示屏(設備像素比例小於等於1)使用1倍的圖 */
.css{
background-image: url(img_1x.png);
}
/* 高清顯示屏(設備像素比例大於等於2)使用2倍圖 */
@media only screen and (-webkit-min-device-pixel-ratio:2){
.css{
background-image: url(img_2x.png);
}
}
/* 高清顯示屏(設備像素比例大於等於3)使用3倍圖 */
@media only screen and (-webkit-min-device-pixel-ratio:3){
.css{
background-image: url(img_3x.png);
}
}
複製代碼
二、使用 image-set
來處理(有兼容問題)
.css {
background-image: url(1x.png); /*不支持image-set的狀況下顯示*/
background: -webkit-image-set(
url(1x.png) 1x,/* 支持image-set的瀏覽器的[普通屏幕]下 */
url(2x.png) 2x,/* 支持image-set的瀏覽器的[2倍Retina屏幕] */
url(3x.png) 3x/* 支持image-set的瀏覽器的[3倍Retina屏幕] */
);
}
複製代碼
什麼是 1像素問題 ?
咱們說的1像素,就是指1 CSS像素。
好比設計師畫了一條線,可是在有些手機上看着明顯很粗,爲何?
由於這個1px,在有些設備上(好比:dpr=3),就是用了橫豎都是3的物理像素矩陣(即:3x3=9 CSS像素)來顯示這1px,致使在這些設備上,這條線看上去很是粗!
其實在在中手機上應該是1/3px顯示這條線。
關於 dpr
,不理解的,能夠看看以前的 這篇 文章。
問題描述清楚了,咱們該怎麼處理呢?
scaleY(0.5)
來解決好比:div的border-top的1px問題解決。
.div:before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: auto;
right: auto;
height: 1px;
width: 100%;
background-color: #c8c7cc;
display: block;
z-index: 15;
-webkit-transform-origin: 50% 0%;
transform-origin: 50% 0%;
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
.div:before {
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 3) {
.div:before {
-webkit-transform: scaleY(0.33);
transform: scaleY(0.33);
}
}
複製代碼
可是,這種方案只能解決直線的問題,涉及到圓角之類的,就無能爲力!
咱們先來說講頁面縮放能解決1px問題的原理示。
首先你們須要瞭解一些 viewport
的常識,參考:這裏
假如如下手機的 dpr=2
對於dpr=2的手機設備,1px就會有 2x2
的物理像素來渲染,可是當縮放之後其實就變成 1x1
個單位渲染了,看下面示意圖:
因此,咱們的思路就是將真個頁面縮小dpr倍,再將頁面的根字體放大dpr倍。這樣頁面雖然變小了,可是因爲頁面總體採用rem單位,當根字體放大dpr倍之後,總體都放大了,看上去總體樣式沒什麼變化。
一、將scale設置爲1/dpr
假如:dpr = 2
<meta name="viewport" content="width=device-width,initial-scale=0.5">
複製代碼
二、clientWidth獲取的值會自動擴大dpr倍
好比,之前是360px,當頁面縮小0.5倍,獲取到的值會變爲720px。
不知道這個原理的,這篇文章講的仍是比較清楚:這裏
var deviceWidth = document.documentElement.clientWidth;
document.documentElement.style.fontSize = deviceWidth + 'px';
複製代碼
三、css中涉及到1像素問題的地方使用 px
做爲單位
好比:
.box{
border : 1px solid #ddd;
}
複製代碼
以上步驟最終整理的結果:
html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
</head>
<body>
<div class="box">
<div class="logo">Logo</div>
</div>
</body>
</html>
複製代碼
js:
//獲取屏幕寬度、dpr值
var deviceWidth = document.documentElement.clientWidth,
dpr = window.devicePixelRatio || 1;
//設置根字體擴大dpr倍
//因爲deviceWidth當頁面縮小dpr倍時,自己獲取的值就增長dpr倍
//因此這裏不須要再乘以dpr了
document.documentElement.style.fontSize = deviceWidth + 'px';
//設置頁面縮放dpr倍
document.getElementsByName('viewport')[0]
.setAttribute('content','width=device-width;initial-scale=' + 1/dpr)
複製代碼
scss:
@function calc($val){
@return $val / 1080;
}
.logo{
width : calc(180rem);
}
.box{
border : 1px solid #ddd;
}
複製代碼
橫豎屏問題,就是當你橫屏手機、豎屏手機時看到的不同的效果問題。
我這裏要說的這個問題,就是設計師會針對橫屏或者豎屏,作不同的設計,好比:橫屏時顯示摘要,豎屏時只有標題,這種狀況下,咱們應該怎麼適配的問題。
關於橫豎屏問題,我將會分2個部分來講明:
咱們知道橫屏,至關於屏幕變寬了,這時候一行顯示的內容就能夠更多。因此,設計師可能會對橫豎屏作2種不一樣的內容設計,如:
若是設計師自己就設計了2套樣式,那麼咱們只須要準備2套css,依據橫豎屏直接顯示對應的樣式,而後html中作好兼容便可。
下文會將如何判斷橫豎屏。
這裏有個要說的就是,設計師沒有設計橫屏的樣式,那麼若是咱們按照上文提到的方案去處理,那麼就會發如今橫屏模式下字體顯得很是大(由於屏幕寬了),顯示的內容就少了。
這種狀況會顯得很不協調,雖然仍是等比例顯示的,只不過須要多拖動下滾動條而已,可是你會以爲很怪。尤爲是再有彈出框的時候,會更麻煩,彈出框有時候可能還會顯示不徹底。。。
好比,下面的樣式:
像這種問題,咱們上面提到的依據屏幕寬度自動調整根目錄font-size的大小,就有點不合適了。這樣雖然保證了橫向的比例是嚴格按照設計搞來的,可是顯示上很是醜。
因此,咱們通常的作法就是在橫屏的時候將 deviceWidth=deviceHeight
。
以上3組畫面對比咱們獲得的效果是:
因此,通過咱們的實際對比體驗之後,一致認爲橫屏時讓 width=height
體驗比較好。
附上核心代碼:
var deviceWidth = document.documentElement.clientWidth,
deviceHeight = document.documentElement.clientHeight
//橫屏狀態
if (window.orientation === 90 || window.orientation === -90) {
deviceWidth = deviceHeight;
};
//設置根字體大小
document.documentElement.style.fontSize = deviceWidth + 'px';
複製代碼
js獲取屏幕旋轉方向:window.orientation
window.addEventListener("onorientationchange" in window ? "orientationchange" : "resize", function() {
if (window.orientation === 180 || window.orientation === 0) {
console.log('豎屏狀態!');
};
if (window.orientation === 90 || window.orientation === -90 ){
console.log('橫屏狀態!');
}
}, false);
複製代碼
@media screen and (orientation: portrait) {
/*豎屏 css*/
}
@media screen and (orientation: landscape) {
/*橫屏 css*/
}
複製代碼
<!-- 豎屏 -->
<link rel="stylesheet" media="all and (orientation:portrait)" href="portrait.css">
<!-- 豎屏 -->
<link rel="stylesheet" media="all and (orientation:landscape)" href="landscape.css">
複製代碼
手機字體縮放是什麼問題呢?
就是當你在手機 設置 -> 字體設置
中將字體放大或者縮小,使得手機總體系統字體發生了變化,這時候可能就會影響到H5頁面正常的顯示。
通過實際測試,這個問題當前發生的機率不是很大,由於不少手機廠商都已經作了保護措施。可是爲了保險起見,咱們仍是有必要進行檢測,一旦不同就要採起措施。
var deviceWidth = document.documentElement.clientWidth;
//設置根字體大小
var setFz = deviceWidth + 'px';
document.documentElement.style.fontSize = setFz;
//獲取實際html字體大小
var realFz = window.getComputedStyle(document.documentElement)
.getPropertyValue('font-size') //如:360px
//比較兩者是否相同
if(setFz !== realFz){
//TODO 設置的字體和實際顯示的字體不一樣
};
複製代碼
好比:你想設置的字體大小爲100px,可是實際大小卻爲50px,那麼你能夠肯定其實用戶字體是縮放了0.5,這時候你就須要將你的字體擴大1倍,這樣才能保證明際頁面的字體是100。
因此,按照這個等比例換算之後,咱們就須要從新設置頁頁面的font-size。
var deviceWidth = document.documentElement.clientWidth;
//設置根字體大小
var setFz = deviceWidth + 'px';
document.documentElement.style.fontSize = setFz;
//獲取實際html字體大小
var realFz = window.getComputedStyle(document.documentElement)
.getPropertyValue('font-size') //如:360px
//比較兩者是否相同
if(setFz !== realFz){
//去掉單位px,下面要參與計算
setFz = parseFloat(setFz);
realFz = parseFloat(realFz);
//從新計算要設置的新的字體大小
//公式推導:100 -> 50,x -> 100,求x?
//即:setFz -> realFz, x -> setFz,求x?
var x = setFz * setFz / realFz;
//從新設置html的font-size
document.documentElement.style.fontSize = x + 'px';
};
複製代碼
這麼作理論上已經解決了問題,可是還有點瑕疵,問題就是:
如何解決這個問題?思路就是:
最終代碼片斷以下:
var deviceWidth = document.documentElement.clientWidth;
var setFz = '100px';
//給head增長一個隱藏元素
var h = document.getElementsByTagName('head')[0],
s = document.createElement('span');
s.style.fontSize = setFz;
s.style.display = 'none';
h.appendChild(s);
//判斷元素真實的字體大小是否100px
//若是不相等則獲取真實的字體換算比例
var realFz = getComputedStyle(s).getPropertyValue('font-size');
if(setFz !== 'realFz'){
//去掉單位px,下面要參與計算
setFz = parseFloat(setFz);
realFz = parseFloat(realFz);
//因爲:var x = setFz * setFz / realFz;
//公式推導:x -> setFz, y -> deviceWidth
//因此:var y = deviceWidth * x / setFz;
//重置deviceWidth
deviceWidth = deviceWidth * setFz / realFz;
};
document.documentElement.style.fontSize = deviceWidth + 'px';
複製代碼
除了上面一步步的分析中間的原理以外,咱們可能還須要考慮或者遇到如下問題:
document.documentElement.clientWidth=0
的bug?因此,在儘量的解決諸多問題後,最終的腳本以下:
html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
</head>
<body>
<!-- 正文 -->
</body>
</html>
複製代碼
js:
/** * @DESCRIPTION 移動端頁面適配解決方案 v1.0 * @AUTHOR Night * @DATE 2018年08月01日 */
(function(window, document){
var docEle = document.documentElement,
dpr = window.devicePixelRatio || 1,
scale = 1 / dpr;
var fontSizeRadio = 1, //手機字體正常比例
isLandscape = false;//是否橫屏
///////////////////////// viewport start //////////////////////////////////
//設置頁面縮放比例並禁止用戶手動縮放
document.getElementsByName('viewport')[0].setAttribute('content','width=device-width,initial-scale='+scale+',maximum-scale='+scale+',minimum-scale='+scale+',user-scalable=no');
///////////////////////// viewport end //////////////////////////////////
//橫屏狀態檢測
if (window.orientation === 90 || window.orientation === -90) {
isLandscape = true;
};
///////////////////// system font-size check start //////////////////////
//試探字體大小,用於檢測系統字體是否正常
var setFz = '100px';
//給head增長一個隱藏元素
var headEle = document.getElementsByTagName('head')[0],
spanEle = document.createElement('span');
spanEle.style.fontSize = setFz;
spanEle.style.display = 'none';
headEle.appendChild(spanEle);
//判斷元素真實的字體大小是否setFz
//若是不相等則獲取真實的字體換算比例
var realFz = getComputedStyle(headEle).getPropertyValue('font-size');
if(setFz !== 'realFz'){
//去掉單位px,下面要參與計算
setFz = parseFloat(setFz);
realFz = parseFloat(realFz);
//獲取字體換算比例
fontSizeRadio = setFz / realFz;
};
///////////////////// system font-size check end //////////////////////
var setBaseFontSize = function(){
var deviceWidth = docEle.clientWidth,
deviceHeight= docEle.clientHeight;
if(isLandscape){
deviceWidth = deviceHeight;
};
docEle.style.fontSize = deviceWidth * fontSizeRadio + 'px';
};
setBaseFontSize();
//頁面發生變化時重置font-size
//防止多個事件重複執行,增長延遲300ms操做(防抖)
var tid;
window.addEventListener('resize', function() {
clearTimeout(tid);
tid = setTimeout(setBaseFontSize, 300);
}, false);
window.addEventListener('pageshow', function(e) {
if (e.persisted) {
clearTimeout(tid);
tid = setTimeout(setBaseFontSize, 300);
};
}, false);
})(window, document);
複製代碼
scss:
//設計稿尺寸大小,假如設計稿寬度750
$baseDesignWidth = 750;
@function calc($val){
@return $val / $baseDesignWidth;
}
//適配元素採用rem,假如設計稿中元素寬度180
.logo{
width : calc(180rem);
}
//邊框採用px,假如設計稿邊框寬度1px
.box{
border : 1px solid #ddd;
}
複製代碼
若是不太考慮老的手機型號,能夠採用 viewport
單位。
因爲我本人也沒有在項目中使用這個方案,因此不過多發表言論,你們有興趣的能夠研究下。
具體方案細節參考:
講了這麼多,這裏總結下,任何事情弄懂原理最重要!
好比,當你首次看 使用Flexible實現手淘H5頁面的終端適配 這篇文章的時候你會很感慨,感受頗有收穫,可是當你實際開始項目的時候,殊不知道該怎麼下手。
俗話說,臺上一分鐘,臺下十年功。
爲了寫本文以及姊妹篇,我我的零零散散的時間加起來不下1個月,一直到(2016年12月2日
)才發表了本文的初版。因爲收到一些流言反饋和後續知識的積累,因而決定今天(2018年08月01日
)再把它從新整理一遍,以讓你們更清楚這中間的原理。
由於中間涉及的東西太多,只要有一個知識有些不清楚,可能就會卡克!好比這個概念 dips
,不一樣的文章有不一樣的說法,並且還給你解釋了它跟 dip
的不一樣,其實就是指 CSS像素
,這些人故意發明一些專業詞彙,搞的你暈頭轉向,因此,當你看了個人這兩篇文章,也許仍是隻知其一;不知其二,這很正常,慢慢來,多多練習,相信你會明白的。
若是還有哪裏不清楚,或者本文有錯誤的地方,感謝批評指正。
本文從新編輯於:2018年08月01日
- 針對你們的留言以及我的的反覆推敲,從新整理了這遍文章;
- 增長針對手機自己字體放大、縮小的解決方案,以及一些新的替代方案思路。
(全文完)