翻译自https://medium.freecodecamp.com/the-fab-four-technique-to-create-responsive-emails-without-media-queries-baf11fdfa848#.y38p2okxs

我发现了一种新的方式来创建响应式的电子邮件,而且不使用媒体查询。这个方案包括了CSS的calc()函数和widthmin-widthmax-width三个属性。

或者说,我喜欢这样称呼他们:绝妙的四剑客(在CSS里)

主要问题

制作响应式的邮件不简单,尤其是当邮件客户端在不支持媒体查询的手机上的时候(比如Gmail,Yahoo,或者Outlook.com)。使用混合的方法,Gmail的策略,或者说不用媒体查询的响应式邮件,对于适应这样的情况都是不错的方法。

到目前为止,上面所说的最后一种方法是我最偏爱的。最大的灵感就是使用display: inline-block;让每一列的div都有一个固定的宽度。一旦一个屏幕只能包含两块内容的时候,他们就会自动的到下面去。但是,我这样做的时候总是会有问题。

一旦这些内容块堆叠起来的时候,他们并不会占满邮件的空间。

我花费了很长的时间来寻找这个问题的解决方案。Flexbox很不错,但是不幸的是,Flexbox对邮件的支持简直糟透了。


解决方案

记住width,min-width和max-width

出了calc()函数之外,我发现这个解决方案还需要CSS的三个属性。为了深刻的理解他们是如何运作的,需要知道这三个属性在一起使用的时候会有什么样的行为(一个法国前端工程师做到非常清晰的总结)。

  • 如果width属性值大于max-width属性值,那么,最终取值为max-width
  • 如果min-width属性值大于width属性值或者max-width属性值,那么,取值为min-width

能够猜出下面的样式中盒子的宽度吗?

1
2
3
4
5
.box {
width: 320px;
min-width: 480px;
max-width: 160px;
}

(答案:盒子宽度为480px)


calc()函数和神奇的公式

先不要急着深入,看一个例子:使用四剑客创建了两列,并且在堆叠的时候会增长到480px。

1
2
3
4
5
6
.block {
display: inline-block;
min-width: 50%;
max-width: 100%;
width: calc((480px — 100%) * 480);
}

让我们把这段代码拆开来看每一个宽度属性。

1
min-width: 50%;

min-width属性定义了我们的桌面版本的列宽。我们可以改变这个值来添加更多的列(比如,4列的话就是25%),或者说,将每一列的宽度设置为固定的像素值。

1
max-width: 100%;

max-width定义了我们的手机版本的列宽。使用100%就能够让每一列适应父级容器。我们可以改变这个值以便与在手机上也是多列(比如,2列的话就是25%)。

1
width: calc((480px — 100%) * 480);

感谢calc()这个函数,让宽度属性成为了奇迹。480这个值吻合了我们渴望的平衡点的值。100%符合我们的列宽的父级容器的宽度。这个计算公式的目的就在于创建一个比max-width大或者比min-width小的值,然后应用其中的一个值。

看两个例子。

在父级容器的宽度为500px的时候,计算出来的宽度值为-9600px。小于min-width的值,所以,最小宽度为50%。这样,我们的布局就是两列了。

在父级宽度为400px的时候,计算出来的宽度值为38400px,大于了min-width,但是max-width比他小,所以宽度为100%。这样,我们的布局就是一列了。


Demo

有一个使用这个技术做的demo,可以在这儿查看

下面是这个demo分别在Gmail的桌面端和iOS端的截图。同样的代码,不同的渲染效果。

在这个demo中,我设置了不同的网格的例子(2列,3列,4列)。第一个使用图片的网格,在桌面端将是4列,在手机端将是2列。其他的网格在移动端将是填满屏幕的宽度。

另外,注意标题是如何切换的。在桌面端是左对齐,在手机端是居中对齐。这是通过给标题设置190px的固定宽度以及设置margin:0 auto;来居中的。在桌面端,标题的父级容器应用的是最小宽度190px,所以会在左边。在手机端,父级容器为满屏宽度,所以会居中。

