移动端适配方案

适配

在不同尺寸的移动设备上, 页面相对性的达到合理的展示(自适应), 或者保持同一效果的等比缩放(看起来差不多)

适配的元素

  1. 字体
  2. 宽高
  3. 间距
  4. 图像(图标, 图片)

适配的方法

  1. 百分比适配
  2. viewport 缩放适配
  3. DPR 缩放适配
  4. rem 适配
  5. vw, wh 适配

百分比适配

360手机站
拉钩 H5 页面 顶部底部, 职位列表 都是高度定死, 宽度 100% 自适应
高度固定, 宽度百分比, 在高度不能定死的状况下, 不是很好用, 一般都是配合其他是适配使用
当元素为奇数或者某个元素占比不均匀的时候, 不是很好算

360顶部模拟

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="format-detection" content="telephone=no,email=no"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        body {
            font-family: helvetica;
            margin: 0;
        }
        body *{
            -webkit-user-select: none;
            -webkit-text-size-adjust: 100%;
            -webkit-text-size-adjust: 100%;
        }
        a, button, input {
            -webkit-tap-highlight-color: rgba(0,0,0,0)
        }
        button, input{
            -webkit-appearance: none;
            border-radius: 0;
        } 
        input::-webkit-input-placeholder{
            color: #000;
        }
        input:focus::-webkit-input-placeholder{
            color: #f00;
        }
        .header {
            width: 100%;
            height: 48px;
            background: #23ac38;
            display: flex;
            justify-content: space-between;
            align-items: center;
            box-sizing: border-box;
            padding: 0 10px;
        }
        .logo img{
            width: 80px;
        }
        .list img{
            width: 20px;
        }
    </style>
</head>
<body>
    <header class="header">
        <span class="logo"><img src="http://p2.qhmsg.com/t01ecc3b6b24e7bdbd8.png" alt=""></span>
        <span class="list"><img src="http://p9.qhmsg.com/t010fa93a99715aad32.png" alt=""></span>
    </header>
</body>
</html>

viewport 适配

把所有机型的css像素设置成一致的

  1. viewport 需要使用 js 动态设置, (不能直接把 device 的值设置为数值)
  2. 通过设置比例(初始比例以及缩放比例), 把宽度缩放成一致
    缩放比 = css 像素 / 375
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="format-detection" content="telephone=no,email=no" />
    <meta name="viewport"
        content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0" id="view">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        body {
            font-family: helvetica;
            margin: 0;
        }

        body * {
            -webkit-user-select: none;
            -webkit-text-size-adjust: 100%;
            -webkit-text-size-adjust: 100%;
        }
        a, button, input {
            -webkit-tap-highlight-color: rgba(0, 0, 0, 0)
        }
        button, input {
            -webkit-appearance: none;
            border-radius: 0;
        }

        input::-webkit-input-placeholder {
            color: #000;
        }
        input:focus::-webkit-input-placeholder {
            color: #f00;
        }
        div {
            width: 75px;
            height: 100px;
            float: left;
        }
        div:nth-child(1) {
            background: #f00;
        }
        div:nth-child(2) {
            background: #ff0;
        }
        div:nth-child(3) {
            background: #f0f;
        }
        div:nth-child(4) {
            background: #0ff;
        } 
        div:nth-child(5) {
            background: #0f0;
        }
    </style>
    <script>
        ; (function () {
            /* 获取 css 像素 (viewport没有缩放, initial-scale=1.0)*/
            var curWidth = document.documentElement.clientWidth
            var curWidth = window.innerWidth
            var curWidth = window.screen.width
            /* 以上三种方式都可以准确的获取到 html 的 width */
            var targetWidth = 375 // 目标值
            var scale = curWidth / targetWidth
            var meta = document.getElementById('view')
            var content = 'initial-scale=' + scale + ', user-scalable=no, maximum-scalable=' + scale + ', minimum-scalable=' + scale
            meta.content = content
        })()
    </script>
</head>
<body>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
</body>
</html>

但是这种肯定是有问题的
ipad 上, 宽度为 768, pro1024, 一张 375 的图片放上去 ...

DPR 适配

把 CSS 像素缩放成与设备像素一样大的尺寸
只有在 PC 端这两个值才是对应的 1css像素 = 1物理像素

