从设计图到CSS:rem+viewport+媒体查询+Sass

根据UI图对移动端的h5页面做样式重构,是前端工程师的本职工作,看似简单,不过想做好却并不容易。下面总结一下其中要点。

rem

rem是一种相对长度单位,参考的基准是<html>标签定义的font-size。比如:

html {
    font-size: 16px;
}
.intro {
    font-size: 1.2rem;
    margin-top: 0.2rem;
}

那么实际效果就是:

.intro {
    font-size: 16px * 1.2rem = 19.2px;
    margin-top: 16px * 0.2rem = 3.2px;
}

viewport

做移动端的h5,通常会在HTML文件中指定一个<meta>标签:

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximun-scale=1.0, user-scalable=no">

关于Viewport,可以参考更专业的教程:

http://www.cnblogs.com/chengkun101/p/5461500.html

http://www.cnblogs.com/2050/p/3877280.html


媒体查询

手机屏幕的分辨率越来越高,比如iPhone 5为640*1136 px、iPhone 6/6S为750*1334 px。我拿到的UI图,其参考分辨率为1440*2560 px

而在写CSS时,通常是以屏幕宽度为参考的。打开Chrome的响应式设计工具,就可以看到各种设备的屏幕宽度,比如iPhone 5的屏幕宽度是320*568 px,iPhone 6/6S的屏幕宽度为375*667 px。分辨率和屏幕宽度之间,是有一个倍屏系数换算的。

设计师给了一张宽为1440px的UI图,而做不同设备的适配,就是前端工程师的职责了。

比如UI图上标注了某个段落的字体大小为64px。为了保证在各种屏幕上的不失真,就要根据实际屏幕宽度做等比例换算,才能写进CSS,即满足:

写入CSS的尺寸/屏幕宽度 = UI图标注的尺寸/UI图宽度

因此:

写入CSS的尺寸 = UI图标注的尺寸/UI图宽度*屏幕宽度

比如上例,当标注尺寸为64px、UI图宽度为1440px时,针对不同屏幕宽度,写入CSS的属性分别为:

  • 屏幕宽度320px:font-size: 64/1440*320 = 14.22px
  • 屏幕宽度360px:font-size: 64/1440*360 = 16px
  • 屏幕宽度375px:font-size: 64/1440*375 = 16.67px
  • ...
  • 屏幕宽度1440px:font-size: 64/1440*1440 = 64px

于是自然想到媒体查询:

@media (min-width: 320px) {
    p.intro {
        font-size: 14.22px;
    }
}

@media (min-width: 360px) {
    p.intro {
        font-size: 16px;
    }
}

@media (min-width: 375px) {
    p.intro {
        font-size: 16.67px;
    }
}

@media (min-width: 1440px) {
    p.intro {
        font-size: 64px;
    }
}

天啊,如果这样写,如果UI图上标注了另一个段落的字体大小为60px,岂不又要写一遍媒体查询!

解决方法是,在<html>标签上只做一次媒体查询,而在p.intro上使用rem单位。

// 对html做媒体查询,定义基准的font-size
@media (min-width: 320px) {
    html {
        font-size: 14.22px;
    }
}

@media (min-width: 360px) {
    html {
        font-size: 16px;
    }
}

@media (min-width: 375px) {
    html {
        font-size: 16.67px;
    }
}

@media (min-width: 1440px) {
    html {
        font-size: 64px;
    }
}

// CSS单位使用rem
p.intro {
    font-size: 1rem;
}

这样一来,其他元素也可以共享<html>的基准font-size,无需媒体查询,只需引入rem单位,如:

// 另一个段落
p.another-intro {
    font-size: 0.9375rem;  // UI图上标注60px
    height: 1.5625rem;  // UI图上标注100px
    margin: 0.5rem;  // UI图上标注32px
}

有点匪夷所思?先来看看如上换算是否满足等比例性。再回顾一下公式:

