# 第11次课 讲稿

> 课程：小程序开发
> 主题：动画与用户登录
> 教材：《微信小程序开发实战（第2版）》第5章
> 时长：3学时（理论 1 + 实践 2）
> 重点：CSS动画、wx.createAnimation、微信登录流程
> 难点：Animation API关键帧、登录态维护

---

## 一、开场（0:00）

今天进入第5章，两个主题：动画和登录。

这两件事放在一起讲有点奇怪，但它们各自都很重要。动画决定了你的小程序"看起来专业不专业"；登录决定了你的小程序"能不能真正做业务"。

先讲动画。小程序里做动画有两条路：**CSS 动画**和 **Animation API**。我先说结论，避免大家纠结：**能用 CSS 就用 CSS**。CSS 动画跑在渲染层，不用跨线程通信，性能更好、代码更少。只有当动画的参数需要在运行时根据数据算出来（比如罗盘要跟着方位角转）时，才用 Animation API。

登录部分，我要先纠正一个普遍的误解：**wx.login 拿到的 code 不是用户身份**，它只是一张一次性的兑换券，必须由你自己的服务器拿去换 openid。为什么不能在小程序里直接换？因为换的时候要带上 AppSecret，那东西一旦泄露，别人就能冒充你的小程序。

---

## 二、明确本课目标（约 5:00）

把目标写在白板上或直接翻到课件第 2 页，逐条念一遍：

- 掌握CSS动画与wx.createAnimation
- 理解微信登录的整体流程
- 掌握openid的获取方式
- 能够实现用户登录与信息展示
- 案例：案例5-1 罗盘动画、案例5-2 用户登录

本次课 3 学时的安排是：**理论讲解 1 学时，上机实践 2 学时**——动画与登录流程讲解 1 学时，案例 5-1/5-2 实操 2 学时。

---

## 三、知识点讲解

### 幻灯片 3：CSS动画

**【8:00 讲解】**

**transition**

过渡动画(hover状态变化)

**@keyframes**

关键帧动画

**animation**

动画属性(name/duration/timing-function/delay)

**transform**

变换(translate/rotate/scale)

- 小程序中CSS动画与Web一致

### 幻灯片 4：wx.createAnimation

**【13:00 讲解】**

- wx.createAnimation({duration, timingFunction, delay})
- animation.opacity() / rotate() / scale() / translate()
**animation.step()**

表示一组动画完成

**export()**

导出动画数据到视图

- 绑定: <view animation="{{animationData}}"></view>

### 幻灯片 5：微信登录流程

**【18:00 讲解】**

**wx.login()**

获取临时登录凭证code

- 将code发送到开发者服务器
- 服务器用code换取openid和session_key
**wx.getUserProfile()**

获取用户信息(头像/昵称)

- 自定义登录态: 3rd_session / token

### 幻灯片 6：案例5-1 罗盘动画 / 案例5-2 用户登录

**【23:00 讲解】**

**案例5-1 罗盘动画**

wx.onCompassChange 取方位角，wx.createAnimation 旋转罗盘

**案例5-2 用户登录**

wx.login 取 code，wx.getUserProfile 取头像与昵称

- 动画绑定：animation="{{animationData}}"，每组动画 step() 后必须 export()
- 登录态维护：自定义 token 存入 storage，后续请求统一携带并处理过期
- 综合运用动画与登录 API 完成两个教材案例

---

## 四、难点突破：用类比讲清楚

下面这几个点是学生最容易卡住的地方，抽象讲不通就换成类比。

### CSS 动画 vs Animation API

CSS 动画像预先录好的一段舞蹈，音乐一响自动跳完；Animation API 像现场遥控的机械臂，每一步你都得下指令。前者省事，后者灵活。

### wx.login 的 code

code 像演唱会门口的兑换券——有效期五分钟，只能用一次，而且必须到指定窗口（你的服务器 + AppSecret）才能换成真正的门票（openid + session_key）。你自己在门口撕开看，什么信息都没有。

### 登录态 token

openid 像身份证号，不该反复在网络上传；token 像进园区发的临时通行证，有有效期、可以吊销、丢了也不致命。所以正规做法是服务器发 token，客户端存 token，每次请求带 token。

### animation.step()

step 是"这一组动作算一个节拍"。opacity().rotate().step() 表示透明度和旋转同时发生；分成两个 step 就是先做完一个再做下一个。export() 是把整段编排递给舞台。

---

## 五、代码演示环节

以下每一段都建议**现场敲**，不要直接贴完整代码。边敲边问"下一行该写什么"，让学生跟上节奏。

### 演示 1：CSS动画

**文件：** `css-animation.wxss`　**预期效果：** CSS动画效果

