上节内容的最后一部分,我们粗略地实现了「简单版」小程序的首页,在实现的过程中我们初步接触了 WXML 和 WXSS 的编写,主要涉及到内容结构和样式两部分,构建内容结构的时候我们用到了 view 视图组件、 page 页面组件、 open-data 开放能力组件,添加样式的时候用到了标签选择器和 Class 选择器,但都是浅尝辄止,相信大家也意犹未尽。

本节内容中,我们将正式了解标签、组件等概念,学习 CSS 、 WXSS 中选择器的使用和添加样式的方法,并以此为基础完成「简单版」小程序所有页面实现

标签 & 组件

「标签」和「组件」其实是两个不同范畴的概念,组件指的是能够承担某种功能的功能体,它往往由 WXML、WXSS 和 JavaScript 共同构造出来,能够完成搭建内容结构、以某种方式呈现内容、响应用户操作、完成业务逻辑等任务,而标签则是在 WXML 内书写组件的一种特定方式,用以向计算机传达「在这个地方我要放置这个组件」的意图。

以上节内容中使用到的 open-data 组件(open-data 组件 | 微信开放文档 )为例,能够接受 typeopen-gidlangdefault-textdefault-avatarbinderror 等特定属性,在不经用户授权且不泄露用户隐私的情况下在小程序应用界面中按照特定的方式呈现出特定用户信息的功能体是「组件」,而我们在 WXML 中写的 <open-data type="``userAvatarUrl"``></open-data> 是「标签」,「标签」用于向计算机传达 open-data 这个「组件」该出现在哪里,如何运作等。

想象一下,当两个人就「苹果」展开讨论的时候,他们之间是有一定的共识的,「苹果」有不同的颜色,红的、青的、黄的,有大小之分,属于水果,在树上成熟,可以吃,有果蒂等等,在交流之前或交流过程中,两人对于「什么是苹果」、「苹果具有什么样的特征」已经建立起了约定和共识。开发者与计算机交流的过程也是如此,在使用组件之前我们需要与计算机进行约定,对组件的功能、使用方式等达成共识,在这种约定中,「标签名」与「组件名」是一致的,于是一个标签就能够指向一个组件,当开发者在 WXML 中以特定格式写下标签名的时候,计算机将能够理解我们想在书写标签的位置放置同名组件。

在现实交流中,单单提及「苹果」二字是不够明确的,除了可以食用的水果之外,我们还有可能指向某知名科技品牌,但在我们与计算机对组件建立起的约定中,标签名与组件之间就是一一对应的关系,只要开发者按照约定的格式书写,计算机就能够准确理解,不会有歧义和误解。

具体来讲,应用由页面组成,页面就是用于组成应用的一个个组件,每个页面中又包含众多容器用于盛放内容,这些容器就是构成页面的一个个组件,而容器之中,我们又会放置内层容器或者图片、按钮等其它组件。开发者从一个个最基础的组件开始搭建整个应用,整体的逻辑跟搭房子是非常类似的,搭房子的时候从最基础的砖、瓦、钢筋水泥、金属材料开始,组合成为窗、墙、门、梁、顶等结构,这些结构再进而组合成为房子 🏠,搭建应用的时候,从最基础的组件开始,有机地组合成为复杂组件,组件层层嵌套成为页面,页面链接在一起也就搭建起了应用。

HTML 中原始标签指代的功能体通常叫做「元素」,在小程序中通常叫做「组件」,两者的差异主要来自两方面:

  • 其一、标签的命名角度不同,HTML 中的标签如 a(anchor)、p(paragraph)、div(division)、style、script 等主要从功能体所盛放的内容类别来命名,而小程序中的标签如 navigator、text、view、swiper、navigator、ad(advertisement)等主要从功能体所承担的角色来命名;
  • 其二、Web 应用开发中同样会涉及组件一说,HTML 规范中提供的标签指代的功能体叫做元素,而开发者在基础元素之上自定义出来的功能体叫做组件,小程序实际上脱胎于 Web 应用,它提供的基础组件类似于在 HTML 元素之上进行的封装,自然也就叫做组件啦,这一点也可以查阅 WXML 模板 | 微信开放文档

本专栏中在强调内容特点的时候会使用「元素」,其它情况下都会使用「组件」。

