h5 手機螢幕適配—REM
一、rem、em和px之間的關係
使用rem之前,先得弄清楚rem、em和px之間的關係,特別是每一個單位的使用跟程式碼塊的繼承之間的關係:
通過對比會發現:只是單位使用不一樣但效果卻是截然不同的。rem和em都是相對單位,px則不是
在CSS樣式表中,單位em是作為字型高度的單位來使用的,但實際字型大小的高度顯示是使用者對DPI的定義來決定的。為了改善這種樣式規則,單位rem則是直接取決於文件根元素字型預設大小,也可以理解為root em,跟em有所不同的是使用rem單位的字型大小在整個文件中都是恆定不變的
二、初識rem
rem 的官方定義:The font size of the root element,即以根節點的字型大小作為基準值進行長度計算。
rem的相容性:
通過上圖可見:
- 大部分主流瀏覽器支援,但要注意IE9、IE10
- iOS:6.1系統以上都支援
- Android:2.1系統以上都支援
三、rem 為解決什麼問題而存在
先不談安卓,就以iphone各種手機的尺寸來說,iPhone4,5 寬度320px,iPhone6 375px,iPhone6 plus 414px. iPad 768px。如果一個按鈕,固定一個75x25的尺寸,那麼就必然會導致在不同尺寸下的相對大小不一樣。這帶來的問題就在於會直接影響到設計的美觀,可能在iPhone6下,一個完美的設計圖,到了iPhone5,就變得low很多。為了讓頁面元素的尺寸能夠在裝置寬度變化的時候也跟著變化,rem就出現了
使用rem適配的原理就是我們只需要在裝置寬度大小變化的時候,調整html的字型大小,那麼頁面上所有使用rem單位的元素都會相應的變化。 這也是rem與px最大的區別。
四、利用meta標籤對viewport進行控制
在講述rem佈局之前得先搞明白的就是移動裝置上的viewport,只有明白了viewport的概念以及弄清楚了跟viewport有關的meta標籤的使用,才能更好地讓我們的網頁適配或響應各種不同解析度的移動裝置。
1、viewport的概念
通俗的講,移動裝置上的viewport就是裝置的螢幕上能用來顯示我們的網頁的那一塊區域,即瀏覽器上(也可能是一個app中的webview)用來顯示網頁的那部分割槽域,但viewport又不侷限於瀏覽器可視區域的大小,它可能比瀏覽器的可視區域要大,也可能比瀏覽器的可視區域要小。
下圖列出了一些裝置上瀏覽器預設的viewport寬度。
2、css中的1px並不等於裝置的1px
css中的畫素只是一個抽象的單位,在不同的裝置或不同的環境中,css中的1px所代表的裝置物理畫素是不同的。
在移動端瀏覽器中以及某些桌面瀏覽器中,window物件有一個devicePixelRatio屬性,它的官方的定義為:
window.devicePixelRatio is the ratio between physical pixels and device-independent pixels (dips) on the device.
window.devicePixelRatio = physical pixels / dips
裝置物理畫素和裝置獨立畫素的比例,也就是 devicePixelRatio = 物理畫素 / 獨立畫素。css中的px就可以看做是裝置的獨立畫素,所以通過devicePixelRatio,我們可以知道該裝置上一個css畫素代表多少個物理畫素。
需要注意的是,devicePixelRatio存在相容性問題,具體可以檢視PPK made some research on devicePixelRatio
拓展:PPK的關於三個viewport的理論
ppk把移動裝置上的viewport分為layout viewport 、 visual viewport 和 ideal viewport 三類,其中的ideal viewport是最適合移動裝置的viewport,ideal viewport的寬度等於移動裝置的螢幕寬度,只要在css中把某一元素的寬度設為ideal viewport的寬度(單位用px),那麼這個元素的寬度就是裝置螢幕的寬度了,也就是寬度為100%的效果。ideal viewport 的意義在於,無論在何種解析度的螢幕下,那些針對ideal viewport 而設計的網站,不需要使用者手動縮放,也不需要出現橫向滾動條,都可以完美的呈現給使用者。
類別 | 簡述 | 說明 | 圖例 |
---|---|---|---|
layout viewport | 瀏覽器預設的viewport | layout viewport 的寬度是大於瀏覽器可視區域的寬度的 | |
visual viewport | 瀏覽器可視區域的大小 | 在Android 2, Oprea mini 和 UC 8中無法正確獲取 | |
ideal viewport | 移動裝置的理想viewport | 理想適配指的是:1、不需要使用者縮放和橫向滾動條就能正常的檢視網站的所有內容;2、顯示的文字的大小是合適,無論是在何種密度螢幕,何種解析度下,顯示出來的大小都是差不多的。當然,不只是文字,其他元素如圖片等也如此 |
3、利用meta標籤對viewport進行控制
在蘋果的規範中,meta viewport 有6個指令,[可以同時使用,也可以單獨使用或混合使用,多個指令同時使用時用逗號隔開]如下:
指令 | 說明 |
---|---|
width | 設定layout viewport 的寬度,為一個正整數,或字串”width-device” |
initial-scale | 設定頁面的初始縮放值,為一個數字,可以帶小數 |
minimum-scale | 允許使用者的最小縮放值,為一個數字,可以帶小數 |
maximum-scale | 允許使用者的最大縮放值,為一個數字,可以帶小數 |
height | 設定layout viewport 的高度,這個屬性對我們並不重要,很少使用 |
user-scalable | 是否允許使用者進行縮放,值為”no”或”yes”, no 代表不允許,yes代表允許 |
要把當前的viewport寬度設為ideal viewport的寬度,既可以設定 width=device-width
,也可以設定 initial-scale=1
,但這兩者各有一個小缺陷,就是iphone、ipad以及IE 會橫豎屏不分[下圖所示],通通以豎屏的ideal viewport寬度為準。所以,最完美的寫法應該是,兩者都寫上去,這樣就initial-scale=1
解決了 iphone、ipad的毛病,width=device-width
則解決了IE的毛病:
<meta name="viewport" content="width=device-width, initial-scale=1">
//注意:縮放是相對於ideal viewport來縮放的,縮放值越大,當前viewport的寬度就會越小,反之亦然
五、rem佈局
rem佈局基本概念
rem佈局就是指為文件的根節點元素設定一個基準字型大小,然後所有的元素尺寸都以rem為單位來寫,為了能夠在不同尺寸的手機螢幕上自適應,需要用js來判斷手機寬度,並動態設定<html>
的字型大小,這樣基準字型變了,元素的尺寸自然相應變化,達到了自適應的效果。
廢話說了辣麼多,這裡給大家介紹一個移動端佈局開發解決方案
使用動態的HTML根字型大小和動態的viewport scale。
遵循視覺一致性原則。在不同大小的螢幕和不同的裝置畫素密度下,讓你的頁面看起來是一樣的。
不僅便捷了你的佈局,同時它使用起來異常簡單。
優勢
- 保證不同裝置下的統一視覺體驗。
- 不需要你再手動設定
viewport
,根據當前環境計算出最適合的viewport
。 - 支援任意尺寸的設計圖,不侷限於特定尺寸的設計圖。
- 支援單一專案,多種設計圖尺寸,專為解決大型,長週期專案。
- 提供
px2rem
轉換方法,CSS佈局,零成本轉換,原始值不丟失。 - 有效解決移動端真實1畫素問題。
用法
1、 引入hotcss.js
<script src="/path/to/hotcss.js"></script>
2、css要怎麼寫
hotcss
提供的將px轉為rem的方法,可根據您的需要選擇使用。
//px2rem.scss
@function px2rem( $px ){
@return $px*320/$designWidth/20 + rem;
}
推薦使用scss來編寫css,在scss檔案的頭部使用import
將px2rem
匯入,
@import '/path/to/px2rem.scss';
如果你的專案是單一尺寸設計圖,那麼你需要去px2rem.scss中定義全域性的designWidth
。
@function px2rem( $px ){
@return $px*320/$designWidth/20 + rem;
}
$designWidth : 750; //如設計圖是750
如果你的專案是多尺寸設計圖,那麼就不能定義全域性的designWidth
了。需要在你的業務scss
中單獨定義。如以下是style.scss
@import '/path/to/px2rem.scss';
$designWidth : 750; //如設計圖是750
$designWidth
必須要在使用px2rem
前定義。否則scss編譯會出錯。
注意:如果使用less,則需要引入less-plugin-functions
,普通的less編譯工具無法正常編譯。
3、想用px怎麼辦?
直接寫px肯定是不能適配的,那hotcss.js
會在html上註冊data-dpr
屬性,這個屬性用來標識當前環境dpr值。那麼要使用px可以這麼寫。
//scss寫法
#container{
font-size: 12px ;
[data-dpr="2"] &{
font-size: 24px;
}
[data-dpr="3"] &{
font-size: 36px;
}
}
可能你會說 talk is cheap,show me the code
,那我現在列下hotcss整個專案的目錄結構。
├── example //所有的示例都在這個目錄下
│ ├── duang
│ ├── normal
│ └── wolf
│
└── src //主要檔案在這裡
├── hotcss.js
├── px2rem.less
├── px2rem.scss
└── px2rem.styl
- hotcss.js
(function( window , document ){
'use strict';
//給hotcss開闢個名稱空間,別問我為什麼,我要給你準備你會用到的方法,免得用到的時候還要自己寫。
var hotcss = {};
(function() {
//根據devicePixelRatio自定計算scale
//可以有效解決移動端1px這個世紀難題。
var viewportEl = document.querySelector('meta[name="viewport"]'),
hotcssEl = document.querySelector('meta[name="hotcss"]'),
dpr = window.devicePixelRatio || 1,
maxWidth = 540,
designWidth = 0;
dpr = dpr >= 3 ? 3 : ( dpr >=2 ? 2 : 1 );
//允許通過自定義name為hotcss的meta頭,通過initial-dpr來強制定義頁面縮放
if (hotcssEl) {
var hotcssCon = hotcssEl.getAttribute('content');
if (hotcssCon) {
var initialDprMatch = hotcssCon.match(/initial\-dpr=([\d\.]+)/);
if (initialDprMatch) {
dpr = parseFloat(initialDprMatch[1]);
}
var maxWidthMatch = hotcssCon.match(/max\-width=([\d\.]+)/);
if (maxWidthMatch) {
maxWidth = parseFloat(maxWidthMatch[1]);
}
var designWidthMatch = hotcssCon.match(/design\-width=([\d\.]+)/);
if (designWidthMatch) {
designWidth = parseFloat(designWidthMatch[1]);
}
}
}
document.documentElement.setAttribute('data-dpr', dpr);
hotcss.dpr = dpr;
document.documentElement.setAttribute('max-width', maxWidth);
hotcss.maxWidth = maxWidth;
if( designWidth ){
document.documentElement.setAttribute('design-width', designWidth);
hotcss.designWidth = designWidth;
}
var scale = 1 / dpr,
content = 'width=device-width, initial-scale=' + scale + ', minimum-scale=' + scale + ', maximum-scale=' + scale + ', user-scalable=no';
if (viewportEl) {
viewportEl.setAttribute('content', content);
} else {
viewportEl = document.createElement('meta');
viewportEl.setAttribute('name', 'viewport');
viewportEl.setAttribute('content', content);
document.head.appendChild(viewportEl);
}
})();
hotcss.px2rem = function( px , designWidth ){
//預判你將會在JS中用到尺寸,特提供一個方法助你在JS中將px轉為rem。就是這麼貼心。
if( !designWidth ){
//如果你在JS中大量用到此方法,建議直接定義 hotcss.designWidth 來定義設計圖尺寸;
//否則可以在第二個引數告訴我你的設計圖是多大。
designWidth = parseInt(hotcss.designWidth , 10);
}
return parseInt(px,10)*320/designWidth/20;
}
hotcss.rem2px = function( rem , designWidth ){
//新增一個rem2px的方法。用法和px2rem一致。
if( !designWidth ){
designWidth = parseInt(hotcss.designWidth , 10);
}
//rem可能為小數,這裡不再做處理了
return rem*20*designWidth/320;
}
hotcss.mresize = function(){
//給HTML設定font-size。
var innerWidth = document.documentElement.getBoundingClientRect().width || window.innerWidth;
if( hotcss.maxWidth && (innerWidth/hotcss.dpr > hotcss.maxWidth) ){
innerWidth = hotcss.maxWidth*hotcss.dpr;
}
if( !innerWidth ){ return false;}
document.documentElement.style.fontSize = ( innerWidth*20/320 ) + 'px';
hotcss.callback && hotcss.callback();
};
hotcss.mresize();
//直接呼叫一次
window.addEventListener( 'resize' , function(){
clearTimeout( hotcss.tid );
hotcss.tid = setTimeout( hotcss.mresize , 33 );
} , false );
//繫結resize的時候呼叫
window.addEventListener( 'load' , hotcss.mresize , false );
//防止不明原因的bug。load之後再呼叫一次。
setTimeout(function(){
hotcss.mresize();
//防止某些機型怪異現象,非同步再呼叫一次
},333)
window.hotcss = hotcss;
//名稱空間暴露給你,控制權交給你,想怎麼調怎麼調。
})( window , document );
栗子:
//.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>這是一個最最普通的演示</title>
<!--
根據頁面渲染機制,`hotcss.js`必須在其他JS載入前載入,萬不可非同步載入。
如果可以,你應將`hotcss.js`的內容以內嵌的方式寫到`<head>`標籤裡面進行載入,並且保證在其他js檔案之前。
為了避免不必要的bug,請將CSS放到該JS之前載入。
-->
<link rel="stylesheet" href="./css/style.css">
<script src="../hotcss.js"></script>
</head>
<body>
<div id="header">普通的演示</div>
<div id="container1">
這是一個普通的演示。在head頭載入hotcss.js。
優雅的使用scss書寫css。單位均使用了px2rem處理
</div>
<div id="container2">
這個裡面的字型沒有使用px2rem。具體寫法參看css。
如果你在使用chrome。請開啟控制檯,嘗試模擬不同裝置,來查看錶現。
</div>
</body>
</html>
//.scss
@import 'px2rem'; //第一步先把px2rem匯入
$designWidth : 640; //寫scss之前,必須要先定義designWidth。
body{
width: 16rem;
margin: 0 auto;
padding: 0;
}
#header{
width: px2rem(640);
height: px2rem(88);
line-height: px2rem(88);
background-color: #33aa33;
text-align: center;
font-size: px2rem(48);
color: rgba(255,255,255,1);
}
#container1{
margin-top: px2rem(10);
padding: px2rem(30);
background-color: #191919;
color: #f0f0f0;
line-height: 1.7;
max-height: 100%;
font-size: px2rem(32);
}
#container2{
margin-top: px2rem(10);
padding: px2rem(30);
background-color: #191919;
color: #f0f0f0;
line-height: 1.7;
max-height: 100%;
font-size: 12px ;
[data-dpr="2"] &{
font-size: 24px;
}
[data-dpr="3"] &{
font-size: 36px;
}
}
介面說明
initial-dpr
可以通過強制設定dpr。來關閉響應的viewport scale。使得viewport scale始終為固定值。
<meta name="hotcss" content="initial-dpr=1">
<script src="/path/to/hotcss.js"></script>
<!--
如iphone微信強設dpr=1,則可以長按識別二維碼。
注意,強制設定dpr=1後,css中的1px在2x,3x屏上則不再是真實的1px。
-->
max-width
通過設定該值來優化平板/PC訪問體驗,注意該值預設值為540。設定為0則該功能關閉。
為了配合使用該設定,請給body增加樣式width:16rem;margin:0 auto;
。
<meta name="hotcss" content="max-width=640">
<script src="/path/to/hotcss.js"></script>
<!--
預設為540,可根據具體需求自己定義
-->
<style>
body{
width: 16rem;
margin: 0 auto;
}
</style>
design-width
通過對design-width的設定可以在本頁執行的JS中直接使用hotcss.px2rem/hotcss.rem2px
方法,無需再傳遞第二個值。
<meta name="hotcss" content="design-width=750">
<script src="/path/to/hotcss.js"></script>
hotcss.mresize
用於重新計算佈局,一般不需要你手動呼叫。
hotcss.mresize();
hotcss.callback
觸發mresize的時候會執行該方法。
hotcss.callback = function(){
//your code here
}
單位轉換hotcss.px2rem/hotcss.rem2px
hotcss.px2rem
和 hotcss.rem2px
。你可以預先設定可以在meta中設定hotcss.designWidth
design-width
,則之後使用這兩個方法不需要再傳遞第二個引數。
迭代後仍然支援在js中設定hotcss.designWidth
,推薦使用meta去設定。
/**
* [px2rem px值轉換為rem值]
* @param {[number]} px [需要轉換的值]
* @param {[number]} designWidth [設計圖的寬度尺寸]
* @return {[number]} [返回轉換後的結果]
*/
hotcss.px2rem( px , designWidth );
/**
* 同上。
* 注意:因為rem可能為小數,轉換後的px值有可能不是整數,需要自己手動處理。
*/
hotcss.rem2px( rem , designWidth );
//你可以在meta中定義design-width,此後使用px2rem/rem2px,就不需要傳遞designWidth值了。同時也支援舊的設定方式,直接在JS中設定hotcss.designWidth
hotcss.px2rem(200);
hotcss.rem2px(350);
六、使用rem的線上站點
使用rem的大站
使用hotcss的站點