```css
/* transition 过渡动画 */
.box {
width: 100rpx;
height: 100rpx;
background: #07c160;
transition: all 0.3s ease;
}
.box:active {
transform: scale(0.9);
opacity: 0.8;
}

/* @keyframes 关键帧动画 */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20rpx); }
to { opacity: 1; transform: translateY(0); }
}

/* animation 属性 */
.fade-in {
animation: fadeIn 0.5s ease-out forwards;
}

/* transform 变换 */
.rotate { transform: rotate(45deg); }
.scale-up { transform: scale(1.5); }
.slide { transform: translateX(100rpx); }

/* ⚠️ 小程序动画注意 */
/* 1. 不支持 animation-name 等拆分写法，需简写 */
/* 2. transform 需硬件加速可加 perspective */
/* 3. transition 只能过渡数值型属性 */
```

**讲解口径：**

小程序 CSS 动画有三种方式：**transition** 过渡动画，用于状态变化时的平滑过渡，如点击缩放、悬停变色；**@keyframes** 定义关键帧，配合 **animation** 属性实现循环或一次性动画；**transform** 变换包括 rotate 旋转、scale 缩放、translate 平移。注意小程序不支持 animation 拆分写法，必须用简写形式，transition 只能过渡数值型属性。

### 演示 2：wx.createAnimation

**文件：** `create-animation.js`　**预期效果：** createAnimation 动画流程

```js
// 创建动画实例
Page({
data: { animationData: {} },

onReady() {
this.animation = wx.createAnimation({
duration: 1000,
timingFunction: 'ease-in-out',
delay: 0,
transformOrigin: '50% 50% 0'
})
},

animate() {
// 透明度动画
this.animation.opacity(0.5).step()

// 旋转动画
this.animation.rotate(180).step({ duration: 800 })

// 缩放动画
this.animation.scale(1.5, 1.5).step()

// 平移动画
this.animation.translate(50, 0).step()

// 导出动画数据
this.setData({
animationData: this.animation.export()
})
}
})

<!-- WXML 绑定动画 -->
<view animation="{{animationData}}">
</view>

// ⚠️ 关键要点
// 1. 每调用 step() 表示一组动画结束
// 2. export() 导出动画数据给 setData
// 3. 同一 step 内多个变换会同时执行
// 4. 不同 step 之间按顺序依次执行
```

**讲解口径：**

wx.**createAnimation** 创建 JS 动画实例，设置 duration 时长、timingFunction 缓动函数。调用动画方法如 **opacity**、**rotate**、**scale**、**translate** 后必须调用 **step()** 标记一组动画结束。同一 step 内的变换同时执行，不同 step 按顺序依次执行。最后调用 **export()** 导出动画数据，通过 **setData** 绑定到 WXML 的 animation 属性上。

### 演示 3：微信登录流程

**文件：** `login-flow.js`　**预期效果：** 微信登录流程图

```js
// 微信登录完整流程
Page({
data: { userInfo: {} },

// 第一步：调用 wx.login 获取 code
handleLogin() {
wx.login({
success: (loginRes) => {
const code = loginRes.code
// 第二步：将 code 发送到自己的服务器
this.requestOpenId(code)
}
})
},

// 第二步：服务器用 code 换取 openid
requestOpenId(code) {
wx.request({
url: 'https://api.example.com/login',
data: { code: code },
success: (res) => {
const { openid, token } = res.data
wx.setStorageSync('token', token)
}
})
},

// 获取用户信息（需用户主动触发）
getUserInfo() {
wx.getUserProfile({
desc: '用于完善个人资料',
success: (res) => {
const { nickName, avatarUrl } = res.userInfo
this.setData({ userInfo: res.userInfo })
// 将用户信息发送到服务器
this.uploadUserInfo(res.userInfo)
}
})
}
})

// ⚠️ 登录流程要点
// 1. wx.login 获取的 code 有效期5分钟
// 2. code 只能使用一次，不能复用
// 3. getUserProfile 必须由用户点击触发
// 4. openid 是用户唯一标识，不可直接传给前端
```

**讲解口径：**

微信登录流程：**wx.login** 获取临时登录凭证 code，code 有效期5分钟且只能用一次；将 code 发送到开发者服务器，服务器用 code 加上 appid 和 appsecret 请求微信接口换取 **openid** 和 session_key；openid 是用户唯一标识，不能直接传给前端。获取用户信息用 **wx.getUserProfile**，必须由用户点击触发，desc 参数说明用途，返回昵称、头像等信息。

### 演示 4：案例5-1罗盘动画+案例5-2用户登录

**文件：** `compass-login.js`　**预期效果：** 罗盘动画 & 用户登录

