利用CSS3单位支持高低密度屏幕

简介

设计在不同屏幕分辨率下表现相同的应用程序一直以来都是很痛苦的事。 起初的屏幕大多是800x600分辨率。 这成为了一个标准,所有人都在设计支持它们的网页及应用。 当更高分辨率的屏幕出现之后,这导致了屏幕上大片的空间被浪费,但是设计者们根据现实情况很快做出了调整。 不幸的是,当智能手机出现以后一切变得更糟。 我们回过头去,不得不再一次考虑我们的应用程序可能显示在不同分辨率显示器上。

这只是问题的一方面。 通过给一些常见的屏幕分辨率提供不同的布局就可能可以解决这个问题。 第二个大问题是像素显示密度(PPI - 像素/点每英寸),它指的是屏幕上每英寸可以显示多少个像素。 我们习惯于100左右的PPI显示级别。 这个数值表示书写16像素字体大小的文字大约需要0.16英寸(4毫米)高,这样文字才足够便于阅读。 屏幕的PPI级别已经提高到了接近300左右的水平。 研究表明这个数值主要受到人眼的限制 - 大多数人无法裸眼识别比1/300英寸更小的点。

然而对于开发人员而言这究竟意味着什么:“像素密度无关的应用程序”?16像素字体书写的文字在100PPI的屏幕上清晰显示,有4毫米高,但是在300PPI的屏幕上它将只有1.4毫米。 这肯定是太小了。 开发人员必须想出解决办法以保证每块屏幕上能有相同的阅读体验。 幸运的是,我们给你准备个一个很好的解决方案。 我将在本文中进行描述。

示例程序在Tizen SDK 2.1.0上进行过测试

全新CSS3单位

在CSS的第三个版本中我们有很多方便使用于移动应用程序中的新的单位。 本文仅着重介绍它们中的五个:rem,vw,vh,vmin和vmax。 还有其他一些单位,但他们并不常用于构建可扩展用户界面。

Rem单位

Rem实际上是与字体大小值有关的em单位的加强版。 根据W3C规范的定义:

“em unit is equal to the computed value of ‘font-size’ property of the element on which it is used.”

例如,如果某个元素将字体大小设置为16px,那么1em就等同于16px,2em就等同于32px以此类推。 现在来看rem单位的定义:

“rem unit is equal to the computed value of ‘font-size’ on the root element.”

1em像素值在文档中不同位置会因为元素的字体大小设置不同而各不相同,然而1rem在文档中各个位置均相同。 换言之,如果我们取出两个em数值,在确定元素的字体大小设置前我们无法比较他们的大小。 如果使用rem单位就不会有这个问题。 2rem总是比1rem大一倍。 下面的图片说明了rem和em单位的区别。

em单位和rem单位对比(em - 左侧,rem - 右侧)

图 1. em单位和rem单位对比(em - 左侧,rem - 右侧)

所谓的根元素是指文档中的第一个元素,即html标签,而不是主体。 请记住这一点不要混淆,因为你可能还不习惯于在html标签中设置字体大小。

Vw, vh, vmin, vmax单位

