zhiqiang21/blog

根据iPhone6设计稿动态计算rem值

zhiqiang21 opened this issue · 6 comments

2019-05-14更新

1.为什么设置font-size=62.5%

简单说下为什么要来更新这篇博客的原因吧。这篇博客计算rem的方式是自己2014年刚毕业的时候做移动端项目写的。本身这段代码没有什么问题,但是在本地开发调试(比如chrome上) 因为设置 html 根元素的font-size=62.5% 导致在chrome上使用了rem单位的容器计算出来的正常的尺寸会有偏差(实际上在移动端设备上显示的又没有问题)。

这个问题的主要原因就是在pc上默认浏览器的字体最小只能显示 12px大小的字体**(当然这个说法也是指的大多数浏览器)。所以当使用这篇博客脚本的来做rem 的动态计算的时,如果元素的尺寸小于12px,在chrome上调试就会看到一个错误的容器尺寸。当时的一个简单的解决办法就是自己手动到chrome设置里面去设置chrome的可以显示的最小字体。**

先来看下在chrome 浏览器里面的测试效果: 第一张和第二张图分别是浏览器计算出的容器的宽度和高度,可以明显发现test1 的宽度多了2像素

同样的代码在safari中浏览器却可以计算出正确的大小,下图可以看到test1和test2的宽度和高度是一样的。

就在我修改这篇文章的时候,chome已经是v74.0xxxx版本。通过设置chrome的最小显示字体已经无法让刚才的测试demo正常显示出计算结果了。意思就是说当我们设置跟节点的html font-size 62.5% 时(根据 1rem=16px, 0.625rem = 10px),计算得到的是10px,chrome也会强制的将计算结果设置为 12px。所以在浏览器中就是 1rem=12px了。

下面再来简单介绍下为什么设置 font-size 62.5% 和怎么规避上面在chrome浏览器中计算误差的问题。

rem px
1 16
0.625 10
6.25 100

由上面的表格可以知道当我们假设1rem = 16px时,那么10px=0.625rem,所以当我们选择10px为基准值的时候,假设我们需要设置的rem 值为 Y,设计稿的尺寸为 X,我们可以得出一个方程式:

10 * Y = 0.625 * X

也就是实际的rem设置的值就是

Y = (0.625 * X) / 10

又因为 rem 是一个相对于 html 跟节点 font-size 的一个相对值。当我们设置了根节点的 html 的font-size 62.5%时,需要设置的rem的值就是

Y = X / 10 (这里是一倍稿,当是2倍稿时,Y=X/10*2)

由以上的解析我们已经知道为什么设置font-size 62.5% 的由来了,而且存在的问题。那么怎么规避在chrome上会计算错误的问题呢?

规避这个问题的方式就是我们将基准值设置为大于chrome能够显示的最小字体 12px。为了方便计算,如上面的表格我们可以使用100px作为基准值。也就是设置根节点的font-size 625%。所以当我们根据设计稿尺寸计算rem尺寸的时候直接除100(如果是2倍稿就除2*100)。

下图可以看出来这种选择更大的基准值时,chrome浏览器可以正确的计算出元素的尺寸。

2.淘宝flexible方案解析

以上设置根节点font-size为百分比单位是一种使用rem适配的方案。还有一种设置font-size为动态的像素值(px),比如淘宝的 lib-flexible。那么简单解释下淘宝方案的换算方法。

我们都知道iphone6/7/8手机的屏幕宽度是375px。当我们的设计稿是以iphone6为标准输出时(假设是1倍稿),在lib-flexible中有一句代码注释如下:

它的意思就是 lib-flexible 认为 1rem = viewwidth/10,那么就有了下面的一个关系:

以一倍的iphone6设计稿为标准:

rem px
1 37.5

当我们设置 37.5px 为基准值,也就是 font-size 37.5px 时,由前面介绍的换算公式当我们要设置rem值时,就是

Y = X / 37.5 (浏览器在计算的时候,实际是 37.5 * x / 37.5)

2.1 lib-flexible 对0.5像素的支持

查看lib-flexible的源码可以看到这段js代码

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)
  }

上面代码的意思就是检测屏幕的 dpr 是不是大于等于2,当满足条件是就在 html 根节点上添加 class="hairlines" 的属性,当我们要使用 0.5像素的时候可以这么写代码