在 WXML 中,书写标签以指代组件的特定格式叫做「标签对」。以一个 view 视图组件为例,我们使用 <view></view> 来表示,其中 <view> 叫做开始标签,</view> 叫做结束标签,view 组件的主要功能是作为容器,容器中要盛放的内容元素或其它组件嵌套至开始标签与结束标签之间,比如上一节中的 <view><open-data></open-data></view>。标签的层层嵌套就构成了整个页面结构,在嵌套关系中,层级高的组件通常叫做「祖先组件」,层级低的组件叫做「后代组件」,相邻层级的两个组件中,层级高的组件为「父组件」,层级低的组件为「子组件」,前一个例子中,view 组件就是 open-data 的父组件,open-data 组件是 view 组件的子组件。在「页面结构树」中,每一个组件都是一个节点,类似地也有「祖先节点」、「后代节点」、「父节点」、「子节点」等称呼。

文本是最为常见的内容元素,也有对应的 <text></text> 组件供使用,但对于常规的文本来说,实际上我们会将文本直接贴在容器中,而不是使用专门的 text 组件,比如 <view>这里是文本</view>,这样做一是比较方便,二是在有大量文本内容的情况下可以提升渲染的性能,需要注意的是这里的文本并不是组件,但在「页面结构树」中是一个节点。关于 text 组件更详细的介绍,请查阅:组件 - 基础内容 - text | 微信开放文档

页面组件树、页面结构树、页面节点树等类似的说法都是强调「树」这种结构,不同的表述不过是从不同的角度来描述这棵树罢了 🤪 不必拘泥于此。

为了使组件更好地发挥作用,我们可以在开始标签中对组件进行更加明确的描述,比如,使用 class 为标签分类 <view class="class_name"></view> ,分配唯一的 ID <view id="id_name"></view> 等,像 classid 这样为组件提供额外信息的字段叫做「属性」,一个组件能够接受哪些属性是写在组件自身的约定中的,组件内部也能够接收到传递给它的某个属性的值(「属性值」),进而能够通过「属性值」来调整自己的表现,还是以 open-data 组件为例,当把 type 属性设置为 userAvatarUrl 的时候,它就会呈现当前用户的头像,当把 type 属性设置为 userNickName 的时候,它就会呈现当前用户的昵称。

  1. <!-- 呈现用户头像 -->
  2. <open-data type="userAvatarUrl"></open-data>
  3. <!-- 呈现用户昵称 -->
  4. <open-data type="userNickName"></open-data>

正如男女同是人,但分别具有鲜明的形态性格特点一样,所有组件都拥有一组共同的属性,比如上面提到的 classid,而特定类型的组件又具有自己特有的属性,比如 open-data 的 type 属性。所有组件的公共属性,请查阅 组件 - 公共属性 | 微信开放文档 ,特定类型组件的自有属性,请查阅 组件 | 微信开放文档

所有组件在使用之前都需要事先约定,但好在并不是所有组件都需要开发者亲自拟写约定,小程序框架中已经内置了相当多常用的组件,开箱即用,按用途来分包括:视图容器、基础内容、表单组件、媒体组件、导航、地图、画布等,应有尽有,几乎可以满足开发者对于页面的一切想象。对于简单的小程序来说,官方提供的组件已经完全够用了,但对于更加复杂一些的应用,考虑到代码复用以及应用的可维护性等因素,开发者也可以在基础组件之上「自定义组件」,完成之后的页面其实也算一个自定义组件。组件更加详细的介绍,请查阅: 组件 | 微信开放文档

从技术实现的角度来说,预置组件还可以分为基础组件、原生组件、开放能力组件等,基础组件就是实现中规中矩,没啥 Trick 的组件,比如 view、text、image、button 等,原生组件由微信客户端创建,涉及到小程序和微信客户端的「地下交易」,之后用到的时候我们会详细介绍,开放能力组件最典型的应用莫过于我们已经使用过的 open-data 组件,通过定制化的处理,open-data 组件让开发者可以在无需请求用户授权、不经手用户个人数据的情况下在应用界面中呈现用户信息,既方便了开发者,又避免了泄露用户隐私的风险。

CSS & WXSS

对于计算机来说,要为页面中的某个元素添加样式,它会问自己两个问题:「为谁添加样式」、「添加什么样式」。对于开发者来说,除了要帮助计算机回答这两个问题之外,还要搞清楚「有哪些样式可以添加」。

在 Web 应用开发中,为元素或组件添加样式的方式有两种,一种叫做内联样式,将样式当作属性写在开始标签中,如: <button style="background-color: green;">这个按钮的背景是绿色</button>,另一种叫做样式表,样式表作为内容写在 HTML 特定的样式标签 style 中,如 <style type="text/css"> p {color: red;} </style>,也可以写在单独的 CSS 文件中(后缀名为 .css),单独的样式文件需要在 HTML 中使用 link 标签引入,如 <link rel="stylesheet" type="text/css" href="./style.css"></link>,作为内容写在 style 标签中的方式通常叫做「内部样式表」,写在单独文件中的方式通常叫做「外部样式表」。