```js
// 案例5-1：罗盘动画 compass.js
Page({
data: { rotate: 0 },

onReady() {
this.animation = wx.createAnimation({
duration: 400,
timingFunction: 'ease-out'
})
// 监听设备方向
wx.onDeviceOrientationChange((res) => {
const angle = res.alpha  // 0-360
this.animation.rotate(-angle).step()
this.setData({ animationData: this.animation.export() })
})
},

onUnload() {
wx.stopDeviceOrientationListening()
}
})

// 案例5-2：用户登录 login.js
Page({
data: { hasLogin: false, userInfo: {} },

onLoad() {
const token = wx.getStorageSync('token')
if (token) this.setData({ hasLogin: true })
},

handleLogin() {
wx.login({
success: (res) => {
this.getUserProfile(res.code)
}
})
},

getUserProfile(code) {
wx.getUserProfile({
desc: '用于完善会员资料',
success: (profileRes) => {
const { nickName, avatarUrl } = profileRes.userInfo
// 将 code + 用户信息发送到服务器
wx.request({
url: 'https://api.example.com/auth/login',
method: 'POST',
data: { code, nickName, avatarUrl },
success: (res) => {
wx.setStorageSync('token', res.data.token)
this.setData({ hasLogin: true, userInfo: profileRes.userInfo })
}
})
}
})
}
})
```

**讲解口径：**

案例5-1罗盘动画：用 **wx.onDeviceOrientationChange** 监听设备方向变化，获取 alpha 角度后通过 **createAnimation** 的 rotate 方法让罗盘指针反向旋转，**step + export + setData** 更新视图。页面卸载时停止监听。案例5-2用户登录：先 **wx.login** 获取 code，再 **getUserProfile** 获取用户信息，将 code 和用户信息一起 POST 到服务器，服务器返回 token 存入本地存储维持登录态。

---

## 六、常见错误与排查

上机环节学生一定会遇到下面这些问题，提前讲一遍能省掉大量重复答疑。

| # | 现象 | 原因与解决 |
|:-:|------|------------|
| 1 | Animation 动画只播一次 | animationData 内容没变化时不会重新触发。需要每次重新 export() 赋值，或者先重置状态。 |
| 2 | getUserProfile 调不起来 | 它必须由用户点击直接触发，写在 onLoad 或异步回调里会被拒绝。另外 getUserInfo 获取昵称头像的能力已被收回，新项目一律用头像昵称填写组件或 getUserProfile。 |
| 3 | 把 AppSecret 写进小程序代码 | 严重安全问题。AppSecret 只能存在服务端，课上一定要强调。 |
| 4 | session_key 过期后解密失败 | session_key 会随用户重新登录而失效，服务端要有失效重登的处理逻辑。 |
| 5 | transform 动画卡顿 | 用 transform 和 opacity 做动画能走合成层；改 width/height/top/left 会触发重排，在低端机上明显掉帧。 |

---

## 七、上机实践指引

本次课对应教材案例 2 个，源码已放在站点 `code/session11/` 下：

- **【案例5-1】罗盘动画** — 含 知识储备、案例实现 共 2 个工程
- **【案例5-2】用户登录** — 含 知识储备、案例实现、服务器端 共 3 个工程

建议顺序：先带学生跑一遍「知识储备」里的最小示例，确认每个知识点都能单独跑通，再进「案例实现」做完整案例。直接上完整案例，出错了不知道错在哪。

---

## 八、本课小结

回到课件小结页，逐条回顾：

- CSS动画
- wx.createAnimation
- 微信登录流程
- 案例5-1罗盘动画+案例5-2用户登录

今天的两个案例，案例5-1 罗盘动画是"数据驱动动画"的典型——传感器给角度，动画跟着转；案例5-2 用户登录是所有商业化小程序的第一道门。

登录这块的流程图，建议大家自己在纸上画一遍：小程序 → code → 自己的服务器 → 微信接口 → openid → token → 小程序。画得出来才算真懂了。

---

## 九、布置作业

三档任务，基础必做，进阶建议做，挑战选做。

### 【基础】完成罗盘动画

按照案例5-1，结合罗盘API和动画API实现指南针旋转效果

### 【进阶】完成用户登录

按照案例5-2，实现完整的登录流程，包含token存储和globalData共享

### 【挑战】动画交互页面

创建一个动画展示页面，包含多种动画效果（旋转、缩放、平移、渐变），支持用户点击触发不同动画组合

---

> 配套资源：课件 `slides-v2/session11.pptx`　课堂视频 `videos-output/session11.mp4`　扩展阅读 `docs/第11次课_扩展阅读.md`
