微信 8.0 更新的一大特色就是支持动画表情,如果发送的消息只有一个内置的表情图标,这个表情会有一段简单的动画,一些特殊的表情还有全屏特效,例如烟花表情有全屏放烟花的特效,炸弹表情有爆炸动画并且消息和头像也会随之震动。
本着前端工程师的职业精神,我就想看看能不能实现一个类似的特效。折腾许久之后,做出来的效果如下:
项目的核心是使用到了 lottie 动画库。lottie 是 Airbnb 出品的、全平台(Web、Android、IOS、React Native)的动画库,它的特点在于能够直接播放使用 Adobe After Effects 制作的动画。设计师在 After Effects 中,利用 Bodymovin 插件把动画导出为 JSON 格式之后,开发者就能够通过相应平台的 SDK 进行播放。(项目地址及示例演示见文末)
在做完这个项目之后我感觉到自己的前端储备又丰富了一层,在以后应对复杂特效时又有了新的思路,如果你也想进一步提升前端开发技能,可以跟着这篇文章实践一下。本篇文章除了使用 Lottie 库之外,全部都是使用原生 HTML/CSS/JavaScript 实现的,这样无论你是 React、Vue 还是其它工程师,都可以快速掌握。
编写界面
本来想跳过 HTML/CSS 部分,但是想到 CSS 可能是大部分人的弱项,所以我决定还是把实现界面的思路写一下,想看核心部分的可以直接跳到:二、发送普通消息部分。
1. HTML 部分
首先看 HTML 部分,从效果图来看:
上边有一个标题栏,显示与 XXX 聊天。
中间是聊天信息面板,包含着双方发送的消息,每条消息由发送者头像和消息内容组成,我发送的在右侧,对方发送的在左侧。
下方是底部信息,有表情选择按钮、编辑消息文本框和发送按钮。
那么根据这个结构编写的 HTML 代码如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<main>
<div class="chat">
<div class="titleBar">与 XXX 聊天</div>
<div class="panel">
<div class="message mine">
<img src="./me.png" alt="" />
<p><span>你好</span></p>
</div>
<div class="message yours">
<img class="avatar" src="./you.png" alt="" />
<p><span>Hi</span></p>
</div>
<!-- 省略其它消息 -->
</div>
<footer>
<button class="chooseSticker">
<img src="./emoji.svg" alt="" />
<div class="stickers"></div>
</button>
<input
class="messageInput"
type="text"
name=""
id=""
placeholder="请输入聊天信息"
/>
<button class="send">发送</button>
</footer>
</div>
</main>
各个元素所对应的界面部分为:
<main /> 元素是一个整体的容器,用于把聊天窗口居中对齐
<div class="chat"> 是聊天应用的容器,用于布局标题栏、聊天面板和底部发送框。
<div class="titleBar"> 用于显示标题栏。
<div class="panel"> 是消息面板,用于布局其中的消息。
<div class="message"> 为消息容器,使用不同的 class 来区分发送方, mine 代表我发送的, yours 代表对方发送的。每条消息里边使用 <img class="avatar" > 来展示头像,使用 <p> 元素来显示文本, <p> 元素里边的 <span> 元素将会作为 lottie 的容器来播放表情动画。
<footer> 用于布局底部操作按钮和消息发送框。其中:
<button class="chooseSticker"> 是表情选择按钮,使用一个笑脸 svg 图片表示,里边的 <div class="stickers"> 是表情选择框弹出层,里边的表情将在 JS 中动态加载,目的是为了实现动画预览。
<input class="messageInput" /> 是聊天消息输入框,没什么特别的。
<button class="send"> 是发送按钮
这个是 HTML 的基本结构,接下来看一下 CSS 样式。
2. CSS 部分
在项目根目录下创建一个 style.css 文件并在 index.html 的<head> 标签中引入:
1
<link rel="stylesheet" href="style.css" />
2.1 全局样式
首先定义一些 CSS 变量,CSS 变量是为了方便我们引用同一属性值的,后边如果更新样式时,可以避免多次修改:
1
2
3
4
5
6
:root {
--primary-color: hsl(200, 100%, 48%);
--inverse-color: hsl(310, 90%, 60%);
--shadow-large: 0 0px 24px hsl(0, 0%, 0%, 0.2);
--shadow-medium: 0 0 12px hsl(0, 0%, 0%, 0.1);
}
这些变量的含义分别是:
--primary-color: hsl(200, 100%, 48%) ,主色调,例如我发送的消息的蓝色背景。
--inverse-color: hsl(310, 90%, 60%) ,反色调,或强调色调,与主色调形成鲜明对比,例如发送按钮的背景色。
--shadow-large: 0 0px 24px hsl(0, 0%, 0%, 0.2) ,大阴影,例如标题栏、底部栏的阴影。
--shadow-medium: 0 0 12px hsl(0, 0%, 0%, 0.1) ,小阴影,例如输入框和表情选择弹出层。
接下来是一些重置样式:
1
2
3
4
5
6
* {
box-sizing: border-box;
padding: 0;
margin: 0;
font-family: Helvetica, "PingFang SC", "Microsoft Yahei", sans-serif;
}
这些样式对所有元素都有效,设置盒子模型为 border-box ,这样内边距、边框都算在宽高之内,设置内间距和外间距为 0,最后设置默认字体。
2.2 Main 容器
Main 容器用于定位聊天应用容器到浏览器中间,使用 grid 布局,宽高分别设置为浏览器可视区域的 100%,并把背景色设置为黑灰色:
1
2
3
4
5
6
7
main {
display: grid;
place-items: center;
width: 100vw;
height: 100vh;
background-color: hsl(0, 0%, 10%);
}
2.3 聊天应用容器
聊天应用容器设置了固定宽高,模拟手机屏幕,并使用 grid 布局来控制标题栏、聊天面板和底部操作栏的位置:
1
2
3
4
5
6
7
8
.chat {
width: 375px;
height: 700px;
background: hsl(0, 0%, 100%);
border-radius: 8px;
display: grid;
grid-template-rows: max-content 1fr max-content;
}
这里使用了 grid-template-rows 把聊天应用分成了 3 行,第一行的标题栏和最后一行的标底部操作栏的高度分别为内容的最大高度,中间的聊天面板则是浮动高度。
2.4 标题栏
标题栏简单的设置了一个内间距、文字居中方式和阴影:
1
2
3
4
5
.titleBar {
padding: 24px 0;
text-align: center;
box-shadow: var(--shadow-large);
}
界面优化提示:内间距用来增加留白,在视觉上引起放松,阴影则为了和下边的聊天面板区分开
2.5 聊天面板
聊天面板使用 flex 布局对其中的消息进行排列,并设置方向为按列排布,然后设置 overflow 为 auto,在消息整体高度超出面板高度时,出现滚动条:
1
2
3
4
5
6
.panel {
display: flex;
flex-direction: column;
padding: 24px 12px;
overflow: auto;
}
界面优化提示:这里的 padding 同样是为了留出足够多的空白,来与其它元素隔开一段距离,以避免拥挤感。
2.6 消息
消息分为消息容器、头像和消息体 3 个部分。其中消息体和头像包含在消息容器中,先来看消息容器的样式。消息容器使用 flex 布局来把消息体和头像放在一行,宽度最大为面板宽度的 80%,并设置字体和外边距:
1
2
3
4
5
6
7
.message {
display: flex;
max-width: 80%;
font-size: 14px;
margin: 8px 0;
position: relative;
}
这里的 position 设置为 relative 是为了定位后边的全屏特效动画。
头像简单设置了宽高、圆角和距离消息体的间距:
1
2
3
4
5
6
.message img {
width: 40px;
height: 40px;
border-radius: 12px;
margin-right: 12px;
}
界面优化提示:这里不得不再提一下间距的重要性,一定不要把各个元素安排的太过紧凑,否则十分影响视觉效果,最直接的影响就是引起视觉上的拥挤感,造成视觉疲劳。
消息体同样的设置了间距和圆角,这里的圆角和头像的保持一致,以增加和谐感。它还设置了阴影,并使用 flex 布局,把里边的文字或表情消息居中对齐:
1
2
3
4
5
6
7
.message p {
padding: 8px 12px;
border-radius: 12px;
box-shadow: var(--shadow-large);
display: flex;
align-items: center;
}
这些样式默认都是基于对方的消息的,如果是我发送的消息需要放到右边,并作一些调整。首先对于我发送的消息,把 flex-flow 改为 row-reverse 这样头像和消息体的位置就互换了,然后使用 align-self 对齐到面板的右边:
1
2
3
4
.message.mine {
flex-flow: row-reverse;
align-self: flex-end;
}
调整头像的外边距,现在应该是距离左边的消息体的边距了:
1
2
3
4
.message.mine img {
margin-right: 0;
margin-left: 12px;
}
设置消息体的背景色为蓝色,文字为白色:
1
2
3
4
.message.mine p {
background-color: var(--primary-color);
color: white;
}
2.7 底部操作栏
先看底部操作栏容器的整体布局,使用 grid 布局把表情选择按钮、消息发送框和发送按钮分成 3 列,其中除消息发送框为浮动宽度外,其它的两个按钮为固定宽度,默认居中对齐,最后设置阴影和间距:
1
2
3
4
5
6
7
footer {
display: grid;
grid-template-columns: 48px 1fr 75px;
justify-items: center;
padding: 12px;
box-shadow: var(--shadow-large);
}
表情选择按钮把自己进行了靠左对齐,并设置相对定位,用于定位表情选择弹出层,然后设置按钮图标的大小:
1
2
3
4
5
6
7
8
.chooseSticker {
justify-self: start;
position: relative;
}
.chooseSticker img {
width: 36px;
height: 36px;
}
表情选择弹出层的 CSS 代码比较多但都很简单,先看一下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.stickers {
display: grid;
grid-template-columns: repeat(auto-fill, 24px);
column-gap: 18px;
border-radius: 8px;
background-color: white;
box-shadow: var(--shadow-medium);
padding: 6px 12px;
font-size: 24px;
position: absolute;
top: calc(-100% - 18px);
width: 300px;
opacity: 0;
}
这段代码的含义是:
弹出层使用 grid 布局,repeat(auto-fill, 24px) 指的是在宽度允许的条件下,在一行中尽可能放置最多的列元素,每列的宽度固定为 24px。然后设置列间距为 18px。
设置圆角、背景色、阴影、内间距和字体大小。
定位设置为绝对定位,把它向上移动包含元素高度(也就是 .chooseSticker 的高度)的 100% 并减去 18px,调整到合适的位置。宽度设置为 300px,透明度设置为 0 把它隐藏。
消息输入框和按钮的样式比较简单,消息输入框的宽度占满整列,发送按钮使用 justify-self: end 把自己进行靠右对齐。这里把代码一次性贴出来:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
.messageInput {
box-shadow: var(--shadow-medium);
padding: 0px 12px;
border-radius: 8px;
width: 100%;
}
.send {
height: 100%;
width: 90%;
border-radius: 8px;
justify-self: end;
color: white;
background-color: var(--inverse-color);
}
最后再添加一个 .show 样式,用于在点击发送表情按钮时,给表情弹出层添加该样式以显示出来:
1
2
3
.show {
opacity: 1;
}