在微信小程序中,只支持使用内联样式外部样式表,内联样式非常简单但也很局限,以下我们讨论的是如何通过外部样式表为组件定义和添加样式。关于选择器的介绍涉及到诸多举例,它们都基于 WXML 结构展开:

<view class="avatar">
  <open-data type="userAvatarUrl"></open-data>
</view>
<view class="btn-group">
  <view class="btn">选择当前头像</view>
  <view class="btn">选择其它图片</view>
  <view id="camera" class="btn">拍照作为头像</view>
</view>
<view class="footer">
  <text class="btn">@cigaret</text>
  <text class="btn link">thoughts.vip</text>
</view>

这段 WXML 整体是上一节中完成的首页标签结构,为了方便举例说明选择器的用途,在其基础之上添加了一个 Footer 区域用于放置作者信息,并且为 拍照作为头像 这个选项分配了唯一的 ID camera

为谁添加样式

首先是「为谁添加样式」,我们需要有一种方式能够明确地指出页面中的任何一个或一组节点,这种方式就是「选择器」,「选择器」是一种符合特定规则的描述,根据能力的不同主要分为三种,分别是「基础选择器」、「组合选择器」和「伪选择器」,三种主要选择器下又细分为多种,以下仅对各种选择器的使用方式和存在意义作简单的梳理,详细的介绍推荐大家查阅:CSS Selectors | MDN Web Docs

基础选择器

基础选择器是单个的描述,其中包括「标签选择器」如使用 view 选中页面中的所有 view 组件,「Class 选择器」如使用 .btn 选中所有被归类为 btn 的组件,包括三个 view 视图组件和两个 text 文本组件,「ID 选择器」如 #camera 选中 <view id="camera" class="btn">拍照作为头像</view>

同时,Class 选择器不受组件类型的限制,只使用 Class 选择器会选择应用了目标 class 的所有类型的组件,在上面关于 Class 选择器的例子中,要想选择类别为 btn 的 text 组件的话,可以使用 text.btn,意即将标签选择器和 Class 选择器组合使用,并且 ,Class 选择器可以组合多个,比如使用 text.btn.link 选取既被归类于 btn,又被归类于 link 的 text 组件。

ID 选择器也可以与标签选择器和 Class 选择器组合,比如使用 view#cameraview#camera.btn 或者 view.btn#camera 选中 <view id="camera" class="btn">拍照作为头像</view> 这一 view 组件。

其实标签选择器和 Class 选择器在其它地方也有体现,如果你用过 Mac 系统的话,使用「文件夹 + 标签」的方式组织文件与使用「标签选择器 + Class 选择器」选取和定位组件的方式是完全一致的。页面组件树结构和文件目录树结构不可避免地是线性的,但 Class 选择器和标签提供了一种不局限于线性结构的节点选取方式!

组合选择器

组合选择器就是将两个以上的基础选择器进行联列,基础选择器是命名式的描述,检索的时候会按照页面组件树逐个组件判断是否符合目标描述,而组合选择器将组件在树中的相对位置也引入进来。比如,使用 .btn-group + view.footer 选择与 class 为 btn-group 的组件同级且紧跟其后的 class 为 footer 的 view 组件,这里的「+」就是「相邻兄弟选择器」,再比如使用 .footer > text.btn 选择以 class 为 footer 的组件为父组件的 class 为 btn 的 text 组件,这里的「>」叫做「子选择器」,类似的还有选择范围更加宽松的「通用兄弟选择器」和「后代选择器」,我们最常用的是 「后代选择器」。原则上组合选择器可以为任意长度,无论组件嵌套层级多深都能够精准选取。

<!-- .btn-group + view.footer 选中的组件 -->
<view class="footer">
  ...
</view>
<!-- .footer > text.btn 选中的组件 -->
<text class="btn">@cigaret</text>

伪选择器

伪选择器主要分为「伪类选择器」和「伪元素选择器」两种。其中伪类选择器又主要包括两种,一种可以实现更加精确的定位,具体到同级或同类型中的第几个组件,比如使用 view > view:first-child 选中以 view 组件为父组件,且是父组件第一个子组件的 view 组件,即 <view class="btn">选择当前头像</view>,另一种与组件的状态有关,可以精确到组件是否被点击、是否被访问过、是否被禁用等情况,比如使用 .btn:active 为按钮添加被点击时候的样式。伪元素选择器可以通过 CSS 向页面中添加非真实存在于组件树中的内容元素,最常使用的伪元素选择器就是 ::before::after 分别用来在组件前后添加内容,比如使用 text.link::before<text class="btn link">thoughts.vip</text> 后面添加内容,添加的内容会在页面中渲染出来,但不算组件树中的真实节点。

