這是一個系列文章,分 3 篇:
javascript
通常咱們在作前端項目以前,都會先拿到視覺稿和交互稿,咱們能夠根據視覺稿上的尺寸、顏色等信息編寫 CSS 樣式,能夠根據交互稿來寫 JS。css
每一個公司的視覺規範不一樣,視覺稿也就不一樣。甚至在一些大公司裏,每一個部門都有本身的視覺規範。好比頁面畫布大小是以 640 爲基準仍是 750。最終給前端開發人員的文件也可能不一樣,好比 PSD 文件、Sketch 文件或者圖片文件,其中包括頁面文件和切圖。html
我作過的項目裏,有這幾種狀況:前端
咱們 UED 畫布基準都是 iphone6,可是有的是給的畫布寬 375 的 Sketch 文件,有的給畫布寬 750 的 PSD 文件。java
下面以我作過的一個頁面爲例,左邊是Sketch 文件的截圖,右邊是 PSD 文件導出的圖片。python
這個頁面涉及到了移動端適配的幾個問題:android
那,拿到視覺稿了,咱們要開始寫代碼了。ios
假如咱們拿到的是寬 750 的視覺稿
**
在寫 CSS 代碼前還得作件事兒,手機端佈局視口默認狀況下是 768px ~ 1024px 之間,手機屏幕寬度你們知道的iphone5 320px, iphone6 375px, iphone6 plus 414px 這樣子。web
像下面圖示這樣,在手機上瀏覽頁面時得橫向滾動,是否是很不友好?chrome
好比屏幕寬 375px,視覺稿 750px,那就設置佈局視口的寬度 width=750,
scale = 375 / 750 = 0.5
<meta name="viewport" content="width=750,initial-scale=0.5,maximum-scale=0.5,user-scalable=no">
複製代碼
再好比屏幕寬 320px,視覺稿 750px,那就設置佈局視口的寬度 width=750,
scale = 320 / 750
固然這是須要根據屏幕寬度動態設置的:
(function(){
var doc = window.document;
var metaEl = doc.querySelector('meta[name="viewport"]');
if(!metaEl){
metaEl = doc.createElement("meta");
metaEl.setAttribute("name", "viewport");
}
var metaCtt = metaEl ? metaEl.content : '';
var matchWidth = metaCtt.match(/width=([^,\s]+)/);
var width = matchWidth ? matchWidth[1] : 750
if(width == 'device-width'){return}
var screenWidth = window.screen.width;
var scale = screenWidth/width;
metaEl.setAttribute("content", "width="+ width +",user-scalable=no,initial-scale=" + scale + ",maximum-scale=" + scale + ",minimum-scale=" + scale);
})()
複製代碼
demo1
下面是依次在 iphone6 plus,iphone6,iphone5 上的展現效果。
那有人就會問啦,給的視覺稿是基於 iphone5 的,畫布寬度 640 怎麼辦?
**
那 CSS 編程就按照視覺給的來,width 設置 640,在寬 640 的佈局視口,CSS 樣式編寫與視覺稿相同,那佈局就能與視覺稿相同啦。再經過動態縮放就能把整個頁面完整的展現在可視窗口中啦。
那又有人問啦,給的不是 750 寬的視覺稿,是 Sketch 視覺稿,寬 375,這怎麼辦?
**
同樣的道理,視覺稿寬 375,視覺稿中的元素都是按寬 375 來佈局的,那咱們佈局視口 width 設置 375,CSS 樣式按375的視覺稿來寫就好啦,最後就是動態縮放啦。
好啦,那咱們來看這種方式能不能解決咱們遇到的幾個問題。
**
實際要解決的是怎麼讓 1 個位圖像素正好覆蓋 1 個物理像素,這樣可讓圖片在不一樣 dpr 的手機上效果同樣。
最好的解決辦法是:不一樣的 dpr 下,加載不一樣的尺寸的圖片
**
實際要解決的是怎麼讓 1 個 CSS 像素正好覆蓋 1 個物理像素,這樣可讓 1px 邊框在不一樣 dpr 的手機上同樣細。
可是佈局視口縮放比爲 1/dpr 才能讓 1 個 CSS 像素是否正好覆蓋1個物理像素。
顯然這種按照佈局視口的寬度與屏幕寬度的比例縮放的方式,只有與視覺稿畫布基準一致的設備才能與視覺稿要求顯示的一致。好比這裏只有在 iphone6 上 1 個 CSS 像素是否正好覆蓋1個物理像素(橫向 200px 的元素對應 400 個物理像素,在縮放 0.5 後,橫向 200px 的元素對應 200 個物理像素,1:1)
其實直接從上面的對比圖就能夠看出來,在不一樣設備上佈局是一致的。
原理很簡單: 咱們寫 CSS 原本就是按視覺稿來寫的,而後總體縮放,以適應各類寬度的設備,元素的比例沒有變。
主要是文字大小,由於是總體縮放,屏幕越大,字體也越大。
這個主要看視覺規範怎麼定,有的以爲這樣挺好,有的就但願字體大小都同樣,還有的但願字體大小不是根據屏幕大小按比例縮放而是對必定範圍內的屏幕寬度設定特定的字體大小。
【原理】
設定佈局視口寬度爲視覺稿畫布寬度,動態設置縮放比例scale=屏幕寬度/視覺稿畫布寬度
【優勢】
實現簡單,可解決不一樣屏幕大小的佈局問題,在各類屏幕上佈局一致
【缺點】
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
複製代碼
這種狀況下有什麼效果呢?
圖片太長,橫着放了^^
看看看,都被擠掉了,爲啥?
這個是 iphone6 的截圖,如今設置了佈局視口寬度是屏幕寬度 375,咱們視覺稿是按 iphone6 二倍圖來的寬 750,那固然就放不下了,好比視覺稿中圖片佔位 400px x 400px,咱們 CSS 中寫的:
img{
width: 400px;
height: 400px;
}
複製代碼
可是佈局視口寬度就 375,圖片就放不下了。
那 iphone6 佈局視口寬只有 375,咱們把視覺稿尺寸所有按比例縮小到 375 就好啦。那 750 到 375,須要除以 2,咱們在編寫 CSS 樣式時除以 2 就好啦。
上面視覺稿中圖片是 400 x 400,咱們 CSS 中就寫:
img{
width: 200px;
height: 200px;
}
複製代碼
其餘元素的寬高、邊框、內邊距、外邊距、字體大小等,統統先除以 2,統統先除以 2。那如今的效果是這樣的,依次是 iphone6 plus,iphone6, iphone5
如今,咱們能看到 100% 呈如今可視窗口中了。那再來看看咱們遇到的問題解決了嗎?
**1px 邊框問題
設置 0.5px,在 dpr=2 的設備中對應 1 個物理像素,在 dpr=3 的設備中對應 1.5 個物理像素。
並且在 dpr=2 的設備中設置 0.5px 不必定能達到咱們想要的效果,看下面:
明明設置的 0.5px,但瀏覽器實際仍是按 1px 處理的。ios7 如下,android 等其餘系統裏,0.5px 會被當成爲 0px 處理? 因此直接設置成 0.5px 是不可行的。 這實際上仍是須要 1 個 CSS 像素正好覆蓋 1 個物理像素就能夠解決的。
**佈局適配的問題
由於沒有按設備屬性動態縮放,每一個設備上每一個css像素大小都同樣,200px 大小的元素在每一個設備上也夠同樣長。
其實從上面的對比圖就能看出來,圖片容器(灰色背景)在不一樣屏幕大小的手機都是同樣的高,整個頁面在小屏手機中就須要向下滾動查看,一排展現的 4 個圖片標籤在大屏手機中右邊的空白就比較大。佈局問題也沒有解決。
**內容適配的問題 **
文字大小通通同樣,也沒有解決。
前面已經講了幾遍了,要解決 1px 邊框的問題,達到在不一樣分辨率設備上效果一致(固然也與視覺稿一致),須要讓不一樣分辨率設備上 1 個 css 像素只覆蓋 1 個物理像素。
前面咱們將 750 視覺稿中的尺寸除以 2 來編寫樣式,只能在 iphone6 上的效果知足。那如今咱們在佈局寬度=屏幕寬度時,讓縮放比例爲 1/dpr,就可讓 1 個 css像素只覆蓋1個物理像素。
好比 750 的視覺稿,元素邊框 1px,咱們 CSS 樣式也寫:
div{
border-width: 1px;
}
複製代碼
而後 JS 動態設置縮放邏輯大概是這樣子:
var doc = document;
var docEl = document.documentElement;
var isIos = navigator.appVersion.match(/(iphone|ipad|ipod)/gi);
var dpr = window.devicePixelRatio || 1;
if (!isIos){ dpr = 1 }
var scale = 1 / dpr;
var metaEl = doc.querySelector('meta[name="viewport"]');
if (!metaEl) {
metaEl = doc.createElement("meta");
metaEl.setAttribute("name", "viewport");
doc.head.appendChild(metaEl)
}
metaEl.setAttribute("content", "width=device-width,user-scalable=no,initial-scale=" + scale + ",maximum-scale=" + scale + ",minimum-scale=" + scale);
複製代碼
那如今不管什麼分辨率的設備,1個css像素實際只佔1個物理像素了。
那如今效果是什麼樣子呢?
全部元素尺寸都縮小 1/dpr 了,本質上是 1 個 CSS 像素寬度縮小了 1/dpr,不一樣 dpr 的設備上 CSS 像素大小就不一樣了。那咱們 CSS 樣式 200px,在不一樣 dpr 的設備上所佔寬度也不一樣了。
注意: 若是在安卓設備上查看 demo3,與 demo2 的效果相同,由於 dpr 都被處理成 dpr=1,沒有縮放,CSS 樣式又與 750 視覺稿一致。因此這種方式只縮放,不控制佈局是不行的。
**
這個問題能夠在待會講佈局適配時解決,佈局適配的目的是讓元素在不一樣設備上佔比一致(與視覺稿相同。)
能夠看下demo3的圖
那咱們但願,元素在佈局視口中的佔比(包括元素寬高、邊距等)在不一樣設備上都相同
同一份樣式怎麼讓元素佔比相同呢?用絕對單位 px 確定是不行的,咱們考慮相對單位 rem。
假若有個寬能夠表示爲 20rem 的元素:
CSS 樣式 | CSS 像素個數 |
---|---|
width:20rem | 20 * html元素的font-size |
元素佔比 = 元素 CSS 像素個數 / 佈局視口寬度
= 20 * html元素的font-size / 佈局視口寬度
複製代碼
因此只要知足:
常量值 * HTML 元素的 font-size = 佈局視口寬度
複製代碼
元素佔比在任何設備上都是某一個定值了。
那咱們只要按下面的公式動態設置 HTML 元素的 font-size 就好啦。
HTML 元素的 font-size = 佈局視口寬度 / 常量值
複製代碼
這個常量值能夠是任意一個常數,但爲了寫樣式方便,咱們會作一些考慮,例如:
爲了解決前面 1px 邊框 retina 屏顯示的問題,頁面縮放了 1/dpr,iphone6 佈局視口的寬度就是:375 * 2 個 css 像素
咱們可讓 iphone6 中 HTML 元素的 font-size = 375 * 2 / 7.5 = 100,這樣能夠直接將視覺稿中的尺寸除以100,獲得 rem 單位的數值
對於以 iphone6 爲基準的 750 視覺稿,而且縮放 1/dpr,HTML 元素的 font-size 與 佈局視口寬度視口的比例能夠是:
HTML 元素的 font-size = 佈局視口寬度 / 7.5
複製代碼
對於以 iphone5 爲基準的 640 視覺稿,而且縮放 1/dpr
HTML 元素的 font-size = 佈局視口寬度 / 6.4
複製代碼
大概的實現就是這樣子:
var doc = document;
var docEl = document.documentElement;
var isIos = navigator.appVersion.match(/(iphone|ipad|ipod)/gi);
var dpr = window.devicePixelRatio || 1;
if (!isIos){ dpr = 1 }
var scale = 1 / dpr;
var metaEl = doc.querySelector('meta[name="viewport"]');
if (!metaEl) {
metaEl = doc.createElement("meta");
metaEl.setAttribute("name", "viewport");
doc.head.appendChild(metaEl)
}
metaEl.setAttribute("content", "width=device-width,user-scalable=no,initial-scale=" + scale + ",maximum-scale=" + scale + ",minimum-scale=" + scale);
setTimeout(function(){
var width = docEl.getBoundingClientRect().width;
docEl.style.fontSize = width / 7.5 + 'px';
})
複製代碼
由於 scale=1/dpr && 基於 750 寬的視覺稿,元素的CSS 樣式(rem)能夠直接由視覺稿尺寸除以 100
最後的效果是:
HTML 元素的 font-size = 佈局視口寬度 / 10
複製代碼
HTML 元素的 font-size = 佈局視口寬度 / 7.5
複製代碼
那有人會問啦,個人視覺稿是基於 iphone6 的,但給的是一倍圖寬 375 怎麼辦?
**
視覺稿通常有兩種類型: 一倍圖 和 二倍圖,好比基於 iphone6 的:
畫布寬 750,元素 400 x 400
畫布寬 375,元素 200 x 200
縮放通常有兩種:
scale = 1,佈局視口寬度 = 375 個 CSS 像素
scale = 1/dpr,佈局視口寬度 = 750 個 CSS 像素
若是視覺稿選擇畫布寬 750,而且 scale=1,那元素 CSS 像素應該由視覺稿中的尺寸除以 2,使佈局視口寬度 375 中 400/2 x 400/2 與 視覺稿寬 750 中 400 x 400 的元素比例一致
若是視覺稿選擇畫布寬 375,而且 scale=1/dpr,那元素 CSS 像素應該由視覺稿中的尺寸乘以 2,使佈局視口寬度 750 中的元素 200x2 x 200x2 與 視覺稿寬 375 中 400 x 400 的元素比例一致
其餘狀況,元素 CSS 像素值就是視覺稿中的值。
知道這個以後,再決定 px 與 rem 的換算值:HTML 元素的 font-size,而後再寫 CSS 樣式(rem)。
舉幾個例子:
若是視覺稿選擇畫布寬 750,而且 scale=1,HTML 元素的 font-size = 375 / 7.5 = 50,400/2/50= 4rem
img{
width: 4rem;
height: 4rem;
}
複製代碼
若是視覺稿選擇畫布寬 375,而且 scale=1/dpr,HTML 元素的 font-size = 750 / 7.5 = 100,200 * 2 /100 = 4rem
img{
width: 4rem;
height: 4rem;
}
複製代碼
畫布寬 750 & scale=1/dpr,HTML 元素的 font-size = 750 / 7.5 = 100,400 / 100 = 4rem
img{
width: 4rem;
height: 4rem;
}
複製代碼
畫布寬 375 & scale=1,HTML 元素的 font-size = 375 / 7.5 = 50,200 / 50 = 4rem
img{
width: 4rem;
height: 4rem;
}
複製代碼
一、設置 HTML 元素的 font-size 與佈局視口寬度成比例,CSS 樣式使用相對單位 rem,元素尺寸也就與佈局視口寬度成比例,從而在每一個設備佈局一致。用視覺稿來肯定這個比例值就能與視覺稿的佈局一致。
二、 設置縮放比例爲 1/dpr 能夠解決 1px 邊框問題。
爲了 rem 與 px 換算方便,選定的比例值可讓 HTML 元素的 font-size 爲 100px。
iphone6 就是 7.5,iphone5 就是 6.4,不一樣基準的視覺稿 rem 與 px 換算比例都是 100.
最後,若是不想動態適配的內容,能夠直接使用 px 單位,好比字體,還有1px邊框。
這裏講的適配方案是今天 [2017-08-11] 的,之後網站方案可能會有改動,因此我把代碼保存下來了,能夠看適配代碼
**
淘寶統必定爲 10,iphone6 是 1rem = 75px,iphone5 1rem = 64px,能夠到手機淘寶驗證。不一樣基準的視覺稿 rem 與 px 換算比例不一樣。
並無作縮放,只使用了相對單位 rem。
HTML 的 font-size = 佈局視口寬度/7.5 = 設備屏幕寬度 / 7.5
複製代碼
在 iphone6 上,HTML 的 font-size 爲 375/7.5 = 50px,若是他們的視覺稿是 1 倍圖,那就是直接除以 50 來寫 CSS 的,若是是二倍圖,就是除以 100 來寫 CSS 的。
這個比例能夠隨便設置,根據實際的需求,網易設置 7.5 時考慮換算簡單,淘寶設置 10 則是考慮兼容 vw。
動態修改 HTML 元素的 font-size 和動態修改 viewport 在不少安卓設備上有兼容性問題。如今愈來愈多的瀏覽器支持 vw、vh,viewport+rem 的方案不必定是最好的方案。
對於 1px 邊框的問題,也有不少其餘的解決方案。