iphone6 的物理像素 750 * 1334. 通过缩放, 将 CSS 像素的 375 * 667 缩放, 按照设计稿 750 切图.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="format-detection" content="telephone=no,email=no" />
    <meta name="viewport"
        content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0" id="view">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        body {
            font-family: helvetica;
            margin: 0;
        }

        body * {
            -webkit-user-select: none;
            -webkit-text-size-adjust: 100%;
            -webkit-text-size-adjust: 100%;
        }
        a, button, input {
            -webkit-tap-highlight-color: rgba(0, 0, 0, 0)
        }
        button, input {
            -webkit-appearance: none;
            border-radius: 0;
        }

        input::-webkit-input-placeholder {
            color: #000;
        }
        input:focus::-webkit-input-placeholder {
            color: #f00;
        }
        div {
            width: 20%;
            height: 100px;
            float: left;
        }
        div:nth-child(1) {
            background: #f00;
        }
        div:nth-child(2) {
            background: #ff0;
        }
        div:nth-child(3) {
            background: #f0f;
        }
        div:nth-child(4) {
            background: #0ff;
        } 
        div:nth-child(5) {
            background: #0f0;
        }
    </style>
    <script>
        ; (function () {
            /*
                要将 375 => 750 就是 375 / 0.5 dpr = 2
                375 / ? = 750  这个 ? 就是 dpr 的倒数
            */
            var meta = document.querySelector('meta[name="viewport"]')
            var scale = 1 / window.devicePixelRatio
            if(!meta){ // 没有默认设置 viewport 的 meta, 创建
                meta = document.createElement('meta')
                meta.name = 'viewport'
                meta.content = 'width=device-width, initial-scale=' + scale + ', user-scalable=no, maximum-scalable=' + scale + ', minimum-scalable=' + scale
                document.head.appendChild(meta)
            } else { // 有默认设置 viewport 的 meta, 修改  
                meta.setAttribute('content', 'width=device-width, initial-scale=' + scale + ', user-scalable=no, maximum-scalable=' + scale + ', minimum-scalable=' + scale)
            }
        })()
    </script>
</head>
<body>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
</body>
</html>

虽然一个CSS 像素对应了一个物理像素, 但是对于不同尺寸的设备, 依然不是一个好的解决方案, 需要 rem 进行配合

rem 适配

把所有的设备分成相同的若干份, 再计算元素宽度所占的份数

em: 当意义为 font-size 的时候, 1em 代表父元素的字体大小, 当作为其他单位(宽度高度)的时候, 代表自身字体大小
chrome 下有最小字体限制 12px, 字体小于 12px 无法再变小

rem: r root, 根元素的 font-sizehtmlfont-size 设置为 20px, 1rem = 20px

  1. 元素适配的宽度 = 元素所占的列数 * 一列的宽度
  2. 元素在设计稿里面的宽度
  3. 列数 (随便给的) 100
  4. 一列的宽度 = 屏幕宽度(css像素) / 列数
  5. 元素实际占的列数 = 设计稿里面的宽度 / 一列的宽度
var colWidth = 0
var col = 100   // 假设 100 列
// 计算 iPhone5 iPhone6里面一列的宽度
colWidth = 375 / col // 3.75px 6
colWidth = 320 / col // 3.20px 5

// 一个 div 占 10 列
var width = 10 * 3.75 // 37.5px 6
var width = 10 * 3.20 // 32.0px 5

// 一个 div 50px 
divCol = 50 / 3.75 // 13.333
divCol = 50 / 3.20 // 15.625

// 所以按照此方案, 50px 的 div, 在不同的设备里, 会占到不同的份数

屏幕已经被分成了若干份
那么 width: 10rem; (写入 CSS 文件的代码)
元素适配的宽度 => 元素所占的列数 * 一列的宽
元素适配的宽度 => width 10 * 1rem
元素所占的列数 => 10
一列的宽 => 1rem

1rem = htmlfont-size

根据栅格系统的原理, 将屏幕分成若干份, 不同的屏幕被分成相同的份数, 所以一份的宽度会不一样
但是份数是一样的, 所以整体比例是一样的

 ; (function (fs) {
            var html = document.documentElement
            var width = html.clientWidth    // css 像素
            html.style.fontSize = width / fs + 'px'
            // 分成 16 列, iPhone5 为例 320px 得到 1rem = 20px
         })(16)

这种方式并没有一个基准点, 只是按照屏幕的增大, htmlfont-size 在变大
我们希望增加一个基准点, 这个点就是 iPhone6, 屏幕仍然分成 16
如果屏幕尺寸为 375, 那么就 html 字体也是16px
如果屏幕小于 375font-size 也会小于 16px
反之, 大于 16px
如下代码:

; (function (doc, win, designWidth) {
    const html = doc.documentElement
    const refreshDom = () => {
        const clientWidth = html.clientWidth
        if(clientWidth >= designWidth){
            html.style.fontSize = "100px"
        }else{
            html.style.fontSize = 16 * clientWidth / 375 + 'px'
        }
    }
    doc.addEventListener('DOMContentLoaded', refreshDom)
})(document, window, 750)

但是这样做有一个弊端, 当计算出了 rem 值之后, 还要除以 dpr, 才是真正应该写的值
而且很多是除不尽的, 跟着一大串小数, 虽然有 less sass 去做这些事情, 但是还是觉得美中不足

rem 最终方案