略微有点尴尬的是,虽然以上介绍的选择器在实际中大都可以使用,但在 WXSS 的官方文档中(WXSS - 选择器 | 微信开放文档)明确表明支持的选择器却只有 Class 选择器、ID 选择器、标签选择器、::after 伪元素选择器、::before 伪元素选择器这五种,在官方给出更加明确的说明之前,大家在生产中运用选择器的时候还是应该做好向下兼容。具体哪些选择器亲测可用,大家可以参考:微信小程序之样式选择器 | 简书用户

实现基本页面 - 图1

还记得上一节中组织页面内容元素(一个头像和 3 个按钮)的时候吗,我们在「将 4 个元素平铺为同一级」和「按照上下结构分别安放」之间选择了后者,这里我们的权衡是「优先使用高可读性的内容结构排布方式和简单、基础的选择器」而不是「追求极致扁平的内容结构和精准复杂的选择器」,我们的代码一方面是写给计算机看的,另一方面也是写给人看的 👏

添加什么样式

其实选择器的逻辑非常像是「你托我向隔壁班的某位同学递个小纸条,而我又不认识那位同学」,这个时候你需要想办法让我理解「目标同学」是谁,实现的方法可以是告诉我他(她)的姓名、音容笑貌、衣着打扮、位置信息等等,确保我能搞清楚目标是谁之后,你只需要将纸条给我就好了,我会将纸条传到符合特定描述的同学手里。当我们要向计算机传达为某项组件添加某些样式的时候,让计算机能够找到「目标组件」的方式就是选择器,选择器确定好之后,我们只需要将样式信息告诉计算机即可,它会将我们为选择器指定的样式信息添加给选择器匹配到的组件。

.css.wxss 文件就是为选择器指定样式信息的地方,指定样式的方式也非常直观,选择器后加一对花括号,将样式信息写到花括号中就好啦,在花括号中,每一项样式都以「键值对」表示,如下样式表代码中, color: white 就是键值对, color 是键名, white 是键值,多项样式之间使用 ; 隔开:

#camera {
  font-weight: bold;
  background-color: blue;
    color: white;
  line-height: 2;
}
view.footer {
  font-weight: bold;
    background-color: black;
  color: white;
  line-height: 2;
}

上面的代码中,我们为「拍照作为头像」的按钮设置了蓝色背景、白色文字、文字加粗和两倍行距,为 Footer 区域设置了黑色背景并将区域内的文字设置为白色、加粗、两倍行距,如果有更多组件应用同样的文字样式的话,按照上面的写法会重复多次 color: white; font-weight: bold; line-height: 2;,大大增加文件体积,解决这个问题的一种方式是将共同的样式合并书写,如下:

#camera, view.footer {
    font-weight: bold;
  color: white;
  line-height: 2;
}
#camera {
    background-color: blue;
}
view.footer {
    background-color: black;
}

#camera, view.footer 叫做「选择器列表」,此后,如果再有组件要使用同样的一组样式,只需要将对应的选择器加到选择器列表中即可。但是,每次有新组件都要确定选择器,将选择器加到样式表中也是比较麻烦的。根据样式的特点,我们完全可以将这套样式定义成一个「文字样式类」,比如 .font-style-normal,如果有组件要使用这套样式的话,将 font-style-normal 添加到该组件的 class 中即可,共同的样式信息都集中在一个地方也使得后续更改只需要修改一个地方即可,有效降低了管理成本。根据这个思路,我们还可以提前定义出更多常见的组件样式,比如个人信息卡片、抽屉导航、下拉列表、轮播图等等,将这些东西集中在一个样式表中,就是「组件样式库」啦,一点都不神秘对叭 😋

以上,提取共同的样式类的确解决了「共性」的问题,那么「个性」的问题该怎么办呢,还是以上文中提到的 WXML 为例:

<view class="avatar">
  <open-data type="userAvatarUrl"></open-data>
</view>
<view class="btn-group">
  <view class="btn">选择当前头像</view>
  <view class="btn">选择其它图片</view>
  <view id="camera" class="btn">拍照作为头像</view>
</view>
<view class="footer">
  <text class="btn">@cigaret</text>
  <text class="btn link">thoughts.vip</text>
</view>