写入CSS的尺寸/屏幕宽度 = UI图标注的尺寸/UI图宽度

p.another-intromargin属性为例:

  • 屏幕宽度320px:margin: 14.22*0.5 = 7.11px,而7.11/320 = 32/1440
  • 屏幕宽度360px:margin: 16*0.5 = 8px,而8/360 = 32/1440
  • 屏幕宽度375px:margin: 16.67*0.5 = 8.335px,而8.335/375 = 32/1440
  • ...
  • 屏幕宽度1440px:margin: 64*0.5 = 32px,而32/1440 = 32/1440

完美。

可以发现一个规律:在媒体查询中,min-width: 1440px时定义了html { font-size: 64px; },那么UI图上标注的尺寸,直接除以64,就可以带个rem尾巴,写入到CSS中。记住64这个参考值,非常有用,下文会经常用到。

当然,你也可以不使用64作为参考值,只要符合等比例规则就可以。下文均是以64为例的。


适配所有规格

以上只以320px、360px、375px为例,得到了<html>的基准font-size。对于iPhone 6s Plus,其屏幕宽度为414px,如何计算font-size

很简单:屏幕宽度/UI图宽度*64

于是,又多了一条媒体查询规则:

@media (min-width: 414px) {
    html {
        font-size: 18.4px;  // 414/1440*64
    }
}

查阅常见移动设备的屏幕尺寸规格,可以得到一个较为完整的媒体查询规则:

html {
    font-size: 12px;
}
@media (min-width:320px){
  html{
    font-size: 14.2222px;
  }
}
@media (min-width:360px){
  html{
    font-size: 16px;
  }
}
@media (min-width:375px){
  html{
    font-size: 16.6667px;
  }
}
@media (min-width:412px){
  html{
    font-size: 18.2857px;
  }
}
@media (min-width:480px){
  html{
    font-size: 21.3333px;
  }
}
@media (min-width:640px){
  html{
    font-size: 28.4444px;
  }
}
@media (min-width:720px){
  html{
    font-size: 32px;
  }
}
@media (min-width:768px){
  html{
    font-size: 34.1333px;
  }
}
@media (min-width:1440px){
  html{
    font-size: 64px;
  }
}

CSS预编译

之前所讲的内容,已经可以使你做出移动端适配的h5页面了,但并不优雅。因为CSS是不支持计算的,“标注尺寸/64”只能手工计算,也就意味着UI图上的每个标注都要手动换算,你会疯掉。

不过,借助CSS预编译工具,如Less/Sass,可以避免这种重复劳动。以下以Scss为例,比如:

p.intro {
    font-size: 64px/64px * 1rem;
}

p.another-intro {
    font-size: 60px/64px * 1rem;  // UI图上标注60px
    height: 100px/64px * 1rem;  // UI图上标注100px
    margin: 32px/64px * 1rem;  // UI图上标注32px
}

当然也可以使用变量:

// 计算rem的基准字体
$rem-base-font-size: 64px;

p.intro {
    font-size: 64px/$rem-base-font-size * 1rem;
}

p.another-intro {
    font-size: 60px/$rem-base-font-size * 1rem;  // UI图上标注60px
    height: 100px/$rem-base-font-size * 1rem;  // UI图上标注100px
    margin: 32px/$rem-base-font-size * 1rem;  // UI图上标注32px
}

这样做的好处是,如果某一天不使用64px作为参考值,只需修改$rem-base-font-size变量。

以上代码看着很冗余,不如写一个函数,隐藏计算过程:

// 将设计图标注的px尺寸,转换为相应的CSS
@function convertPxFormUI($px) {
    $rem-base-font-size: 64px;
    @return $px / $rem-base-font-size * 1rem;
}

p.intro {
    font-size: convertPxFormUI(64px);
}

p.another-intro {
    font-size: convertPxFormUI(60px);  // UI图上标注60px
    height: convertPxFormUI(100px);  // UI图上标注100px
    margin: convertPxFormUI(32px);  // UI图上标注32px
}

