使用wxss为响应式开发带来的一些模式和思维上的改变。
WXSS(WeiXin Style Sheets)是一套样式语言,用于描述 WXML 的组件样式。
WXSS 用来决定 WXML 的组件应该怎么显示。
为了适应广大的前端开发者,我们的 WXSS 具有 CSS 大部分特性。 同时为了更适合开发微信小程序,我们对 CSS 进行了扩充以及修改。
与 CSS 相比我们扩展的特性有:
rem的重定义
前端工程师对rem非常熟悉,rem是以html元素的font-size为基准的尺寸计量单位。rem方便了开发者对响应式UI的尺寸进行统筹管理。
wxss中的rem与css中的rem的含义完全不同,下面是微信官方文档中对rem的定义:
rem(root em): 规定屏幕宽度为20rem;1rem = (750/20)rpx
其中的750这个数值是wxss将设备屏幕的宽统一定义为750rpx,对此,下文会讲解。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML Strict//EN">
<META http-equiv="Content-Type" content="text/html; charset=utf-8">
<html lang="zh-cn">
<head>
<style>
.cnblogs-markdown .hljs,blockquote,html{background-attachment:scroll;background-size:auto;background-origin:padding-box;background-clip:border-box}#mainContent,.cnblogs-markdown .hljs,blockquote,html{background-image:none;background-repeat:repeat;background-position-x:0;background-position-y:0}pre{margin-top:0;margin-bottom:0;white-space:pre-wrap;word-wrap:break-word}blockquote{margin-top:10px;margin-bottom:10px;background-color:transparent;border-color:#efefef;border-width:2px;border-style:solid}#cnblogs_post_body{margin-bottom:20px}#cnblogs_post_body ul{padding-left:0;margin-left:30px}#cnblogs_post_body ol{padding-left:40px}#cnblogs_post_body ol li{list-style-type:decimal}#cnblogs_post_body ul li{list-style-type:disc}#cnblogs_post_body p{text-indent:0;margin:10px auto}#cnblogs_post_body h3{line-height:1.5;font-size:16px;font-weight:700;margin:10px 0}.cnblogs-markdown .hljs,.cnblogs-markdown code{font-family:"Courier New",sans-serif!important;font-size:12px!important}.cnblogs-markdown .hljs{color:#000;display:block;overflow-x:auto;background-color:#fff}.hljs-built_in,.hljs-keyword,.hljs-name,.hljs-selector-tag,.hljs-tag{color:#00f}.hljs-addition,.hljs-attribute,.hljs-literal,.hljs-section,.hljs-string,.hljs-template-tag,.hljs-template-variable,.hljs-title,.hljs-type{color:#a31515}.hljs-attr{color:red}.cnblogs-markdown code{line-height:1.8;vertical-align:middle;display:inline-block;background-color:#f5f5f5!important;padding:0 5px!important;margin:1px 5px;border-color:#ccc!important;border-width:1px!important;border-style:solid!important;border-radius:3px!important}.cnblogs-markdown pre code{height:auto;line-height:normal;vertical-align:auto;display:block;margin:auto}.cnblogs-markdown .hljs{line-height:1.5!important;padding:5px!important}.cnblogs-markdown pre{margin-top:10px;margin-bottom:10px}body{background-image:url(images/bg.gif);background-repeat:repeat;background-color:#fffdfa}html{color:#000;overflow-y:scroll;background-color:#fff}blockquote,body,button,dd,dl,dt,fieldset,form,h1,h2,h3,h4,h5,h6,hr,input,lengend,li,ol,p,pre,td,textarea,th,ul{list-style-type:none;list-style-image:none;padding:0;margin:0}body,button,input,select,textarea{line-height:1.5;font-family:Tahoma,Arial,Helvetica,sans-serif;font-size:12px;font-style:normal;font-variant:normal;font-weight:400;font-size-adjust:none;font-stretch:normal}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:400}address,cite,dfn,em,var{font-style:normal}code,kbd,pre,samp,tt{font-family:"Courier New",Courier,monospace}ol,ul{list-style:none}a:link,a:visited{color:#39f;text-decoration:none}:focus{outline:0}fieldset,img{border-color:currentColor;border-width:medium;border-style:none}img{-ms-interpolation-mode:bicubic}#blogTitle,#footer,#main,#navigator{width:1000px;position:relative;margin:0 auto}#main{margin-top:6px;margin-bottom:0;background-color:#f5f5f5}#mainContent,.postBody blockquote,.postCon blockquote{background-attachment:scroll;background-size:auto;background-origin:padding-box;background-clip:border-box;background-color:#fff}#mainContent{width:765px;float:left;display:inline-block;box-shadow:0 0 8px #999;border-radius:6px}.entrylistPostSummary,.postBody,.postCon{clear:both;margin-top:26px;color:#444;line-height:1.8;font-size:14px}.postBody h3,.postCon h3{font-size:16px}.postBody ul,.postCon ul{margin-left:20px;list-style:none}.postBody ul li,.postCon ul li{list-style:disc inside}.postBody ol,.postCon ol{margin-left:20px;list-style:none}.postBody blockquote,.postCon blockquote{width:90%;color:#666;background-image:url(/images/blockquote.gif);background-repeat:no-repeat;background-position-x:left;background-position-y:top;padding:6px 0 6px 45px;margin:0 auto}.post{padding:32px}.forFlow p{margin-bottom:18px}.forFlow img{margin-top:20px;margin-bottom:20px}#cnblogs_post_body{font-size:14px}#cnblogs_post_body img{max-width:650px}
</style></head><body> <div id="home"> <div id="main"> <div id="mainContent"> <div class="forFlow"> <div id="post_detail"> <div id="topics"> <div class="post"> <div class="postBody"> <div class="cnblogs-markdown" id="cnblogs_post_body"> <p> 使用wxss为响应式开发带来的一些模式和思维上的改变。
</p> <h3 id="rem的重定义"> rem的重定义 </h3> <p> 前端工程师对<code>rem</code>非常熟悉,rem是以html元素的<code>font-size</code>为基准的尺寸计量单位。rem方便了开发者对响应式UI的尺寸进行统筹管理。 </p> <p> wxss中的<code>rem</code>与css中的<code>rem</code>的含义完全不同,下面是微信官方文档中对rem的定义: </p> <blockquote> <p> rem(root em): 规定屏幕宽度为20rem;1rem
= (750/20)rpx </p> </blockquote> <p> 其中的750这个数值是<strong>wxss将设备屏幕的宽统一定义为750rpx</strong>,对此,下文会讲解。 </p> <p> 各位读到这里是否脑海里浮现了一个想法:<strong>wxss的rem怎么听起来有点像bootstrap的<a href="http://v3.bootcss.com/css/#grid">栅格系统</a>呢?</strong> </p> <p> wxss将屏幕宽分为20rem,bootstrap将设备屏幕宽度分为12列。初看起来确实有点类似。但其实wxss的rem和bootstrap的栅格系统并不相同。虽然wxss和bootstrap都是讲屏幕尺寸分割为单元格,但rem和栅格的定位不同。
</p> <p> bootstrap的开发者使用指定的classname进行元素间的比例分配,这其实接近为css3中的flexbox;而wxss的rem是一个<em>尺寸单位</em>,你可以在<strong>合理的场景</strong>下将任何以<code>px</code>为单位的属性值替换为<code>rem</code>。 </p> <p> 所以,开发小程序UI时,需要抛弃思维中对rem的常规认知。截止目前,笔者还未遇到必须使用小程序rem的需求,希望大家踊跃探讨。 </p> <h3 id="rpx的奇妙之处">
rpx的奇妙之处 </h3> <p> 上文提到wxss将设备屏幕的宽统一定义为750rpx,其中的rpx是wxss带来的新的尺寸单位。rpx的定义如下: </p> <blockquote> <p> rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。 </p> </blockquote> <p> css中的px与设备的物理像素并非绝对的一比一关系。尤其是在移动设备上,px与物理像素的比例与设备的dpr(devicePixelRadio)有关,详细的对应关系各位可自行查阅。
</p> <p> rpx称为相对像素值,rpx与物理像素也并非绝对的一比一关系。wxss将设备宽定义为750rpx,是以iPhone6的分辨率(750x1334)为基准划分的。也就是说,在iPhone6上,1rpx=1物理像素=0.5px。官方文档列出了几种屏幕的rpx对应关系如下: <br /> <img src="https://i-blog.csdnimg.cn/blog_migrate/a6396b489cfdc3d3e4809d738a9f9651.png" /> </p>
<p> 大家可以从中得到rpx和px的换算公式: </p> <blockquote> <p> 1rpx = 1px/dpr </p> </blockquote> <p> 其中iPhone6的dpr=2。 </p> <p> 那么rpx带给响应式UI什么改变呢? </p> <p> 目前大部分UI工程师在制作UI稿的时候是按照iPhone6的尺寸设计,然后前端工程师按照UI稿尺寸的一半进行UI的还原开发。这样在iPhone6以及接近iPhone6尺寸的设备上是没有任何问题的。但是移动设备的尺寸多种多样,我们的产品不可能只应对iPhone6(况且iPhone7已经来了哈哈...),所以通常的做法是使用css的媒体查询根据设备的尺寸再进行适配微调。
</p> <p> 如果使用rpx是不是就可以解决这个问题呢?笔者在开发过程中尝试使用rpx代替px,使用UI稿的原始尺寸还原UI,截止到目前体验非常好。rpx本身代表的是相对像素,所以不论多大尺寸的屏幕,<strong>rpx的UI占据的屏幕比例是绝对固定的</strong>,是等比缩放的。 </p> <p> 但是rpx并非万能的,比如使用css sprites的图标。请看下文。 </p> <h3 id="sprites图标的响应式处理"> sprites图标的响应式处理 </h3> <p> 使用css sprites作为图标背景时,每个图标的尺寸是以px为单位固定的,比如:
</p> <div class="sourceCode"> <pre class="sourceCode css"> <code class="sourceCode css hljs"><span class="fl"><span class="hljs-selector-class">.icon</span></span><span class="kw">{</span> <span class="kw"><span class="hljs-attribute">background-image</span>:</span>
<span class="dt"><span class="hljs-built_in">url</span>(</span><span class="st"><span class="hljs-string">'//image.daojia.com/icon.png'</span></span><span class="dt">)</span><span class="kw">;</span> <span class="kw"><span class="hljs-attribute">display</span>:</span>
<span class="dt">inline-block</span><span class="kw">;</span> <span class="kw"><span class="hljs-attribute">vertical-align</span>:</span> <span class="dt">middle</span><span class="kw">;</span> <span class="kw">}</span> <span class="fl"><span class="hljs-selector-class">.icon__circle</span></span><span
class="kw">{</span> <span class="kw"><span class="hljs-attribute">background-position</span>:</span> <span class="dt"><span class="hljs-number">0</span></span> <span class="dt"><span class="hljs-number">0</span></span><span class="kw">;</span> <span class="kw"><span
class="hljs-attribute">width</span>:</span> <span class="dt"><span class="hljs-number">40px</span></span><span class="kw">;</span> <span class="kw"><span class="hljs-attribute">height</span>:</span> <span class="dt"><span class="hljs-number">40px</span></span><span
class="kw">;</span> <span class="kw">}</span></code> </pre> </div> <p> 如果图标的尺寸不符合UI设计,则进行一定比例的缩放: </p> <div class="sourceCode"> <pre class="sourceCode css"> <code class="sourceCode css hljs"><span class="fl"><span class="hljs-selector-class">.icon__circle</span></span><span
class="kw">{</span> <span class="kw"><span class="hljs-attribute">transform</span>:</span> <span class="hljs-built_in">scale</span>(<span class="dt"><span class="hljs-number">0.5</span></span>)<span class="kw">;</span> <span class="kw">}</span></code> </pre>
</div> <p> 也就是说,使用sprites图标不可避免地会用到px,如果与rpx结合使用,是不能保证同rpx一样等比缩放效果的。那么怎么去解决这个问题呢? </p> <p> 根据上文总结出的rpx与px的换算公式,如果想要将以px规定的UI达到同rpx一样的响应式缩放效果,必须将px与设备的dpr进行计算。但是css作为一种标记语言,并不具备动态特性,无法动态地获取设备dpr并计算。所以,单纯使用wxss并不能解决上文提到的问题。 </p> <p> 好消息是小程序提供了获取设备信息的API,并且支持CommonJS模块化方案。有了这些功能,我们可以在封装组件时加入动态的逻辑配置。
</p> <p> 还是以上文的代码为例,sprites图的<code>icon__circle</code>尺寸为40px*40px,我们的目标是将其适配为20rpx,以下是笔者的开发方案。 </p> <p> 比如项目中有一个user组件,包含了一些sprites图标节点。user组件的文件目录如下: </p> <ul> <li> <code>user.wxml</code> - 组件模板; </li> <li> <code>user.wxss</code> - 组件样式; </li> <li> <code>user.js</code>
- 组件逻辑。 </li> </ul> <p> 首先给<code>user.wxml</code>中icon对应的element设置动态的<code>transform</code>: </p> <div class="sourceCode"> <pre class="sourceCode xml"> <code class="sourceCode xml hljs"><span class="kw"><span class="hljs-tag"><<span class="hljs-name">view</span></span></span><span
class="ot"><span class="hljs-tag"> <span class="hljs-attr">class</span>=</span></span><span class="st"><span class="hljs-tag"><span class="hljs-string">'icon icon__circle'</span></span></span><span class="ot"><span class="hljs-tag"> <span class="hljs-attr">style</span>=</span></span><span
class="st"><span class="hljs-tag"><span class="hljs-string">"transform: scale({{iconScale}})"</span></span></span><span class="kw"><span class="hljs-tag">></span><span class="hljs-tag"></<span class="hljs-name">view</span>></span></span></code> </pre> </div>
<p> 其中<code>iconScale</code>是引用user的外部组件index传递给user组件的: </p> <div class="sourceCode"> <pre class="sourceCode xml"> <code class="sourceCode xml hljs"><span class="kw"><span class="hljs-tag"><<span class="hljs-name">import</span></span></span><span class="ot"><span
class="hljs-tag"> <span class="hljs-attr">src</span>=</span></span><span class="st"><span class="hljs-tag"><span class="hljs-string">'user.wxml'</span></span></span><span class="kw"><span class="hljs-tag">/></span></span> <span class="kw"><span class="hljs-tag"><<span
class="hljs-name">template</span></span></span><span class="ot"><span class="hljs-tag"> <span class="hljs-attr">is</span>=</span></span><span class="st"><span class="hljs-tag"><span class="hljs-string">'product-user'</span></span></span><span class="ot"><span
class="hljs-tag"> <span class="hljs-attr">data</span>=</span></span><span class="st"><span class="hljs-tag"><span class="hljs-string">"{{iconScale: userIconScale}}"</span></span></span><span class="kw"><span class="hljs-tag">/></span></span></code> </pre>
</div> <p> userIconScale是index组件的一个data,userIconScale的值并非index组件规定的,而是由index组件的js调用<code>user.js</code>动态获取的。以下代码是<code>user.js</code>暴露的API: </p> <div class="sourceCode"> <pre class="sourceCode javascript"> <code class="sourceCode javascript hljs"><span class="kw"><span
class="hljs-keyword">const</span></span> ORIGIN_ICON_PX <span class="op">=</span> <span class="dv"><span class="hljs-number">40</span></span><span class="op">;</span> <span class="kw"><span class="hljs-keyword">const</span></span> TARGET_ICON_RPX <span class="op">=</span>
<span class="dv"><span class="hljs-number">20</span></span><span class="op">;</span> <span class="va"><span class="hljs-built_in">module</span></span>.<span class="at">exports</span> <span class="op">=</span> <span class="op">{</span> <span class="at">getIconScale</span>()
<span class="op">{</span> <span class="kw"><span class="hljs-keyword">let</span></span> result <span class="op">=</span> <span class="dv"><span class="hljs-number">1</span></span><span class="op">;</span> <span class="va">wx</span>.<span class="at">getSystemInfo</span>(<span
class="op">{</span> <span class="dt">success</span><span class="op">:</span> <span class="kw"><span class="hljs-function"><span class="hljs-keyword">function</span></span></span><span class="hljs-function">(<span class="hljs-params">res</span>) </span><span
class="op">{</span> <span class="kw"><span class="hljs-keyword">let</span></span> _dpr <span class="op">=</span> <span class="va">res</span>.<span class="at">pixelRatio</span><span class="op">;</span> result <span class="op">=</span> TARGET_ICON_RPX/(ORIGIN_ICON_PX
<span class="op">*</span> _dpr)<span class="op">;</span> <span class="op">}</span> <span class="op">}</span>)<span class="op">;</span> <span class="cf"><span class="hljs-keyword">return</span></span> result<span class="op">;</span> <span class="op">}</span>
<span class="op">}</span></code> </pre> </div> <p> 然后在index组件的js中调用以上API: </p> <div class="sourceCode"> <pre class="sourceCode javascript"> <code class="sourceCode javascript hljs"><span class="kw"><span class="hljs-keyword">let</span></span> getIconScale
<span class="op">=</span> <span class="at"><span class="hljs-built_in">require</span></span>(<span class="st"><span class="hljs-string">'user.js'</span></span>).<span class="at">getIconScale</span><span class="op">;</span> <span class="at">Page</span>(<span
class="op">{</span> <span class="dt">data</span><span class="op">:</span> <span class="op">{</span> <span class="dt">userIconScale</span><span class="op">:</span> <span class="dv"><span class="hljs-number">1</span></span> <span class="op">},</span> <span class="at">onLoad</span>()<span
class="op">{</span> <span class="kw"><span class="hljs-keyword">this</span></span>.<span class="at">setData</span>(<span class="op">{</span> <span class="dt">userIconScale</span><span class="op">:</span> <span class="at">getIconScale</span>() <span class="op">}</span>)<span
class="op">;</span> <span class="op">}</span> <span class="op">}</span>)<span class="op">;</span></code> </pre> </div> <p> 以上只是初步的方案,很多地方需要再仔细琢磨。不过以上方案基本上具备了一个组件的逻辑封装,并且达到了我们对响应式的开发需求。 </p> </div> </div> </div> </div> </div> </div> </div> </div> </div></body></html>
使用wxss为响应式开发带来的一些模式和思维上的改变。
rem的重定义
前端工程师对rem
非常熟悉,rem是以html元素的font-size
为基准的尺寸计量单位。rem方便了开发者对响应式UI的尺寸进行统筹管理。
wxss中的rem
与css中的rem
的含义完全不同,下面是微信官方文档中对rem的定义:
rem(root em): 规定屏幕宽度为20rem;1rem = (750/20)rpx
其中的750这个数值是wxss将设备屏幕的宽统一定义为750rpx,对此,下文会讲解。
各位读到这里是否脑海里浮现了一个想法:wxss的rem怎么听起来有点像bootstrap的栅格系统呢?
wxss将屏幕宽分为20rem,bootstrap将设备屏幕宽度分为12列。初看起来确实有点类似。但其实wxss的rem和bootstrap的栅格系统并不相同。虽然wxss和bootstrap都是讲屏幕尺寸分割为单元格,但rem和栅格的定位不同。
bootstrap的开发者使用指定的classname进行元素间的比例分配,这其实接近为css3中的flexbox;而wxss的rem是一个尺寸单位,你可以在合理的场景下将任何以px
为单位的属性值替换为rem
。
所以,开发小程序UI时,需要抛弃思维中对rem的常规认知。截止目前,笔者还未遇到必须使用小程序rem的需求,希望大家踊跃探讨。
rpx的奇妙之处
上文提到wxss将设备屏幕的宽统一定义为750rpx,其中的rpx是wxss带来的新的尺寸单位。rpx的定义如下:
rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。
css中的px与设备的物理像素并非绝对的一比一关系。尤其是在移动设备上,px与物理像素的比例与设备的dpr(devicePixelRadio)有关,详细的对应关系各位可自行查阅。
rpx称为相对像素值,rpx与物理像素也并非绝对的一比一关系。wxss将设备宽定义为750rpx,是以iPhone6的分辨率(750x1334)为基准划分的。也就是说,在iPhone6上,1rpx=1物理像素=0.5px。官方文档列出了几种屏幕的rpx对应关系如下:
大家可以从中得到rpx和px的换算公式:
1rpx = 1px/dpr
其中iPhone6的dpr=2。
那么rpx带给响应式UI什么改变呢?
目前大部分UI工程师在制作UI稿的时候是按照iPhone6的尺寸设计,然后前端工程师按照UI稿尺寸的一半进行UI的还原开发。这样在iPhone6以及接近iPhone6尺寸的设备上是没有任何问题的。但是移动设备的尺寸多种多样,我们的产品不可能只应对iPhone6(况且iPhone7已经来了哈哈...),所以通常的做法是使用css的媒体查询根据设备的尺寸再进行适配微调。
如果使用rpx是不是就可以解决这个问题呢?笔者在开发过程中尝试使用rpx代替px,使用UI稿的原始尺寸还原UI,截止到目前体验非常好。rpx本身代表的是相对像素,所以不论多大尺寸的屏幕,rpx的UI占据的屏幕比例是绝对固定的,是等比缩放的。
但是rpx并非万能的,比如使用css sprites的图标。请看下文。
sprites图标的响应式处理
使用css sprites作为图标背景时,每个图标的尺寸是以px为单位固定的,比如:
.icon{
background-image: url('//image.daojia.com/icon.png');
display: inline-block;
vertical-align: middle;
}
.icon__circle{
background-position: 0 0;
width: 40px;
height: 40px;
}
如果图标的尺寸不符合UI设计,则进行一定比例的缩放:
.icon__circle{
transform: scale(0.5);
}
也就是说,使用sprites图标不可避免地会用到px,如果与rpx结合使用,是不能保证同rpx一样等比缩放效果的。那么怎么去解决这个问题呢?
根据上文总结出的rpx与px的换算公式,如果想要将以px规定的UI达到同rpx一样的响应式缩放效果,必须将px与设备的dpr进行计算。但是css作为一种标记语言,并不具备动态特性,无法动态地获取设备dpr并计算。所以,单纯使用wxss并不能解决上文提到的问题。
好消息是小程序提供了获取设备信息的API,并且支持CommonJS模块化方案。有了这些功能,我们可以在封装组件时加入动态的逻辑配置。
还是以上文的代码为例,sprites图的icon__circle
尺寸为40px*40px,我们的目标是将其适配为20rpx,以下是笔者的开发方案。
比如项目中有一个user组件,包含了一些sprites图标节点。user组件的文件目录如下:
user.wxml
- 组件模板;user.wxss
- 组件样式;user.js
- 组件逻辑。
首先给user.wxml
中icon对应的element设置动态的transform
:
<view class='icon icon__circle' style="transform: scale({{iconScale}})"></view>
其中iconScale
是引用user的外部组件index传递给user组件的:
<import src='user.wxml'/>
<template is='product-user' data="{{iconScale: userIconScale}}"/>
userIconScale是index组件的一个data,userIconScale的值并非index组件规定的,而是由index组件的js调用user.js
动态获取的。以下代码是user.js
暴露的API:
const ORIGIN_ICON_PX = 40;
const TARGET_ICON_RPX = 20;
module.exports = {
getIconScale() {
let result = 1;
wx.getSystemInfo({
success: function(res) {
let _dpr = res.pixelRatio;
result = TARGET_ICON_RPX/(ORIGIN_ICON_PX * _dpr);
}
});
return result;
}
}
然后在index组件的js中调用以上API:
let getIconScale = require('user.js').getIconScale;
Page({
data: {
userIconScale: 1
},
onLoad(){
this.setData({
userIconScale: getIconScale()
});
}
});
以上只是初步的方案,很多地方需要再仔细琢磨。不过以上方案基本上具备了一个组件的逻辑封装,并且达到了我们对响应式的开发需求。