想象这样一种情境,我想让所有的按钮背景颜色都是蓝色,但「拍照作为头像」背景颜色为绿色,应该如何操作?为所有的按钮设置蓝色背景很简单,将 background-color: blue; 添加到 .btn 的样式信息中即可,可如果想要其中的一个与众不同,难道我要将分别定义每一个按钮的样式吗?这样做未免过于麻烦。按照常规的思路,我们应该「先总结大家的共性,然后再点名描述个性」,即先为所有的按钮添加蓝色背景,然后再单独点名「拍照作为头像」的按钮背景为绿色,这也符合我们表述需求的逻辑。CSS 全称 Cascading Style Sheets,译作层叠样式表,层叠是其核心特性,该特性表示样式信息可以互相覆盖,并约定了一系列覆盖的规则,这些规则体现在样式表间是「层叠性」,体现在选择器中是「优先级」、体现在样式本身是「继承性」,脱胎于 CSS 的 WXSS 也具有这些特性。

组件本身是不带任何样式的,之所以在我们还未定义和添加样式之前组件可以呈现特定的形态是因为小程序底层准备了一张「看不见的样式表」,这张样式表为组件预置了基础样式,而开发者明确定义的样式表中的样式信息覆写了组件预置的部分样式,这是「样式表的层叠性」。样式表覆写样式属于不完全覆写,大概过程可以理解为将所有样式表按照样式表被导入的顺序合并到一张表中,合并之后的样式表中所有的样式中,相同选择器的同「键名」的样式信息会按照「后来居上」的规则进行覆盖。

/* 先引入 a 样式表 */
.header {
    color: white;
  background-color: black;
}
/* 再引入 b 样式表*/
.header {
    color: green;
  font-size: 24px;
}
/* 最终 .header 的样式信息 */
.header {
  background-color: black;
  color: green;
  font-size: 24px;
}

不同的选择器也会指向相同的组件,即不同选择器为同一组件同一样式设置不同的值,此时样式信息的覆盖会按照选择器的权重进行覆写,权重大的选择器中的样式信息会覆盖权重小的选择器中的同「键名」样式信息,这是「选择器的优先级」,选择器的权重有一套计算规则,常用基础选择器的权重从大到小依次为:ID 选择器 > Class 选择器 > 标签选择器 = 伪元素选择器,详细信息以及组合选择器的权重计算规则,请研读:层叠与继承 | MDN Web Docs,权重相同的选择器中同「键名」样式按照后来居上的规则进行覆盖。

/* 样式表中的样式 */
page .header {
    color: green;
  font-size: 24px;
}
.header {
    color: white;
  background-color: black;
}
#unique {
    font-size: 16px;
  background-color: blue;
}
/* <view id="unique" class="header"></view> 最终应用的样式 */
{
    color: green;
  font-size: 16px;
  background-color: blue;
}

在组件层面上, colorfont-size 等部分样式可以按照组件树从祖先组件传递到后代组件中,即这些元素是可继承的,这一特征叫做「继承性」。由于继承性的存在,我们可以简单地通过为祖先组件设置样式来更改所有后代组件的未被设置的相应样式,而无需为组件树中的每个组件都单独设置样式,这一特性也可以用来解决上面提到的样式信息重复、徒增文件体积的问题,但只适用于部分属性。

在微信小程序中,样式表还自带作用域的机制,开发者为每个页面定义的样式表只会应用在该页面中,在其它页面中不可用,而定义在应用级样式表 app.wxss 中的样式可以在每个页面中使用。当我们涉及到自定义组件的时候,我们还会接触到「组件级别的样式隔离」。

细心的朋友可能已经注意到了,在上一节中,虽然微信小程序提供了 button 基础组件,但我们在绘制按钮的时候却选择使用通用的 view 视图组件来绘制,作此抉择的一个原因就是因为 button 组件自带样式比较顽固,不容易覆写,而且 button 组件的预置样式与微信风格高度一致,如果此时选择覆写,之后在需要用到的时候还将面临去除覆写的问题,徒增烦恼,另外一个原因是我们用到的功能只有「点击」, view 组件完全可以满足。

有哪些样式可以添加

具体有哪些样式可以添加其实是一个经验性的问题,大家最好去查阅:样式列表 | MDN Web Docs,为了接下来的内容大家可以顺利阅读,这里先简单罗列一些常用的样式。

布局

组件在界面中的位置其实有 X、Y、Z 三个维度,原则上我们可以让某个组件出现在页面的任何位置,包括屏幕的可见区域和不可见区域,X 轴和 Y 轴的位置可以通过 top、right、bottom、left 四个样式项来调整,它们分别表示组件距离参考容器的上、右、下、左四个边缘的距离,参考容器主要通过 position 样式项来设置,Z 轴一般可以用 z-index 样式项来设置。

