项目需求:开发一个邮件模板,用于向海外客户邮箱推送各种消息,需要根据消息类型,展示不同的内容。比如:异常告警的时候呈现的是一种形式,注册及忘记密码又是另一种形式。除此之外,同一个模板还需要兼容移动端和PC端。UI大致如下:
因此,需要考虑众多因素来实现这个需求。首先是邮件模板,经查阅资料及实践,发现网页邮箱借助于浏览器强大的渲染能力还是很好兼容的,但是邮箱客户端由于安全性及渲染能力的制约,并不能很好的渲染html文件,编写兼容性良好的html邮件模板,需要使用15年前的网页制作方法。主要经验总结如下:
1.Doctype
兼容性最好的Doctype是XHTML 1.0,所以我们常用的HTML5文件头不能使用,需要换上以下这种文件头Doctype,使用这种Doctype就意味着不能使用HTML5的语言。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>HTML Email编写指南</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> </head> </html>
2.布局
由于不能使用HTML5的语法,并且邮箱客户端对CSS的支持情况不尽相同,所以布局要使用古老的table布局,且必须使用table布局,就不要使用div p等标签了。首先在最外层放置一个大表格,用来设置背景。
<body style="margin: 0; padding: 0;"> <table border="1" cellpadding="0" cellspacing="0" width="100%"> <tr> <td> Hello! </td> </tr> </table> </body>
3.图片
图片是唯一可以引用的外部资源,鉴于安全性考虑,外部资源比如样式表文件、字体文件、视频文件等一律不能引用。
<img border="0" style="display:block;outline:none;text-decoration:none,-ms-interpolation-mode:bicubic;border:none;">
因为在有些邮箱里,图片不是默认加载的,往往加载前需要用户的许可。那么高宽的指定可以使邮件在没有图片撑出样子前也能保持良好的大小结构,加上 alt
属性更可以明确告知图片的内容让用户选择是否下载它们。
如果因为项目需要(比如需要适配 Retina 高分屏),width
和 height
属性更是必不可少的,并且由于一些 outlook 版本的奇葩表现,width
和 height
属性一定不要加上单位!一定不要加上单位!一定不要加上单位!重要的事情说三遍。
因为加上单位会使一些版本的 OutLook 无法正确识别,导致图片显示使用实际的宽高而非我们设置的。
<img width="10px" height="10px" src="*.png" />
style
内容里面 background
可以设置 color
,但是 image
会被过滤,就是说不能通过 CSS 来设置背景图片了。但是有一个很有意思的元素属性,也叫 background
,里面可以定义一个图片路径,但是功能有限,比如无法定位背景图片等。
例如要给一个单元格加一个背景,必须这样写:
<td background="*.png"> <!-- ... --> </td>
Outlook 2007-2013 不支持图片的 margin
与 padding
样式,必要的时候可以尝试 hspace
和 vspace
属性:
<img vspace="10" hspace="10" src="*.png" />
4.文本相关
所有的CSS规则,都需要采用行内样式。因为放置在网页头部的样式,会被Gmail客户端过滤掉。而我们面向海外的主要客户群使用的正式Gmail邮箱。
在 HTML 邮件中,font-family
只支持系统字体,不支持自定义字体,也不支持 font
简写,color
尽可能也不要使用简写。对于加粗字体,可以使用b标签而不要使用font-weight,前文介绍过,能用html标签和属性解决的样式问题就不要使用CSS样式。
5.行高
在 OutLook 中会有个默认的行高最小值,特别是当设置 font-family
为微软雅黑时,默认的行高差不多为 Word 中的两倍行距,如果 line-height
设置的值小于默认的行高,无论你设置的是多少,则始终使用默认值,在很多情况下这是不能忍的,好在有个神奇的 mso-line-height-rule
,使用行高时添加 mso-line-height-rule:exactly;
就能使行高始终等于我们所设置的值。
<td style="mso-line-height-rule: exactly; line-height: 36px;"> <!-- ... --> </td>
这只是微软的CSS属性,对其他客户端没影响。并且该属性只在这块元素上有效。
整体总结:
1.页面宽度请设定在550到650px以内。
2.使用table表格来布局。
3.如果需要邮件居中显示,请在table里设定align="center"。
4、不要写<style>标签、不要写class,所有CSS都用style属性,什么元素需要什么样式就用style写内联的CSS。
5、不要使用外链的css样式定义文字和图片(外链的css样式在邮件里将不能被读取,所以发送出去的邮件因为没有链接到样式,将会使你的邮件内容样式丢失),正确的写法:<td style="font-family:arial;font-size:12px;color:black">文字</td>。
6、不使用flash、java、javascript、frames、iframe、activeX以及DHTML,如果页面中的图片一定要动态的,请将flash文件转换成gif动画使用,但在outlook2007里,gif将不能正常显示,因为outlook2007限制gif动画。
7、不要使用<table></table>以外的body、meta和html之类的标签,部分邮箱系统会把这些过滤掉。
8、背景图片代码写法如下:<table background="background.gif" cellspacing="0" cellpadding="0"></table>,但请注意,outlook对背景图片不识别。
9.font-family属性不能为空,否则会被QQ屏蔽为垃圾邮件。
10.若邮件模板内侧边或者上下有空白间距,不要用 padding,必须得用标准的 td 来设定空白间距,否则会导致各个邮箱解析不同。
11、在 yahoo 邮箱里定义 line-height 的注意事项:需在块级元素里定义 line-height。如果 td 里有 p 标签,则 line-height 也必须在 p 中定义。无论是 td 还是 p,如果有超链接,则都必须在 a 标签里定义 line-height。如果只是在 td 或者 p 里面定义 line-height 的话,那 yahoo 邮箱将无法识别 a 里面的行高。
12.少用float, margin,padding. 绝对定位不能用,清除浮动用<table style="clear:both"></table>
13.如果 td 和 td 之间有间隔,使用<td style="border-bottom:10px solid #fff"></td>,这样写的话 td 之间是不会有间隔的。使用<td style="margin-bottom: 10px"></td>也是不会有空格的。如果 td 之间有间隙,必须用<td></td><td height="10px"> </td><td></td>来隔开。但是如果是 table,则<table style="border-top:10px solid #ffffff; border-bottom:20px solid #ffffff"></table>里面的内容会在上下有空行。
14.少用图片,邮箱不会过滤你的img标签,但是系统往往会默认不载入陌生来信的图片,如果用了很多图片的邮件,在片没有载入的情况下,丑陋无比甚至看不清内容,没耐心的用户直接就删除了。图片上务必加上alt。
outlook 规则
1.在<td>里设置 margin 是无效的,不论是 margin-left、margin-right、margin-top 或者 margin-boottom 都没有效果。
2.如果要使用<P>标签要考虑到<P>标签本身自带的上下行之间的行高。
yahoo规则
在table里设定align="center"无法居中, 需要内联style=“margin:0 auto,width:XX”
foxmail 规则
foxmail中所有p标签的Margin:0; 使用p标签时需要设置margin
邮件模板正好之后,接下来就是要解决根据不同的消息类型来展示不同的内容这个问题,目前我们采用的是thymeleaf模板引擎。主要用到的渲染语法如下:
1.获取变量值
<p th:text="'Hello!, ' + ${name} + '!'" >3333</p> 全部替换 <p th:text="${copyright}">© 2018 Closeli, Inc., All rights reserved </p>
可以看出获取变量值用$
符号,对于javaBean的话使用变量名.属性名
方式获取,这点和EL表达式一样.
另外$
表达式只能写在th标签内部,不然不会生效,上面例子就是使用th:text
标签的值替换p
标签里面的值,至于p里面的原有的值只是为了给前端开发时做展示用的.这样的话很好的做到了前后端分离.
2.引入图片url地址
Thymeleaf对于URL的处理是通过语法@{…}来处理
<a th:href="@{http://blog.csdn.net/u012706811}">绝对路径</a> <a th:href="@{/}">相对路径</a> <a th:href="@{css/bootstrap.min.css}">Content路径,默认访问static下的css文件夹</a> 动态获取src地址 <img alt="" width="300" src="cid:inlineCid0"/>
3.字符串替换
很多时候可能我们只需要对一大段文字中的某一处地方进行替换,可以通过字符串拼接操作完成:
<span th:text="'Welcome to our application, ' + ${user.name} + '!'">
4.布尔值条件判断
通过布尔值条件判断渲染来切换不同的显示界面从而达到预期效果,同时还用到了解析对象的语法th:utext
<div class="push-content" th:if="${pushContent != null}"> <td th:text="${pushContent.title}">异常运动异常</td> </div>
html5操作支持
th:abbr th:accept th:accept-charset th:accesskey th:action th:align th:alt th:archive th:audio th:autocomplete th:axis th:background th:bgcolor th:border th:cellpadding th:cellspacing th:challenge th:charset th:cite th:class th:classid th:codebase th:codetype th:cols th:colspan th:compact th:content th:contenteditable th:contextmenu th:data th:datetime th:dir th:draggable th:dropzone th:enctype th:for th:form th:formaction th:formenctype th:formmethod th:formtarget th:fragment th:frame th:frameborder th:headers th:height th:high th:href th:hreflang th:hspace th:http-equiv th:icon th:id th:inline th:keytype th:kind th:label th:lang th:list th:longdesc th:low th:manifest th:marginheight th:marginwidth th:max th:maxlength th:media th:method th:min th:name th:onabort th:onafterprint th:onbeforeprint th:onbeforeunload th:onblur th:oncanplay th:oncanplaythrough th:onchange th:onclick th:oncontextmenu th:ondblclick th:ondrag th:ondragend th:ondragenter th:ondragleave th:ondragover th:ondragstart th:ondrop th:ondurationchange th:onemptied th:onended th:onerror th:onfocus th:onformchange th:onforminput th:onhashchange th:oninput th:oninvalid th:onkeydown th:onkeypress th:onkeyup th:onload th:onloadeddata th:onloadedmetadata th:onloadstart th:onmessage th:onmousedown th:onmousemove th:onmouseout th:onmouseover th:onmouseup th:onmousewheel th:onoffline th:ononline th:onpause th:onplay th:onplaying th:onpopstate th:onprogress th:onratechange th:onreadystatechange th:onredo th:onreset th:onresize th:onscroll th:onseeked th:onseeking th:onselect th:onshow th:onstalled th:onstorage th:onsubmit th:onsuspend th:ontimeupdate th:onundo th:onunload th:onvolumechange th:onwaiting th:optimum th:pattern th:placeholder th:poster th:preload th:radiogroup th:rel th:rev th:rows th:rowspan th:rules th:sandbox th:scheme th:scope th:scrolling th:size th:sizes th:span th:spellcheck th:src th:srclang th:standby th:start th:step th:style th:summary th:tabindex th:target th:title th:type th:usemap th:value th:valuetype th:vspace th:width th:wrap th:vspace th:width th:wrap th:xmlbase th:xmllang th:xmlspace