.test1 {
            width: 100px;
            height: 100px;
            border: 1px solid red;
        }

        .test2 {
            width: 100px;
            height: 100px;
            border: 1px solid goldenrod;
        }
        /* 当满足dpr>=2时这段代码会生效,否者不会生效 */
        .hairlines .test2 {
            border: .5px solid goldenrod;
        }

建议使用淘宝的rem 方案解决移动端适配的问题。地址:https://github.com/amfe/lib-flexible

3.字体的适配

使用 rem 我们可以控制元素在不同设备上面等比例的缩放和扩大,根据设备的dpr可以实现真实的一像素。那么字体的大小使用什么适配呢?

3.1 需不需要适配

在我的工作中(移动端项目)很少需要对字体进行适配。主要也是产品或者是UE、UI都对这里没有需求。直接使用的设计稿的 px尺寸。

3.2 需要适配怎么做

如果是需要对字体进行适配我们应该怎么做呢?

  • 第一、就是像元素的宽高一样使用rem
  • 第二、根据设备的dpr适配字体大小(可以使用media query来动态的设置字体的大小);

使用rem会有点儿小问题,在安卓手机上dpr不统一,以及宽度的不统一,上面的两种方法都是尽可能的接近适配需要的大小。

3.3衬线字体和非衬线字体的区别

这里介绍个前端关于字体使用最经常使用的两个概念。

**衬线体:**具有装饰性(有边角);代表字体:Times New Roman。常用于印刷品(书本杂志等),适用于长篇文章段落,因为边角易于辨别每个字母,读者在阅读较多的段落时会变得轻松。

**非衬线体:**顾名思义就是无装饰性(无边角),易识别;代表字体:Helvetica (iOS7、iOS8的预设字体)、San Francisco(iOS9、iOS10的预设字体)、Roboto(Android L的预设字体)、Arial(windows的预设字体);缺点:某些字母相对难区分,如大些的I(i)与小写的l(L)。常用语电子设备。

旧版的博客内容(脚本内容已经更新20190514)

rem 单位在做移动端的h5开发的时候是最经常使用的单位。为解决自适应的问题,我们需要动态的给文档的更节点添加font-size 值。使用mediaquery 可以解决这个问题,但是每一个文件都引用一大串的font-size 值很繁琐,而且值也不能达到连续的效果。

就使用js动态计算给文档的fopnt-size 动态赋值解决问题。

使用的时候,请将下面的代码放到页面的顶部(head标签内);

/**
 * [以iPhone6的设计稿为例js动态设置文档 rem 值]  
 * px和 rem的换算方式是 设计稿尺寸除100 如果是2倍稿 则是设计稿尺寸 除 2*100=200 3倍稿尺寸可以类推。
 * @param  {[type]} currClientWidth [当前客户端的宽度]
 * @param  {[type]} fontValue [计算后的 fontvalue值]
 * @return {[type]}     [description]
 */
<script>
    var currClientWidth, fontValue,originWidth;
    //originWidth用来设置设计稿原型的屏幕宽度(这里是以 Iphone 6为原型的设计稿)
    originWidth=375;
    __resize();

	//注册 resize事件
    window.addEventListener('resize', __resize, false);

    function __resize() {
        currClientWidth = document.documentElement.clientWidth;
        //这里是设置屏幕的最大和最小值时候给一个默认值
        if (currClientWidth > 640) currClientWidth = 640;
        if (currClientWidth < 320) currClientWidth = 320;
        //
        fontValue = ((625 * currClientWidth) /originWidth).toFixed(2);
        document.documentElement.style.fontSize = fontValue + '%';
    }
    </script>

请问你这个不会出现进入页面先缩小后放大的情况吗?我的代码跟你的类似,但是我的就会出现页面先缩小后放大的情况,不知道应该怎么解决。

jawil commented

现在用css单位vw就行,不需要写js了。@ChaseChan

@ChaseChan 哈哈,你把这段js放在Head标签里面就可以了。另外现在还是推荐的淘宝的lib-flexible 吧。https://github.com/amfe/lib-flexible

@zhiqiang21 ,我就是放在head里面的,现在比较笨的方法是计算过后再把body给展示出来。

@jawil 公司不给用,那个貌似是安卓4.4.4以上才支持,公司还要兼容老设备,唉。

不用这样的哈,检查下你的代码看不是不是Head标签还有其它的js代码,阻塞了这段代码的执行。如果有请把这段代码移到最前面的位置,或者考虑把跟业务有关的js都移到body标签的底部