弹性盒子布局 Flex 和 网格布局 Grid 允许开发者设置布局规律,相应的样式项组合能够直接向计算机传达「让这个组件的子组件横着排,排到放不下为止,然后换行继续排 🤐」这种语义描述,这样设置的布局可以自动根据组件的大小、屏幕的大小等动态调整,而不需要开发者写单独的逻辑实时计算并修改组件的位置,这种特性也叫做「响应式」。响应式布局也常常与「媒体查询」一起使用,所谓媒体查询就是提前为不同屏幕尺寸的设备定义多套样式,用户的设备会根据当前屏幕的实际尺寸自动判断应该使用哪套样式,媒体查询也可以用来实现 Dark Mode / Light Mode 的自动切换。

盒子

每个组件都采用叫做「盒模型」的机制,在这个机制中,组件的实际大小由外边距(margin)、边框(border)、内边距(padding)、内容的大小共同决定。虽然我们可以通过 width 和 height 直接设置组件的大小,但 width 和 height 的计算方式受另外一个样式项 box-sizing 影响,box-sizing 可以控制组件在计算 width 和 height 的时候是「边框 + 内边距 + 内容大小」还是「内边距 + 内容大小」还是「内容大小」。

实现基本页面 - 图2

盒子还可以设置可见性、透明度、背景颜色、背景图片、滤镜、阴影、圆角等,其中每一类样式都有足够多的样式项。

文本

文本方面有字体、字号、加粗、斜体、上下标、颜色、背景色、阴影等。

其它

坐标变换、过渡动画、帧动画等,颜色、尺寸的表示方式和单位也丰富多样。

页面实现

趁热打铁,接下来我们按照「确认图片 → 选择贴图 → 合成完毕」的顺序来完成其它页面的开发,首先,依次创建好每个页面文件,快捷创建步骤为:在 pages 目录下新建页面文件夹,右键单击创建好的文件夹,选择 新建 Page,页面创建完成的同时,开发者工具会自动帮我们将新建好的页面路径添加到 app.json 中。需要注意的是,虽然开发者工具会主动替我们去做许多琐事,但它不是神,偶尔也会犯错,作为开发者的我们还是应该知道每个最终需要显示出来的页面,都需要在 app.json 中进行配置以通知计算机。

了解代码的运行逻辑可以有力地帮助我们定位和解决偶尔出现的小错误。

页面都创建完毕之后,我们的 pages 目录结构如下:

| - pages/
  | - avatar-confirm/
  | - composition-complete/
  | - index/
  | - sticker-select/
  | - app.js
  | - app.json
  | - app.wxss

将页面文件统一安置在同名文件夹下仅仅是出于自认为合理和整齐,你完全可以采用其它结构 😋

app.json 配置文件中 pages 字段是一个列表,该列表用于配置本应用包括的页面,其中的第一项将默认作为应用的首页。在我们动手实现页面和承接和跳转之前,为了实时预览页面的开发情况,需要手动将当前开发的页面暂时设为应用的首页。

确认图片

我们暂时将「确认图片」页面设置为应用的首页, app.json 配置如下:

{
     "pages": [
    "pages/avatar-confirm/avatar-confirm",
    "pages/index/index",
    "pages/sticker-select/sticker-select",
    "pages/composition-complete/composition-complete"
  ],
  ...
}

如果你没有调整过 avatar-confirm 的页面文件的话,修改完 app.json 文件之后保存编译,微信开发者工具中小程序预览视图中显示的内容为:「miniprogram/pages/avatar-confirm/avatar-confirm.wxml」,接下来打开 avatar-confirm.wxml,开始搭建页面内容结构,该页面原型(「确认图片」页面原型 | Xiaopiu)如下:

image.png

根据原型图 ,该页面分为上下两部分,页面的上半部分放置一个 image 组件,下半部分放置两个横向排列的按钮,按钮文字分别为「更换图片」和「选择贴图」,我们将 WXML 设计为如下,几乎与首页无异:

<!--miniprogram/pages/avatar-confirm/avatar-confirm.wxml-->
<view>
  <image></image>
</view>
<view>
  <view>更换图片</view>
  <view>选择贴图</view>
</view>

此时刷新预览,会发现按钮显示无误,但页面上方并没有图片,参考官方文档的描述(组件 - 媒体组件 - image | 微信开放文档 ),image 组件接受一个 src 属性,值为图片的地址, 为了方便,我们暂时先使用腾讯云开发的 Logo 😉

云开发 Logo 地址为:https://tencentcloudbase.github.io/favicon.png

将图片地址传递给 image 组件之后,保存编译查看预览,图片果然呈现出来啦。沿用首页的命名方式,我们为组件添加一些 class,为定义样式做准备,完成之后 WXML 如下:

<!--miniprogram/pages/avatar-confirm/avatar-confirm.wxml-->
<view class="avatar">
  <image src="https://tencentcloudbase.github.io/favicon.png"></image>
