/ 前端

移动端适配记

做工作室的新官网,芦荟学姐用sketch画了设计稿,分别是pc和mobile的版本。有两种方案来实现网站的pc和移动适配。一种是直接做成响应式,类似bootstrap实现的那样。还有一种就是专门定制不同的版本,也就是两套。考虑到响应式版本会给移动端的带来不少不必要,冗余的代码。且一套分工不能十分明确。所以我和芦荟选择后面一套。

我负责移动端的开发,就记录一下移动适配的方案。

检测设备

当pc设备访问m.betahouse.us 的时候 跳转到www.betahouse.us

var pathname = window.location.pathname;
    var url = location.href;
    var mDomain = 'm.betahouse.us';
    if ( (url.indexOf(mDomain) != -1) && ! navigator.userAgent.match(/(iPhone|iPod|Android|ios|iPad)/i) ) {
        var newUrl = url.replace('http://m', 'http://www');
        location.href = newUrl;
    }

移动端适配

之所以要适配移动端,就是因为移动端分繁多杂的屏幕规格。有屏幕大小之分,有屏幕清晰之分等等。适配移动端主要依据dpr来实现。

什么是dpr?

首先可以打开chrome调试工具

iphone5的dpr
iphone5 的 dpr
iphone6 plus的dpr
iphone6 plus 的 dpr
可以看到 iphone5的dpr是2,而iphone6plus的dpr是3,但dpr到底是个什么鬼?

dpr全称device pixel ratio,即设备像素比。是物理像素与css像素(确切讲是设备独立像素(density-independent pixel))x方向或y方向的数量之比。在js中dpr可以使用 window.devicePixelRatio来得到。

如图
dpr

在普通屏幕下,1个css像素 对应 1个物理像素(1:1) dpr为1
在retina 屏幕下,1个css像素对应 4个物理像素(1:4) dpr为2

这就有一个很严重的问题,比如当我们想在retina上实现border 1px的时候,实际对应的css像素应该是0.5px;如果在dpr为3的屏幕上,css像素就应该是0.333333...px。且不说要写多个适配media,css的border设置成0.5px或者0.3333px,本身就存在无法被识别的问题。那要怎么办呢? 请往下看。

屏幕缩放

在移动端开发中,在head中都得添加一串meta标签,比如:

<meta content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0" name="viewport" />

先来认识一下各个属性代表什么意思

  • width - viewport的宽度
  • height - viewport的高度
  • initial-scale - 初始的缩放比例
  • minimum-scale - 允许用户缩放到的最小比例
  • maximum-scale - 允许用户缩放到的最大比例
  • user-scalable - 用户是否可以手动缩放

所以上面的meta标签表示:强制让文档的宽度与设备的宽度保持1:1,并且文档最大的宽度比例是1.0,且不允许用户点击屏幕放大浏览;

文档的宽度与设备的宽度保持1:1,是在普通屏幕下的做法,那我们可不可以根据相应的dpr在动态设置这个比呢?

比如,在dpr为2的屏幕中,将整个页面缩放到原来的0.5倍。

<meta name="viewport" content="width=640,initial-scale=0.5,maximum-scale=0.5, minimum-scale=0.5,user-scalable=no">

这样页面中所有的border: 1px都将缩小0.5,从而达到border: 0.5px;

当然,页面的缩放,会影响整个页面的px设置。字体会变小,布局的宽高都会变。那怎么办? 请继续往下看。

多屏适配

在移动端适配中,rem 是不能不提的,他根据root的{font-size}来计算尺寸,所以根据他的实现原理,我们可以根据不同的dpr来设置不同的html{font-size}, 从而使得设置相同的rem值,但在不同的dpr屏幕下有不同的表现。

综上,我们创建一个脚本,根据dpr动态设置meta的缩放,root 的 font-size。

var dpr, rem, scale;
var docEl = document.documentElement;
var fontEl = document.createElement('style');
var metaEl = document.querySelector('meta[name="viewport"]');

// 获取dpr
dpr = window.devicePixelRatio || 1;
// 设置基准值  除以10是为了方便计算
rem = docEl.clientWidth * dpr / 10;
scale = 1 / dpr;


// 设置viewport,进行缩放,达到高清效果
metaEl.setAttribute('content', 'width=' + scale * docEl.clientWidth + ',initial-scale=' + scale + ',maximum-scale=' + scale + ', minimum-scale=' + scale + ',user-scalable=no');

// 设置data-dpr属性,留作的css hack之用
docEl.setAttribute('data-dpr', dpr);

// 动态写入样式
docEl.firstElementChild.appendChild(fontEl);
fontEl.innerHTML = 'html{font-size:' + rem + 'px!important;}';

// 给js调用的,某一dpr下rem和px之间的转换函数
window.rem2px = function(v) {
v = parseFloat(v);
return v * rem;
};

window.px2rem = function(v) {
    v = parseFloat(v);
    return v / rem;
};

window.dpr = dpr;
window.rem = rem;

css布局

//  上面js脚本设置的rem基准值, 也就是root font-size的值
rem = docEl.clientWidth * dpr / 10;

以iphone6的高清视觉稿 750×1334 为例

使用以上脚本计算出的rem基准值是75。

那么我们可以用写一个mixin

//sass
@mixin px2rem($name, $px){
    #{$name}: $px / 75 * 1rem;
}

//less
.px2rem(@name, @px){
    @{name}: @px / 75 * 1rem;
}

所以设计稿是750*300px的div
css可以这么写

// sass
@include px2rem('width',750);
@include px2rem('height',300);

// less
.px2rem(width, 750);
.px2rem(height, 300);

转化成的css

width: 10rem; // -> 750px
height: 4rem; // -> 300px

因为iphone6的dpr为2,页面scale了0.5,所以在手机屏幕上显示的真实宽高应该是375×150px。

字体大小

字体在不同的dpr上大小一致,比如在dpr为1的手机上显示16px;那么在dpr2的手机上显示32px;

所以我们可以封装一个px2px的mixin,使得在不同的dpr上显示字体大小一致。

//sass
@mixin px2px($name, $px){
    #{$name}: round($px / 2) * 1px;
    [data-dpr="2"] & {
        #{$name}: $px * 1px;
    }
    // for mx3
    [data-dpr="2.5"] & {
        #{$name}: round($px * 2.5 / 2) * 1px;
    }
    // for 小米note
    [data-dpr="2.75"] & {
        #{$name}: round($px * 2.75 / 2) * 1px;
    }
    [data-dpr="3"] & {
        #{$name}: round($px / 2 * 3) * 1px
    }
    // for 三星note4
    [data-dpr="4"] & {
        #{$name}: $px * 2px;
    }
}

// less
.px2px(@name, @px){
    @{name}: round(@px / 2) * 1px;
    [data-dpr="2"] & {
        @{name}: @px * 1px;
    }
    // for mx3
    [data-dpr="2.5"] & {
        @{name}: round(@px * 2.5 / 2) * 1px;
    }
    // for 小米note
    [data-dpr="2.75"] & {
        @{name}: round(@px * 2.75 / 2) * 1px;
    }
    [data-dpr="3"] & {
        @{name}: round(@px / 2 * 3) * 1px
    }
    // for 三星note4
    [data-dpr="4"] & {
        @{name}: @px * 2px;
    }
}

当然其他想要在不同dpr下保持一致的css属性也可以是用px2px

比如:

//sass
@include px2px('padding',20);
@include px2px('right',20);

//less
.px2px(padding, 20);
.px2px(right, 8);

以上。

参考链接移动端高清、多屏适配方案