Vw和vh在可扩展用户界面中非常有用。 可惜它们没有被浏览器很好的支持,尤其在移动设备上 (http://caniuse.com/viewport-units)。 不过在Tizen的WebKit引擎中包含了它们,你可以在你的应用程序中使用它们。 我会在后面的段落中告诉你如何使用它们。

Vw和vh代表相应的视窗宽度和高度百分比。 1vw(vh)等于1%视窗宽度(高度)。 当我们的应用程序必须要缩放到屏幕宽度或高度时,这个单位是最好的选择。

vw/vh和%单位之间的区别类似于rem和em单位之间的区别。 %单位与父元素大小有关,而vw/vh与根元素大小即视窗大小有关。 当我们针对桌面浏览器进行开发时,视窗是指浏览器的内容区,而不是整个窗口或屏幕。

另外有用的单位是vmin和vmax,表示视窗/内容区较长或较短的边的百分比。

利用em和百分比单位设计UI

在开发应用程序时,我们希望能够尽可能多的支持不同的平台,所以我决定将重点放在em和%单位。 它们长时间来都被所有的浏览器所支持。 如果你的目标平台是Tizen而不想去支持比较老的移动或桌面浏览器,那么你可以使用vw和vh单位。 下面的例子中我将演示如何设计一个在不同像素密度显示器上看上去都没有差别的简单的游戏菜单。 该菜单将显示为竖排模式(目前我们没有编写代码来支持不同的屏幕方向,这将在后续的文章中讨论)。 下图所示是应用程序的截图。

应用程序截图

图 2. 应用程序截图

计算字体大小

以下部分描述的是计算应用程序初始字体大小的计算过程。 如果你不想计算整个数学过程或不关心它是如何计算的,你可以跳过这个部分。 当然,如果你是个严谨的设计者我建议你阅读该部分。

最开始我们必须要确定能够保证最佳阅读体验的文字字体大小。 要做到这一点,你需要做一些计算。 首先,确定最可读的字体高度(以毫米为单位)。 想要在不同的屏幕上显示不同的字体大小,我建议字母的高度在2至3毫米之间就足够了。 最好不要使用小于2毫米的字体大小。

然后,计算不同像素密度的屏幕上的字体大小(以像素为单位)。 最常见的移动设备屏幕像素密度在200至300PPI之间。 你可以根据屏幕尺寸的像素值除以英寸值来决定,或者可以直接参考一些设备的规范。

接下来,用字体大小(以英寸为单位)乘以屏幕的DPI。 当然,在此之前我们要将它的单位从毫米换算为英寸。 现在我们就得到了最佳字体大小的范围。 然后取平均值并四舍五入,得到最终数值为25px。 下表列举了所有计算过程。

字体高度(毫米) 字体高度(英寸) PPI 字体大小/高度(像素)
  = 字体高度 (毫米) / 10 / 2,54   = 字体高度 (英寸) * PPI
2 0,0787 200 15,7480
3 0,1181 200 23,6220
2 0,0787 300 23,6220
3 0,1181 300 35,4331
平均值 (像素): 24,6063 ≈ 25

使用计算出的字体大小

我们得到的字体大小究竟代表什么?它表示如果设备的PPI在我们的支持范围内,那么将保证在设备上显示的实际字体高度不会小于2毫米。 然而工作并没由结束。

我们必须考虑我们的应用程序会以我们刚才计算得到的25px字体大小为基准,运行在不同的设备上。 比如配备HD屏幕(1280x720px)的智能手机。 屏幕的像素密度大约在300PPI左右,如果文字以25px的字体书写那么字体高度将会在2至3毫米这个完美高度之间。 但如果是WVGA屏幕(840x480px - 200PPI左右)或者PPI更小的屏幕呢?在一块200PPI的屏幕上,相同的文字将会有1.5倍大,而在100PPI的屏幕上将会是3倍大。 可以通过在PPI较小的屏幕上减小字体大小解决这个问题。

要做到这一点,我们需要获得运行应用程序的屏幕的PPI数值。 如何获取这个值呢?有几种方法可以估算它,但都不完美并且不能跨平台兼容。 我的建议是基于屏幕的宽度和高度来解决这个问题,而非PPI。 两个4英寸的屏幕,一个为200PPI另一个为300PPI,它们的分辨率会有不同。 PPI数值较小屏幕分辨率也会更小。 这是我们可以利用的属性。

这大概是什么样呢?首先,我们需要明确我们的主要平台。 假设我们为300PPI的HD(1280x720px)屏幕设计应用程序。 对于任何较小的屏幕分辨率,我们都计算出一个缩放因子,用来减少字体大小。 如果分辨率小一半,那么缩放因子等于0.5。 用这个值乘以25px字体大小将得到12.5px,也就是我们所需的数值。 还有一个问题:我们应该用屏幕的哪个边来进行计算?答案是高度。 为什么呢?它与我们的应用程序所使用的纵向模式有关。 我们还希望在桌面浏览器上以横向模式运行我们的应用程序,并且我们希望应用程序缩放到浏览器内容区的高度,而非宽度。 按宽度缩放可能导致应用程序溢出到浏览器内容区以外。 下图所示为两者的区别。

缩放到浏览器的宽度

图 3. 缩放到浏览器的宽度

缩放到浏览器的宽度

图 4. 缩放到浏览器的高度

编写代码

首先,我们必须在头部声明基准视窗分辨率。 正如我所提到的,基准视窗大小为1280x720px,我们可以在meta标签中对它进行声明。

<meta name="viewport" content="user-scalable=no, width=720, height=1280">

对于桌面浏览器,我们必须把应用程序的HTML代码放在需要根据浏览器内容区高度进行缩放的容器内。 我们之所以要这样做是因为屏幕的方向不同。 即使我们在桌面浏览器(横向模式)运行我们的应用程序,我们仍然希望保留竖向模式。

简单起见,UI结构只有很少的几行HTML代码。

应用程序显示的菜单与像素密度无关,它在WVGA屏幕,HD屏幕和桌面浏览器上看上去一样。

<div id="container">
    <h1>Scalable Menu</h1>
    <img src="images/tizen.png" />
    <ul>
        <li>Start</li>
        <li>Settings</li>
        <li>About</li>
        <li>Exit</li>
    </ul>
    <p> Application displays pixel density independent menu that looks the same on WVGA screens, HD screens and in desktop browsers.</p>
</div>

最重要的操作是缩放容器和字体大小。 我们在calculateFontSize函数中实现这个功能。 我们在窗口大小调整和加载事件监听器里调用它。

// Initial sizes.
var initialFontSize = 25,
    initialWidth    = 720,
    initialHeight   = 1280,
    calculateFontSize;
 
calculateFontSize = function () {
    var currentHeight,
        scaleFactor,
        fontSize,
        scaledWidth,
        scaledHeight,
        container;
        
    // Get current client/screen height.
    currentHeight = window.innerHeight;
 
    // Calculate scale factor and scaled font size.
    scaleFactor = currentHeight / initialHeight;
    fontSize    = initialFontSize * scaleFactor;
    
    // Calculate scaled container size.
    scaledHeight = currentHeight;
    scaledWidth  = initialWidth * scaleFactor;
    
    // Set scaled container and font size.
    container = document.getElementById('container');
    container.style.width        = scaledWidth + 'px';
    container.style.height       = scaledHeight + 'px';
    document.body.style.fontSize = fontSize + 'px';
};
 
window.addEventListener('resize', calculateFontSize, false);
window.addEventListener('load', calculateFontSize, false);

再让我们在样式表上下点工夫。 在文档的主体中我们定义初始分辨率1280x720px下的字体大小为25px。 现在,对于主体中的任何元素而言,1em等于25px。 我们可以更改字体大小来改变这个等式。 例如,将一个div元素的字体大小设置为200%。 这时1em等于多少像素呢?没错,现在1em等于50px。 如果你想在之前讨论的div元素里放置另外一个1em等于25px的元素,你必须把这个元素的字体大小改为50%。 为什么要这样?这是级联样式表特殊的地方。 每一个子元素从它们的父元素那里继承参数数值(如果它们没有被覆盖)。 所以,该div元素中的任何元素都和它们的父元素拥有同样的字体大小。

比如我们需要将一个元素扩展为屏幕的宽度。 需要多少em?你需要做一些简单的计算。 只需要将720px除以1em的像素值,在这个主体中为25px。 720px / 25px = 28,8em – 这个数值可以让元素完美的调整至屏幕的宽度。

对于图片,你需要准备一个在最高分辨率下图像清晰的文件,然后在其他屏幕上只需要将它缩小。 在示例应用程序中,没有设置任何使用em数值的像素属性。 这里着重于通过适当的填充和留白,使得设计尽量简洁, 你可以在互联网上找到很多描述界面设计方法的好的文章,不过这不是本文的主题。

在示例应用程序中使用了三种不同的字体大小:标题中的应用程序名称为200%,菜单按钮为150%,菜单下的文本为100%。 通过设置不同的字体大小,修改了不同上下文中1em对应的像素值。 现在50px对应200%字体大小,37.5px对应150%,25px对应100%。 当然,对于那些设备/内容区分辨率与我们的基准分辨率1280x720px不同的设备/浏览器而言,1em(该数值乘以缩放因子)的值都不相同。 我们从主体中取出一个元素,如果它的字体大小没有改写,那么字体大小的数值将会是100%。 有一种情况,例如,一个图片标签,其宽度设置为18em,对于HD屏幕来说是450px。 如果我们不想支持更大的屏幕,我们可以准备一个刚好450px宽的图像,渲染引擎可以把它缩小到任何较低的分辨率。

研究一下本文附带的示例应用程序的样式文件,然后利用桌面浏览器试验用户界面的缩放。 尝试在Tizen模拟器和仿真器上运行不同分辨率的应用程序。

利用vw和vh单位设计UI

让我们考虑利用vw和vh单元来创建与之前章节中演示的相同的应用程序。 我们要看一下他们中的哪个更合适。 如前所述,在桌面浏览器中我们将所有内容缩放到内容区的高度,所以选择vh单位。

在1280px高的屏幕上,我们希望文字以25px字体大小书写。 要计算vh值我们要用字体大小除以屏幕高度,得到的数值大约是0.0195vh。 换言之,它等于屏幕高度的1.95%,这已经足够了。

如果想要将像素值转换为vh,这将更加简单。 我们可以计算单个像素的vh值,然后用任何像素值乘以这个数值得到以vh为单位的数值:100/1280=0.078125。 让我们进行一些计算作为练习:

  • 整块屏幕的宽度:0.078125*720 = 56.25vh。
  • 25px字体大小: 0.078125 * 25 = 1.953125vh。 注意字体大小计算得到的值与之前的段落相同,表明计算结果无误。

当使用vh单位时,容器是我们唯一需要注意的元素。 我们需要设置他的宽度和高度属性。 然而WebKit引擎不能够正确的渲染以vw和vh单位设置的边框和阴影。

#container {
    margin: auto;
    overflow: hidden;
    width: 56.25vh; height: 100vh;
}

你可以从前面的章节下载以vh为单位的示例程序。

总结

本文描述了CSS3单位,它们适用于构建具备可缩放用户界面的Tizen应用程序及其它程序。 本文展示了如何编写适用于所有平台和浏览器的代码,以及如何保证应用程序总是保持相同的视觉效果。 希望你现在已经获得了所有用于编写具有可缩放UI的应用程序的必要知识。