</view>
<view class="btn-group">
  <view class="btn">更换图片</view>
  <view class="btn">选择贴图</view>
</view>

编写完成的 avatar-confirm.wxss 如下,为了方便入门的同学查看,我将部分样式的讲解以注释的形式添加到代码中啦:

/* miniprogram/pages/avatar-confirm/avatar-confirm.wxss */
page {
  /* vh、vw 分别是尺寸单位,全称为 viewheight、viewwidth,意即视图高度和视图宽度,100 意即 100% */
  height: 100vh;
  width: 100vw;
  /* 采用 flex 布局方式 */
  display: flex;
  /* 设置子组件纵向排布,默认为自上而下 */
  flex-direction: column;
  /* 设置纵向的子组件分布方式 */
  justify-content: space-around;
  /* 设置横向的子组件居中排布 */
  align-items: center;
}

.avatar {
  width: 80vw;
  height: 80vw;
}

.avatar image {
  /* 设置 image 组件宽高均为父组件的 100%,即撑满父组件 */
  width: 100%;
  height: 100%;
}

.btn-group {
  width: 100%;
  display: flex;
  /* 对比 page 的 flex 布局设置,如果不指定 flex-direction 的话,子组件默认为从左到右横向排布 */
  /* 以下两项分别是横向和纵向的子组件排布方式,具体对应哪个方向取决于 flex-direction 的设置 */
  /* 设置横向的子组件分布方式 */
  justify-content: space-around;
  /* 设置纵向的子组件居中排布 */
  align-items: center;
}

.btn {
  width: 40%;
  /* px 是尺寸单位,意即 pixel,这里是 15 像素 */
  font-size: 15px;
  /* 行高设置为 3 倍字号,在这里相当于设置为 45px */
  line-height: 3;
  /* 设置文本对齐方式为居中 */
  text-align: center;
  /* 设置边框圆角半径为 30 像素 */
  border-radius: 30px;
  /* 
       设置文字颜色为白色,rgb 是一种颜色表示方式,rgb 三者取值区间均为[0, 255]
     rgba 中的 a 代表透明度,取值区间为 [0, 1] 
   */
  color: rgba(255, 255, 255, 1);
  /* 设置背景颜色 */
    background-color: rgba(153, 153, 153, 1);
}

各种编程语言都有特定的注释格式,在 HTML & WXML 中是 <!-- 注释 -->,在 CSS & WXSS 中是 \* 注释 *\,在 JavaScript 中单行注释为 // 注释,多行注释为 /* 注释 */

注释一来是写给日后的自己看的,二来也是写给协作者看的,它的作用主要是说明「为什么」,而不是像上面一样写「是什么」🙅 大家要养成良好的注释习惯,不要效仿上面的教学行为哦!

本页面开发成果示例如下:

image.png

选择贴图

开发「选择贴图」页面之前,先通过 app.json 中的 pages 列表将其暂时设为应用的首页,该页面原型(「选择贴图」页面原型 | Xiaopiu)如下:

image.png

「选择贴图」页面相较前两个页面稍微复杂一点,页面仍然分为上下两个部分,上半部分是头像显示区域,包含原始头像和贴图两个图片,下半部分是操作区域,包含「贴图选择区域」和一个「确认」按钮,「贴图选择区域」支持横向滚动,可以使用 scroll-view 组件实现(组件 - 视图容器 - scroll-view | 微信开放文档)。

我们将该页面 WXML 设计为如下,:

<!--miniprogram/pages/sticker-select/sticker-select.wxml-->
<view>
    <image src="https://tencentcloudbase.github.io/favicon.png"></image>
  <image src="https://tencentcloudbase.github.io/favicon.png"></image>
</view>
<view>
    <scroll-view>
        <image src="https://tencentcloudbase.github.io/favicon.png"></image>
        <image src="https://tencentcloudbase.github.io/favicon.png"></image>
        <image src="https://tencentcloudbase.github.io/favicon.png"></image>
        <image src="https://tencentcloudbase.github.io/favicon.png"></image>
        <image src="https://tencentcloudbase.github.io/favicon.png"></image>
    </scroll-view>
    <view>确认</view>
</view>

根据官方文档的介绍,scroll-view 组件需要稍微配置一下才能表现为预期的样子,将 scroll-x 属性值设置为 true 让它可以横向滚动,而滚动需要空间,所以为其指定宽度为 width: 100%,并添加 white-space: nowrap 使子组件不自动换行。配置完成之后,顺便为必要的组件插入 class ,为添加样式做准备,此时 WXML 代码如下所示:

