最近公司作了不少花里胡哨的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
解决方案,本章主要讲的理论,下一章咱们会根据实战得出移动端适配的最佳实践
,若有兴趣记得点赞关注哦💓