移动端H5界面适配解决方案
技术、总结 Feb 18, 2017
由于手机不同的屏幕尺寸,在做移动端H5页面时,为了能够更好的适配不同的屏幕,使其达到相对较好的显示效果,我们就需要对H5页面进行适配。
常规的适配方案包括使用百分比、em单位、rem单位、手淘的flexible等方案。其中百分比和em是大家已经耳熟能详的的解决方案,这里将不再介绍。本文将着重介绍rem的使用,和手淘flexible解决方案的实现思路。
在文章开始前,我们先了解一些有关移动端屏幕的基本概念,来做一些简单的铺垫。
概念简述
viewport
viewport是大家经常看到的名词,它表示浏览器的视窗大小,一般都是使用meta标签来定义网页相对于视窗的规格。我们经常会使用下面的方式来定义页面的视窗规格:
<meta name="viewport" content="width=device-width, initial-scale=1" />
表示页面的宽度即为浏览器视窗的宽度,初始化大小为1(不进行缩放)。
物理像素
物理像素又被称为设备像素,它是显示设备中一个最微小的物理部件。我们经常看到的一倍屏、二倍屏(Retina)、三倍屏,指的是设备以多少物理像素来显示一个CSS像素,也就是说,多倍屏以更多更精细的物理像素点来显示一个CSS像素点。例如,在普通屏幕(一倍屏)下1个CSS像素对应1个物理像素,而在Retina屏幕(二倍屏)下,1个CSS像素对应的却是4个物理像素。多倍屏的物理像素与CSS像素的关系,是以“田”字型关联的,二倍屏即为横向2*纵向2的物理像素表示一个CSS像素。
CSS像素
CSS像素是一个抽像的单位,主要使用在浏览器上,用来精确度量Web页面上的内容。一般情况下,CSS像素称为与设备无关的像素(device-independent pixel),简称DIPs。在Web端写页面样式的时候,我们一般会以1px来表示1个CSS像素。
设备独立像素dp
设备独立像素表示屏幕可视区宽度,简称dip或dp。移动端设备分为非视网膜和视网膜屏幕。
非视网膜屏幕是指,屏幕可视区域宽度是320像素,就只能显示320像素的内容。
视网膜屏幕是指,屏幕可视区域宽度是320像素,却能够显示640像素、960像素的内容。
设备像素比dpr
设备像素比全称device pixel ratio,简称dpr,用来定义物理像素和设备独立像素的对应关系。计算公式为:
设备像素比 = 物理像素 / 设备独立像素
在Retina屏上,设备像素比=2,也就是说1个CSS像素相当于2个物理像素。
移动端适配方案
通过上面概念的梳理,我们大致了解了移动端的一些屏幕基础知识。接下来,我们来探讨下移动端H5页面的适配方案。
rem
rem是CSS的一个单位。相对于px来说,它不是固定的一个像素大小;相对于em来说,它的计算依赖于根元素(html)而非父元素。
rem是根据根元素(html)的font-size值来动态计算的,假设此时根元素的font-size为16px,则1rem=16px。根据这个特点,可以根据设备宽度动态计算根元素的font-size,使得以rem为单位的元素可以动态响应屏幕宽度来展示。
viewport
通过设置viewport的值,来进行缩放。例如在一倍屏下,可以这样设置:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
设置页面的显示比例和浏览器视窗1:1。对于二倍屏,我们可以这样设置:
<meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">
然后我们把页面上的元素的css像素乘以2,相当于是我页面内容放大两倍,然后页面再整体缩小一倍。
手淘的实现方式
手淘flexible的实现思路:
1、动态修改标签
2、给<html>元素添加data-dpr属性,并且动态改写data-dpr的值
3、给<html>元素添加font-size属性,并且动态改写font-size的值
页面加载前,通过执行一段js脚本,来根据手机屏幕的宽度、dpr和设计稿宽度设置根元素的data-dpr和font-size。
摘要一段工作中使用到的js实现方法:
(function(global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.rem = factory());
}(this, (function() {
//'use strict';
//@todo 设计图尺寸不同机型等比例缩放模式,基础html尺寸不同机型适配模式
var rem = {
designWidth: 750, //设计稿宽px值
px2rem: 100, //px to rem为100倍比例转换,宽度建议用百分比
defaultFontSize: 20, //默认1rem字体大小
maxWidth: 0, //rem适配最大尺寸(屏幕再大也就那个尺寸了)
dpr: parseInt(window.devicePixelRatio || 1),
};
try {
if (rem.android) {
//android设置dpr不生效
rem.dpr = 1;
}
// 设置默认最大尺寸
var clientWidth = window.screen.width;
if(clientWidth >= 480){
rem.maxWidth = 360;
}
} catch (e) {}
//根据devicePixelRatio自定计算scale
//可以有效解决移动端1px这个世纪难题。
var remEl = document.querySelector('meta[name="rem"]');
//允许通过自定义name为rem的meta头,通过initial-dpr来强制定义页面缩放
if (remEl) {
var remCon = remEl.getAttribute('content');
if (remCon) {
var initialDprMatch = remCon.match(/initial\-dpr=([\d\.]+)/);
if (initialDprMatch) {
rem.dpr = parseFloat(initialDprMatch[1]);
}
var designWidthMatch = remCon.match(/design\-width=([\d\.]+)/);
if (designWidthMatch) {
rem.designWidth = parseFloat(designWidthMatch[1]);
}
var maxWidthMatch = remCon.match(/max\-width=([\d\.]+)/);
if (maxWidthMatch) {
rem.maxWidth = parseFloat(maxWidthMatch[1]);
}
}
}
document.documentElement.setAttribute('data-dpr', rem.dpr);
document.documentElement.setAttribute('max-width', rem.maxWidth);
var viewportEl = document.querySelector('meta[name="viewport"]');
var scale = 1 / rem.dpr;
var 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);
}
var d = document.createElement('div');
d.style.width = '1rem';
d.style.display = 'none';
document.head.appendChild(d);
try {
rem.defaultFontSize = parseFloat(window.getComputedStyle(d, null).getPropertyValue('width'));
} catch (e) {}
rem.get_px = function(px_num) {
var px = parseInt(px_num);
if (!px) {
return false;
}
return rem.dpr * px + 'px';
}
rem.resize = function() {
//对,这个就是核心方法了,给HTML设置font-size。
var measureWidth = 0;
try {
measureWidth = document.documentElement.getBoundingClientRect().width;
} catch (e) {}
if (!measureWidth) {
measureWidth = window.innerWidth;
}
if (!measureWidth) { return false; }
//设置最大尺寸
if (rem.maxWidth) {
var tp = rem.maxWidth * rem.dpr;
if (measureWidth > tp) {
measureWidth = tp;
}
}
var fontSize = measureWidth / (rem.designWidth / rem.px2rem) / rem.defaultFontSize * 100;
var elName = 'rem';
var remStyle = document.getElementById(elName);
if (remStyle) {
remStyle.innerHTML = "html{font-size:" + fontSize + "% !important;}";
} else {
remStyle = document.createElement('style');
remStyle.setAttribute('id', elName);
remStyle.innerHTML = "html{font-size:" + fontSize + "% !important;}";
document.head.appendChild(remStyle);
}
rem.callback && rem.callback();
};
rem.resize();
//直接调用一次
window.addEventListener('resize', function() {
clearTimeout(rem.tid);
rem.tid = setTimeout(rem.resize, 33);
}, false);
//绑定resize的时候调用
window.addEventListener('load', rem.resize, false);
//防止不明原因的bug。load之后再调用一次。
setTimeout(function() {
rem.resize();
//防止某些机型怪异现象,异步再调用一次
}, 333);
return rem;
})));