; (function (doc, win, designWidth) {
    const html = doc.documentElement
    const refreshDom = () => {
        const clientWidth = html.clientWidth
        if(clientWidth >= designWidth){
            html.style.fontSize = "100px"
        }else{
            html.style.fontSize = 100 * (clientWidth / designWidth) + 'px'
        }
    }
    doc.addEventListener('DOMContentLoaded', refreshDom)
})(document, window, 750)

只有一行代码不同
那么html.style.fontSize = 100 * (clientWidth / designWidth) + 'px' 又是什么意思呢 ?
基于 iPhone6 的尺寸, 将整个页面适配成7.5 rem.
所以, 只要设计稿是750px, 在切图的时候, 量出来出多少px, 直接将这个值除以 100, 加上remok
这个时候, 屏幕被分割成多少份这个概念已经不怎么明朗了, 说是7.5份吗? 好像不太对劲. 那就索性不管了.

如果设计图是 640px, 那么整屏的宽度就是 6.4rem, 只需要将参数修改一下就可以了.
但是上面的代码是用到了 ES6 的语法, ios9 不是原生支持的, 直接在上面跑会出现很大的问题, 就相当于适配没有做一样.
所以改写吧:

; (function (doc, win, designWidth) {
           var html = doc.documentElement
           function refreshDom(){
               var clientWidth = html.clientWidth
               if(clientWidth >= designWidth){
                   html.style.fontSize = "100px"
               }else{
                   html.style.fontSize = 100 * (clientWidth / designWidth) + 'px'
               }
           }
           doc.addEventListener('DOMContentLoaded', refreshDom)
         })(document, window, 750)

这种方案总体来说没什么大问题了. 如果在设计稿上量出来长度为50px, 那么实际写上去的长度应该是 50 / 2 = 25px. 因为 750 的设计稿, 375px就会占满屏幕. 或者是写.5rem.

字体也要用标注的字体大小除以 2, 如标注 28px, 实则应该写 14px, 或者 .28rem

hotcss

大佬写的号称移动端终极解决方案, 原理跟如上一样, 只是考虑的东西更多, 功能更强大.
github 上下载或者 clone , 地址是: https://github.com/imochen/hotcss
需要做一点点小小的处理, 将第十四行左右的 maxWidth = 540, 修改为设计稿的尺寸, 这个 540 是几年前写的, 相较现在来说有点小了.

然后在页面中引入这个 hotcss.js. 使用 sass.
在写 scss 的地方引入 px2remscss 文件, 然后就可以在scss上愉快的写代码了.

量出来多少 (100px) 就直接 px2rem(100)

vw, vh 适配

这俩单位就是为适配而生的

vw: viewport's width 1vw = 视口宽度的 1%
vh:viewport's height 1vh = 视口高度的 1%
vmax: 取 vw, vh 中的最大值
vmin: 取 vw, vh 中的最小值

支持情况: ios >= 8; android >= 4.4

浏览器将任何屏幕都分成 100 份, 只要使用此单位, 就不会翻车

两种方案

方案一: 通篇使用vw

@function vw($px){
    @return $px / 750 * 100vw;
}

为方便切图, 写了一个如上函数(scss), 以 750 为基准, 在任何屏幕上都可以看做是'750'
因为例如此函数传 250 进去, 会被转换成 33.33333vw, 三个这样的大小将占满屏幕宽度, 在任何尺寸屏幕上的都是这样.

方案二: 通过 vw 设置根节点的字体大小, 页面尺寸依然使用 rem

一个很暴力的方式, 还是以 750 为基准. 375(css像素) / 750(设计稿宽度) * 100 这是 rem 适配的原理所在
这个值最终是 50, 这个值就是 rem 适配中, html 的字体大小 .
那通过 vw 适配, 要达到 rem 同样的适配效果, 是不是应该让 ?vw = 50px ?
所以 50 / 3.75 = 13.333333333333334 (15位小数)
然后:

html {
    font-size: 13.333333333333334vw;
}

然后, rem 适配需要的 js 代码可以删除了. 然后在任意尺寸的屏幕上也是可以横着走的.
整个屏幕 7.5rem. 与 rem 适配的结果都是一样的
需要注意一些问题:

  1. css 尺寸写 rem, 比如 1/4 屏幕大小的 div, width: 1.875rem;
  2. htmlfont-size 会带来负面影响. 很多有文本属性(受font-size影响)的标签里面的内容会继承 html 的字体大小, 13.33vw 字体其实是非常大的, 所以不处理的话, 页面将会乱套. imgdisplay属性默认是 inline-block, 它会根据字体来对齐. 如果字体继承了 html. 图片就会跑到一个匪夷所思的地方. 这个时候需要将img的包裹层的 font-size 做出相应的调整
  3. 比较一劳永逸的方法, 在该设置的字体的地方设置字体大小, 然后最主要的是, 在body里面, 将 font-size 设置为chrome最小字体 12px, 阻止页面的元素去继承html的字体大小. 即现在 html 的字体大小只能影响 rem 这个单位.

这些问题逐一解决之后, 这也是一个很好的方案.

完...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345