最近公司作了不少花裏胡哨的H5活動,其實H5頁面並不難每一個前端均可以寫,但細說下來有不少前端細節作的並非那麼完美,其實把H5頁面作完善,適配完美也是件挺難的事(至少我以爲是這樣),下面咱們就來總結下關於H5適配的那些事
css
爲了更好理解此篇文章,你能夠先閱讀爲何咱們常說1px問題而不說2px 對設備獨立像素
,css像素
,邏輯像素
,設備像素比
概念有基本的瞭解html
此文是適配系列文章的上前端
研究以前咱們能夠看看大廠都是如何適配H5android
地址: main.m.taobao.com/ios
方案: Flexible
markdown
分析:值得聊的是,雖然Flexible
是淘寶團隊出的關於移動端適配的方案,但手機淘寶彷佛並無使用此方案,能夠看下面幾張圖得出結論app
咱們如今改變手機型號iphone
能夠發現人家的適配單位直接是px
根本沒有使用rem
,只不過px
的值是經過手機屏幕的不一樣動態計算出來的,因此當咱們改變蘋果的大小時,網站就會刷新
動態計算出對應的px
值,從而達到適配的目的函數
隨便進去一個淘寶的內頁,發現使用的適配方案是vw
工具
地址:m.jd.com/ 方案:rem
分析:京東的適配比較粗暴,直接使用 媒體查詢改變html的根font-size 而後使用rem
進行適配
地址:job.bytedance.com/campus/m/po…
方案:responsive.js
分析: 我以爲responsive.js
和淘寶的Flexible.js
本質上是一個東西,都是動態的改變html
的font-size
而後用rem
進行適配
經過這些大廠的產品,咱們能夠總結到,移動端適配的三種方案
那麼?這邊文章就這麼完了?😶其實這纔剛剛開始咱們今天的乾貨
Flexible
Flexible
做爲移動端適配的鼻祖,很是具備研究價值,而且如今不少的移動端H5都在用這個方案進行適配,今天咱們就來學習下他的原理
0.3.2版本
這個版本Flexible
適配原理是經過meta
標籤改變頁面的縮放比例,從而達到適配的目的,同時,這個方案也能夠解決1px
的問題,源碼以下
(function(win, lib) {
var doc = win.document;
var docEl = doc.documentElement;
var metaEl = doc.querySelector('meta[name="viewport"]');
var flexibleEl = doc.querySelector('meta[name="flexible"]');
var dpr = 0;
var scale = 0;
var tid;
var flexible = lib.flexible || (lib.flexible = {});
// 若是已經設置<meta name="viewport">屬性,就根據當前設置的屬性
if (metaEl) {
var match = metaEl
.getAttribute("content")
.match(/initial\-scale=([\d\.]+)/);
if (match) {
scale = parseFloat(match[1]);
dpr = parseInt(1 / scale);
}
} else if (flexibleEl) {
// 同上
var content = flexibleEl.getAttribute("content");
if (content) {
var initialDpr = content.match(/initial\-dpr=([\d\.]+)/);
var maximumDpr = content.match(/maximum\-dpr=([\d\.]+)/);
if (initialDpr) {
dpr = parseFloat(initialDpr[1]);
scale = parseFloat((1 / dpr).toFixed(2));
}
if (maximumDpr) {
dpr = parseFloat(maximumDpr[1]);
scale = parseFloat((1 / dpr).toFixed(2));
}
}
}
if (!dpr && !scale) {
// 這裏就是 flexible 的核心代碼
var isAndroid = win.navigator.appVersion.match(/android/gi);
var isIPhone = win.navigator.appVersion.match(/iphone/gi);
var devicePixelRatio = win.devicePixelRatio;
if (isIPhone) {
// iOS下,對於2和3的屏,用2倍的方案,其他的用1倍方案
if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
dpr = 3;
} else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)) {
dpr = 2;
} else {
dpr = 1;
}
} else {
// 其餘設備下,仍舊使用1倍的方案
dpr = 1;
}
// 將 <meta> 根據當前設備的 dpr 標籤進行縮放
scale = 1 / dpr;
}
docEl.setAttribute("data-dpr", dpr);
// 根據當前的 dpr 自動設置 <meta> 屬性
if (!metaEl) {
metaEl = doc.createElement("meta");
metaEl.setAttribute("name", "viewport");
metaEl.setAttribute(
"content",
"initial-scale=" +
scale +
", maximum-scale=" +
scale +
", minimum-scale=" +
scale +
", user-scalable=no"
);
if (docEl.firstElementChild) {
docEl.firstElementChild.appendChild(metaEl);
} else {
var wrap = doc.createElement("div");
wrap.appendChild(metaEl);
doc.write(wrap.innerHTML);
}
}
function refreshRem() {
// 對ipad等設備的兼容
var width = docEl.getBoundingClientRect().width;
if (width / dpr > 540) {
width = 540 * dpr;
}
// 將屏幕10等分,設置 fontSize
var rem = width / 10;
docEl.style.fontSize = rem + "px";
flexible.rem = win.rem = rem;
}
win.addEventListener(
"resize",
function() {
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
},
false
);
win.addEventListener(
"pageshow",
function(e) {
if (e.persisted) {
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
}
},
false
);
// 設置字體 12 * dpr
if (doc.readyState === "complete") {
doc.body.style.fontSize = 12 * dpr + "px";
} else {
doc.addEventListener(
"DOMContentLoaded",
function(e) {
doc.body.style.fontSize = 12 * dpr + "px";
},
false
);
}
refreshRem();
flexible.dpr = win.dpr = dpr;
flexible.refreshRem = refreshRem;
// 工具函數
flexible.rem2px = function(d) {
var val = parseFloat(d) * this.rem;
if (typeof d === "string" && d.match(/rem$/)) {
val += "px";
}
return val;
};
flexible.px2rem = function(d) {
var val = parseFloat(d) / this.rem;
if (typeof d === "string" && d.match(/px$/)) {
val += "rem";
}
return val;
};
})(window, window["lib"] || (window["lib"] = {}));
複製代碼
適配效果以下
咱們從源碼中很容易看出data-dpr
,html的 font-size
,body的font-szie
及meta的縮放比例
是如何計算出來的
下面咱們簡單看下對dpr
的計算
if (isIPhone) {
// iOS下,對於2和3的屏,用2倍的方案,其他的用1倍方案
if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
dpr = 3;
} else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)) {
dpr = 2;
} else {
dpr = 1;
}
} else {
// 其餘設備下,仍舊使用1倍的方案
dpr = 1;
}
複製代碼
能夠看出,在這個版本中,只對ios
的dpr進行了處理,對於安卓
機型都是默認dpr = 1
,顯然這樣的處理有點不太合理
關於
<meta>
標籤這一塊,咱們能夠這樣理解,你經過一個鏡框(手機屏幕375px寬度)看一篇報紙(頁面內容 750px 的寬度) ,此時鏡框是緊貼着報紙的,那你經過鏡框看到的內容,就只能鏡框區域的那些內容,爲了能看到所有的內容,就要鏡頭拉遠一些,flexible就是作了以上的事情,而後讓咱們在寫尺寸的時候徹底能夠按照設計稿來寫,也不會幫咱們除以對應的dpr的倍數,可是會幫咱們把視口拉遠了到1/dpr
flexible-2.0
2.0的版本已經沒有針對viewport
的縮放了,增長了對0.5px
的判斷,源碼以下:
(function flexible(window, document) {
var docEl = document.documentElement;
var dpr = window.devicePixelRatio || 1;
// 設置 body 字體
function setBodyFontSize() {
if (document.body) {
document.body.style.fontSize = 12 * dpr + 'px';
} else {
document.addEventListener('DOMContentLoaded', setBodyFontSize);
}
}
setBodyFontSize();
// 設置 rem 基準值
function setRemUnit() {
var rem = docEl.clientWidth / 10;
docEl.style.fontSize = rem + 'px';
}
setRemUnit();
// reset rem unit on page resize
window.addEventListener('resize', setRemUnit);
window.addEventListener('pageshow', function (e) {
if (e.persisted) {
setRemUnit();
}
});
// detect 0.5px supports
if (dpr >= 2) {
var fakeBody = document.createElement('body');
var testElement = document.createElement('div');
testElement.style.border = '.5px solid transparent';
fakeBody.appendChild(testElement);
docEl.appendChild(fakeBody);
if (testElement.offsetHeight === 1) {
docEl.classList.add('hairlines');
}
docEl.removeChild(fakeBody);
}
})(window, document);
複製代碼
咱們看對0.5px
問題的處理
if (dpr >= 2) {
var fakeBody = document.createElement('body');
var testElement = document.createElement('div');
testElement.style.border = '.5px solid transparent';
fakeBody.appendChild(testElement);
docEl.appendChild(fakeBody);
if (testElement.offsetHeight === 1) {
docEl.classList.add('hairlines');
}
docEl.removeChild(fakeBody);
}
複製代碼
大概邏輯是,判斷設備支不支持0.5px
, 若是支持 就在body
上面添加一個名爲hairlines
的class
,因此2咱們的代碼能夠這樣寫
/* dpr=1的時候*/
.line{
border:1px solid red;
}
/* dpr>=2且支持0.5px的時候*/
.hairlines .line{
border:0.5px solid red;
}
複製代碼
可是這也會出現如下兩個問題
dpr>2 且不支持0.5px
的安卓機,咱們應該如何統一處理呢?dpr=3
那麼border
就應該是0.3333px
而不是0.5px
了,可是flexible
把這些狀況都用一個hairlines
包含了看來 flexible
彷佛並不完美,可是咱們也不可否認flexible
適配方案, 拋去1px
問題,能夠說flexible
是完美的
vw/vh
我的認爲vw
適配原理其實和flexible
同樣,都是平分窗口,只不過一個分了10
份一個分了100
份
關於vw
咱們在下章實戰用的時候在具體說明
1px
問題我在爲何咱們常說1px問題而不說2px文章中提到過,若是對於
UI
要求不高的時候,1px
其實也不算什麼問題,每每項目上有不少比1px
更重要的bug須要咱們去解決,但瞭解1px
的本質有助於咱們很好的理解移動端適配原理
解決思路
既然1個css像素表明兩個物理像素,設備又不認0.5px的寫法,那就畫1px,而後再想盡各類辦法將線寬減小一半。
基於這種思考,咱們有如下解決方案
這兩種方案原理同樣,都是設置元素一半有顏色,一半透明,好比作一個2px
高度的圖片,其中1px
是咱們想要的顏色,1px
設置爲透明,適配過程以下
也是flexible 0.3.2
使用的適配方案 咱們能夠把代碼稍微改造下
if (!dpr && !scale) {
var isAndroid = win.navigator.appVersion.match(/android/gi);
var isIPhone = win.navigator.appVersion.match(/iphone/gi);
var devicePixelRatio = win.devicePixelRatio;
// 對於2和3的屏,用2倍的方案,其他的用1倍方案
if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
dpr = 3;
} else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)) {
dpr = 2;
} else {
dpr = 1;
}
// 將 <meta> 根據當前設備的 dpr 標籤進行縮放
scale = 1 / dpr;
}
複製代碼
原理也很簡單,根據對應的dpr
調整對應的縮放比例,從而達到適配的目的,直接縮放頁面我的感受有點暴力
縮放整個頁面太暴力
,那能不能只是縮放邊框呢,答案確定是能夠的咱們不是有 transform: scale
嗎
.border1px{
position: relative;
&::after{
position: absolute;
content: '';
background-color: #ddd;
display: block;
width: 100%;
height: 1px;
transform: scale(1, 0.5); /* 進行縮放*/
top: 0;
left: 0;
}
}
複製代碼
本文主要講解了常見移動端適配及1px
解決方案,本章主要講的理論,下一章咱們會根據實戰得出移動端適配的最佳實踐
,若有興趣記得點贊關注哦💓