由于每一个元素都是建立在网格的父级容器的宽度上,所以这个技术最棒的一个方面就是,一封邮件甚至可以适应网页邮件应用。例如,在Oulook.com上,无论你选择邮件面板在底部或者在右边,邮件都会很正确的响应面板的宽度。使用媒体查询是不可能做到这样的。


浏览器支持

calc()对浏览器的支持在IE9及以上。事实证明,calc()对邮件客户端的支持也很好。在Apple Mail(iOS和OS X),Thunderbird,Outlook(iOS和Android应用),Gmail(网页邮件,iOS和Android应用),AOL(网页应用),以及旧版的Outlook.com(只在欧洲使用)这些上面都能很好的运作。

旧版的Outlook.com

尽管Outlook.com有一个小怪癖。他会通过calc()过滤掉任何包含包含小圆括号的语句的属性。这也就意味着,calc(480px - 100%)这一语句是支持的,但是calc((480px - 100%) * 480)这一语句就不支持了。由于我的初始的公式包含小括号,我们需要对他进行重构来避免小括号。所以,支持旧版的Outlook.com的公式就是这个样子:

1
width: calc(480px * 480 — 100% * 480);

不支持的客户端

当然,calc()在一些老的邮件客户端上不受支持,比如Lotus Notes,或者最新的Windows的Outlook。在Outlook的网页应用和Yahoo(网页应用,iOS和Android应用)上都不受支持。这两者会去掉任何包含calc()的属性。

退却的方案

在这些例子中,我建议为那些不支持calc()的客户端通过设置固定的宽度来复制所有的需要的属性。为了从那些客户端中隐藏掉四剑客,我建议使用calc(),即使在技术层面没有那么有用。我们的第一个例子会像下面这样:

1
2
3
4
5
6
7
8
.block {
display: inline-block;
min-width: 240px;
width: 50%;
max-width: 100%;
min-width: calc(50%);
width: calc(480px * 480 — 100% * 480);
}

Outlook网页应用

然而,Outlook的网页应用(Office 365和新的Outlook.com)又有一个他自己的怪癖。当calc()函数中包含了乘法符号*的时候,他会移除整句样式。意味着,我们需要手动的算出结果并且只保留减法。比如下面这样:

1
width: calc(230400px — 48000%);

Webkit前缀

老版本的Android(Android5.0以前)或者iOS(iOS 7以前)需要-webkit-才能起作用。我们的最终版本就会是下面这个样子:

1
2
3
4
5
6
7
8
9
10
.block {
display: inline-block;
min-width: 240px;
width: 50%;
max-width: 100%;
min-width: -webkit-calc(50%);
min-width: calc(50%);
width: -webkit-calc(230400px — 48000%);
width: calc(230400px — 48000%);
}

缺点和总结

与其它在电子邮件世界开发的东西一样,四剑客这样的技术也不是完美的,下面是我能够想到的一些缺陷:

  • 在Yahoo上不会起作用。虽然他的桌面端支持媒体查询。所以,我们能够通过制作一个移动端优先的版本来改进一下,然后再在桌面端上提升他的兼容性。
  • 你只能设置一个平衡点。对于邮件来说,这也许不是那么大的一个问题。就像设计桌面端几乎不会超过600px以及顶多需要一个平衡点来适应手机。
  • 从桌面端到手机端的时候,你只能减少列的数量。很正常,你不可能在手机上显示4列,在桌面上显示一列。
  • 最终计算的版本(为了支持旧版的Outlook.com以及优雅的退化在新的版本)的可读性很差。使用预处理器和mixin来省城这些需要的属性可能会更有帮助。

我仍然觉得这种技术在以后会有更多的使用,尤其是对Gmail的优化。当热,肯定也会有网站的使用之处(比如小部件,广告等)。