这样做的好处是,如果某一天摒弃了rem,使用了其他换算规则,只需修改convertPxFormUI()函数体即可。


更进一步

既然Sass帮助我们计算,那么媒体查询中的一堆font-size,Sass是否可以代劳呢?

之前介绍了计算规则:屏幕宽度/UI图宽度*64。OK,看来可以这样写:

// 计算rem的基准字体
$rem-base-font-size: 64px;

// UI设计图的分辨率宽度
$UI-resolution-width: 1440px;

@mixin html-font-size() {
    @media (min-width:320px){
      html{
        font-size: 320px / $UI-resolution-width * $rem-base-font-size;
      }
    }
    @media (min-width:360px){
      html{
        font-size: 360px / $UI-resolution-width * $rem-base-font-size;
      }
    }
    @media (min-width:375px){
      html{
        font-size: 375px / $UI-resolution-width * $rem-base-font-size;
      }
    }
    @media (min-width:412px){
      html{
        font-size: 412px / $UI-resolution-width * $rem-base-font-size;
      }
    }
    @media (min-width:480px){
      html{
        font-size: 480px / $UI-resolution-width * $rem-base-font-size;
      }
    }
    @media (min-width:640px){
      html{
        font-size: 640px / $UI-resolution-width * $rem-base-font-size;
      }
    }
    @media (min-width:720px){
      html{
        font-size: 720px / $UI-resolution-width * $rem-base-font-size;
      }
    }
    @media (min-width:768px){
      html{
        font-size: 768px / $UI-resolution-width * $rem-base-font-size;
      }
    }
    @media (min-width:1440px){
      html{
        font-size: 1440px / $UI-resolution-width * $rem-base-font-size;
      }
    }
}

很冗余,用循环来实现:

// 计算rem的基准字体
$rem-base-font-size: 64px;

// UI设计图的分辨率宽度
$UI-resolution-width: 1440px;

// 需要适配的屏幕宽度
$device-widths: 320px, 360px, 375px, 412px, 480px, 640px, 720px, 768px, 1440px;

@mixin html-font-size() {
    @each $current-width in $device-widths {
        @media (min-width: $current-width) {
            html {
                $x: $UI-resolution-width / $current-width;  // 计算出是几倍屏
                font-size: $rem-base-font-size / $x;
            }
        }
    }
}

这样做的好处是,如果某一天老板说,屏幕宽度450px时效果不理想。你只需在$device-widths中新增一个宽度即可。


Sass最终方案

  • _variables.scss:定义变量
// 计算rem的基准字体
$rem-base-font-size: 64px;

// UI设计图的分辨率宽度
$UI-resolution-width: 1440px;

// 需要适配的屏幕宽度
$device-widths: 240px, 320px, 360px, 375px, 412px, 480px, 540px, 640px, 720px, 768px, 1024px, 1440px;
  • _utils.scss:定义函数与mixin
// 根据不同设备的屏幕宽度,定义<html>的基准font-size
@mixin html-font-size() {
    @each $current-width in $device-widths {
        @media (min-width: $current-width) {
            html {
                $x: $UI-resolution-width / $current-width;  // 计算出是几倍屏
                font-size: $rem-base-font-size / $x;
            }
        }
    }
}

// 将设计图标注的px尺寸,转换为相应的CSS
@function convertPxFormUI($px) {
    @return $px / $rem-base-font-size * 1rem;
}
  • index.scss:Scss入口文件
@import 'utils', 'variables';

@include html-font-size();

p.intro {
    font-size: convertPxFormUI(64px);
}

p.another-intro {
    font-size: convertPxFormUI(60px);
    height: convertPxFormUI(100px);
    margin: convertPxFormUI(32px);
}

总结

rem + viewport + 媒体查询 + Sass

posted @ 2017-05-20 20:20  高-岩  阅读(3618)  评论(1编辑  收藏  举报