<!--miniprogram/pages/sticker-select/sticker-select.wxml-->
<view class="avatar-area">
    <image class="avatar" src="https://tencentcloudbase.github.io/favicon.png"></image>
  <image class="sticker-selected" src="https://tencentcloudbase.github.io/favicon.png"></image>
</view>
<view class="operation-area">
    <scroll-view scroll-x="true" style="width: 100%; white-space: nowrap;">
        <image src="https://tencentcloudbase.github.io/favicon.png"></image>
        <image src="https://tencentcloudbase.github.io/favicon.png"></image>
        <image src="https://tencentcloudbase.github.io/favicon.png"></image>
        <image src="https://tencentcloudbase.github.io/favicon.png"></image>
        <image src="https://tencentcloudbase.github.io/favicon.png"></image>
    </scroll-view>
    <view class="btn">确认</view>
</view>

本页面样式代码示例如下:

/* miniprogram/pages/sticker-select/sticker-select.wxss */
page {
  width: 100vw;
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  align-items: center;
}

.avatar-area {
  width: 80vw;
  height: 80vw;
  /* position 设为 relative,子组件将以它为参照组件进行定位 */
  position: relative;
}

.avatar-area image.avatar {
  width: 100%;
  height: 100%;
}

.avatar-area image.sticker-selected {
  width: 30%;
  height: 30%;
  /* 设置 position 为 absolute 之后可以让 top, right, bottom, left 等生效 */
  position: absolute;
  right: 0;
  bottom: 0;
}

.operation-area {
  width: 100%;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
}

/* 我们将 scroll-view 的内联样式也统一移到样式表中啦
     结构归结构,样式归样式,降低耦合,便于管理 👍
 */
.operation-area scroll-view {
  width: 90%;
  height: 15vh;
  white-space: nowrap;
  /* border 为 border-width, boder-style, border-color 的简写*/
  border: 2px solid black;
}

.operation-area scroll-view image {
  width: 15vh;
  height: 15vh;
}

.operation-area .btn {
  width: 60%;
  /* margin 为 margin-top, margin-right, margin-bottom, margin-left 的简写 */
  margin: 5vh 0 0 0;
  font-size: 15px;
  line-height: 3;
  border-radius: 30px;
  text-align: center;
  color: rgba(255, 255, 255, 1);
    background-color: rgba(153, 153, 153, 1);
}

本页面开发成果示例如下:

image.png

合成完毕

「合成完毕」页面并没有十分特别的地方,该页面原型(「合成完毕」页面原型 | Xiaopiu)如下:

image.png

该页面 WXML、 WXSS 示例代码分别如下:

<!--miniprogram/pages/composition-complete/composition-complete.wxml-->
<view class="avatar">
  <image src="https://tencentcloudbase.github.io/favicon.png"></image>
</view>
<view class="btn-group">
  <view class="btn btn-vice">再做一个</view>
  <view class="btn btn-primary">保存至本地</view>
</view>
/* miniprogram/pages/composition-complete/composition-complete.wxss */
page {
  width: 100vw;
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  align-items: center;
}

.avatar {
  width: 80vw;
  height: 80vw;
}

.avatar image {
  width: 100%;
  height: 100%;
}

.btn-group {
  width: 100%;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
}

.btn {
  width: 60%;
  margin: 2.5vh 0;
  font-size: 15px;
  line-height: 3;
  border-radius: 30px;
  text-align: center;
  color: rgba(255, 255, 255, 1);
    background-color: rgba(153, 153, 153, 1);
}

.btn-vice {
  color: rgba(128, 128, 128, 1);
    background-color: rgba(255, 255, 255, 1);
    border: rgba(0, 0, 0, 1) solid 3px;
}

.btn-primary {
  color: rgba(255, 255, 255, 1);
    background-color: rgba(67, 207, 124, 1);
    border: rgba(0, 0, 0, 1) solid 3px;
}

初学的朋友体会一下此处两个按钮样式的实现哦 ☝

该页面开发成果示例如下:

image.png

小结

本节中,我们一起了解了标签和组件的概念,明白了一个个基础的组件如何构建出一个完整的应用,我们一起梳理了为组件和页面添加样式的逻辑,选择器作为中介,一边用于定位页面内容和组件,一边用于添加样式信息,计算机会将二者进行匹配,从而完成样式的添加,我们还简单认识了有哪些样式是可以实现的。以这些内容为基础,我们完成了「🎭头像定制」简单版本所有页面的开发工作,看起来还不错,很棒 👍

在下一节中,我们将着手进行功能的开发工作,在此之前,大家要尽可能熟练地掌握组件的使用样式的添加哦,之后再遇到就不会再停下来单独介绍啦!

by-nc-sa-4.0.png
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.