☁️
第2课:云上金句
调用公网 API 获取实时数据
2 课时
JavaScript 进阶
需要网络
← → 键切换 · 点击底部导航跳转
1 / 24
🎯 这节课做什么
☁️ 点击按钮 → 从互联网拿到一句金句
不用自己存数据了,全世界都是你的数据库!
🧠
理解 API
API 是什么?请求和响应是怎么回事?
🛡️
try/catch
API 挂了怎么办?优雅地处理错误
2 / 24
📄 完整代码(231行)
全部在一个 HTML 文件里,分 3 大部分:
🎨
CSS 第9-142行
134行样式
毛玻璃卡片 + 加载动画 + 调试区
🏗️
HTML 第144-164行
20行结构
卡片 + 加载圈 + 按钮 + 调试区
⚡
JS 第166-229行
63行 JS
fetch() + try/catch + 事件绑定
3 / 24
🧠 什么是 API?
API = Application Programming Interface(应用程序接口)
→ "我要炒饭" →
→ "做炒饭" →
← "炒饭好了" ←
💡
翻译成编程语言
你(JavaScript)→ "给我一句金句" → 服务器(quotable.io)
服务器 → 返回 JSON → 你收到数据展示在页面
4 / 24
🔍 这节课用的 API
我们使用 https://api.quotable.io/random
免费、无需注册、无需 API Key
GET https://api.quotable.io/random
// 返回的 JSON:
{
"content": "不积跬步...",
"author": "荀子",
"tags": ["wisdom"],
...
}
试试 在浏览器地址栏直接输入上面的网址看看
5 / 24
🎨 CSS ① — 通用重置(第9行)
* { margin: 0; padding: 0; box-sizing: border-box; }
💡
* 通用选择器
* 选中页面所有元素
一次性去掉所有默认边距
💡
box-sizing: border-box
默认:width 不包括 padding
改后:width 包括 padding
布局更不容易乱
6 / 24
🎨 CSS ② — body + 毛玻璃卡片(第10-29行)
| 10-18 | body { ... } | 和之前一样:flex居中 + 渐变色背景 |
| 21 | background: rgba(255,255,255,0.05) | 半透明白色背景 |
| 22 | backdrop-filter: blur(20px) | ⚠️ 新知识!背景模糊=毛玻璃效果 |
| 23 | border: 1px solid rgba(255,255,255,0.1) | 半透明边框 |
💡
backdrop-filter: blur() 毛玻璃
让元素后面的背景变模糊
blur(20px) = 模糊程度,越大越糊
配合半透明背景 (rgba 0.05) + 半透明边框
= 高级的「磨砂玻璃」效果
7 / 24
🎨 CSS ③ — 标签 + 展示区(第41-74行)
.api-badge(第41-49行)
| 42 | display: inline-block | 只占自己内容宽 |
| 43 | padding: 4px 12px | 内间距小圆片 |
| 44 | border-radius: 20px | 完全圆角 |
| 45 | background: rgba(167,139,250,0.15) | 紫色半透明背景 |
| 46 | color: #a78bfa | 紫色文字 |
.quote-display(第52-59行)
| 53 | min-height: 140px | 比上节课高一些 |
| 54-56 | display: flex; flex-direction: column; justify-content: center | flex垂直排列+纵向居中 |
| 57 | align-items: center | 水平居中 |
8 / 24
🎨 CSS ④ — 加载动画 spinner(第77-88行)
/* 默认隐藏 */
.spinner { display: none; width: 32px; height: 32px; }
/* 要显示时 */
.spinner.show { display: block; }
/* 定义旋转动画 */
@keyframes spin {
to { transform: rotate(360deg); }
}
💡
转圈圈的原理
border: 3px solid rgba(255,255,255,0.1)
= 3像素粗的浅灰色边框(整个圆环)
border-top-color: #a78bfa
= 上面的边框是紫色(只有一段是彩色)
border-radius: 50%
= 完全圆形
💡
@keyframes spin
@keyframes 定义动画
spin 是动画的名字
to { rotate(360deg) } = 最终转一圈
animation: spin 0.8s linear infinite
= 用spinanimation · 每0.8秒转一圈
· linear匀速 · infinite永远重复
9 / 24
🎨 CSS ⑤ — 按钮 / 错误 / 调试区(第90-141行)
.btn 按钮
| 102-105 | .btn:hover { ... } | 悬停浮起 |
| 106-110 | .btn:disabled { ... } | ⚠️ 新知识!按钮禁用样式 opacity: 0.6 半透明 cursor: not-allowed 禁止光标 |
.debug 调试区(第120-141行)
| 126 | <summary> | HTML details 标签的可点击标题 |
| 127 | cursor: pointer | 鼠标变手 |
| 131-141 | .debug pre { ... } | pre 标签样式:等宽字体+深色背景 |
💡
details + summary 标签
HTML 自带的折叠面板
点击 <summary> 展开/收起 <pre>
不需要 JS 就能实现展开/收起效果
10 / 24
🏗️ HTML 结构(第144-164行)
第146行 <h1>☁️ 云上金句</h1>
第147行 <p>从互联网实时获取...</p>
第148行 <div class="api-badge">🔗 api.quotable.io</div>
第150行 <div class="quote-display">
第151行 <div id="spinner"></div> <!-- 加载动画 -->
第152行 <div id="quoteText">点击按钮...</div>
第153行 <div id="quoteAuthor">等待...</div>
第154行 </div>
第156行 <button id="fetchBtn">🌐 从云端获取</button>
第157行 <div id="errorMsg"></div>
第160-163行 <details><summary>🔍 查看 API 原始数据</summary>
第162行 <pre id="rawData"></pre>
第163行 </details>
🆕
新增元素
#spinner = 加载动画容器
.api-badge = 显示 API 地址的标签
#errorMsg = 错误提示
#rawData = 调试信息显示区
<details> = 可折叠区域
11 / 24
⚡ JS ① — 获取元素(第167-173行)
// 这次要操作5个元素!
const quoteText = document.getElementById('quoteText');
const quoteAuthor = document.getElementById('quoteAuthor');
const fetchBtn = document.getElementById('fetchBtn');
const spinner = document.getElementById('spinner');
const errorMsg = document.getElementById('errorMsg');
const rawData = document.getElementById('rawData');
📝
每个 ID 对应什么?
quoteText → 金句文字
quoteAuthor → 作者名字
fetchBtn → 获取按钮
spinner → 转圈圈
errorMsg → 错误提示
rawData → 原始 JSON 显示
💡
和上一节课对比
上节课只操作3个元素
这节课操作6个元素
需要获取的越多,getElementById 就越多
12 / 24
⚡ JS ② — async function(第179行)
async function fetchQuote() { ... }
🆕
async 是什么意思?
async = asynchronous = 异步的
意思是这个函数里可能有需要等待的操作
(比如等待服务器响应)
和普通函数的区别:
普通函数 = 立即执行完
async函数 = 可能等一会儿才出结果
💡
生活中的类比
普通函数 → 你问同桌几点了,他直接告诉你
async 函数 → 你发微信问朋友,等他回复
等回复的这段时间,你可以做别的事!
浏览器不会卡死
13 / 24
⚡ JS ③ — 加载状态(第180-188行)
// 清空旧状态
errorMsg.style.display = 'none'; // 隐藏错误提示
quoteText.style.opacity = '0'; // 金句淡出
quoteAuthor.style.opacity = '0';
// 显示加载动画
spinner.classList.add('show'); // 显示转圈圈
fetchBtn.disabled = true; // 按钮变灰不能点
fetchBtn.textContent = '加载中...'; // 改按钮文字
🆕
classList.add('show')
给 spinner 加上 class="show"
CSS 里 .spinner.show { display: block }
= 让隐藏的转圈圈显示出来
🆕
disabled = true
按钮禁用,用户点不了
CSS 里 .btn:disabled 控制样式
防止重复点击
14 / 24
⚡ JS ④ — 核心 fetch()(第190-197行)
try {
// ★★★ 关键代码:调用 API ★★★
const response = await fetch('https://api.quotable.io/random');
const data = await response.json();
// ★★★★★★★★★★★★★★★★★★★★
// 显示原始数据
rawData.textContent = JSON.stringify(data, null, 2);
}
| 192 | await fetch(url) | ⚠️ await + fetch = 发送请求到服务器 等服务器返回响应 |
| 193 | await response.json() | ⚠️ 把响应数据转成 JS 对象 response 是原始响应,.json() 解析它 |
| 197 | JSON.stringify(data, null, 2) | 把 JS 对象转成格式化的 JSON 字符串 null=不过滤 · 2=缩进2空格 方便人类阅读 |
15 / 24
💡 await 到底是什么?
没有 await 会怎样:
// ❌ 没有 await
const response = fetch(url);
// response = Promise { <pending> }
// 还没拿到数据!
有 await 就对了:
// ✅ 有 await
const response = await fetch(url);
// response = Response { status: 200, ok: true }
// 数据已经拿到了!
💡
await = "等一等"
await 告诉 JS:"在这里等,拿到结果再继续往下执行"
网络请求要时间,await 让你安心等待
注意:await 只能在 async function 里使用
16 / 24
⚡ JS ⑤ — 提取 + 展示数据(第199-212行)
// 提取数据
// 注意:不同的 API 返回的字段名不同
const text = data.content; // 这个API用 content 字段
const author = data.author; // 这个API用 author 字段
// 展示到页面上
quoteText.textContent = text;
quoteAuthor.textContent = author;
// 淡入动画
setTimeout(function() {
quoteText.style.opacity = '1';
quoteAuthor.style.opacity = '1';
}, 50);
⚠️
字段名不同的问题
quotable.io 用 content
换成其他 API 可能用 quote 或 text
每次换 API 都要查一下它的字段名
💡
调试面板的作用
页面底部的"查看原始数据"
显示 data 的所有字段
方便你知道该用 data.什么
17 / 24
⚡ JS ⑥ — try/catch/finally(第190-224行)
try {
// 可能会出错的代码
response = await fetch(url);
data = await response.json();
// ... 正常执行的代码
} catch (err) {
// 如果 try 里出错了 → 来这里
errorMsg.textContent = '❌ 获取失败...';
errorMsg.style.display = 'block';
} finally {
// 不管有没有错 → 都会执行这里
spinner.classList.remove('show');
fetchBtn.disabled = false;
fetchBtn.textContent = '🌐 从云端获取';
}
🛡️
catch
try 里出错了
→ 跳到这里处理错误
🏁
finally
无论对错都会执行
恢复按钮/隐藏加载
18 / 24
⚡ JS ⑦ — catch 错误处理详解(第214-218行)
} catch (err) {
// err 是错误对象,包含错误信息
errorMsg.textContent = '❌ 获取失败,请检查网络或稍后再试';
errorMsg.style.display = 'block'; // 显示错误提示
rawData.textContent = '错误:' + err.message; // 显示具体错误
}
❌
什么时候会触发 catch?
• 网络断了(没连 WiFi)
• API 服务器挂了
• URL 写错了
• 跨域被阻止
总之:任何网络问题
✅
没有 catch 会怎样?
JS 报错 → 页面白屏
用户不知道发生了什么
学生:老师我的页面不动了 😭
有 catch → 显示友好提示
用户:哦原来没联网 👍
19 / 24
⚡ JS ⑧ — finally 恢复状态(第219-224行)
} finally {
// 恢复按钮状态(不管成功还是失败)
spinner.classList.remove('show'); // 隐藏转圈
fetchBtn.disabled = false; // 按钮恢复可点击
fetchBtn.textContent = '🌐 从云端获取'; // 恢复按钮文字
}
🔑
为什么要用 finally?
如果把恢复代码写在 try 里面:
如果 catch 执行了 → try 里的恢复代码不会执行
按钮就永远显示"加载中..." 🥲
finally 保证 无论什么情况都会恢复按钮状态
20 / 24
⚡ JS ⑨ — classList 操作(第186/221行)
// 加上 class
spinner.classList.add('show');
// 去掉 class
spinner.classList.remove('show');
💡
classList 是什么?
每个元素都有 classList 属性
它管理元素的所有 class
.add('show') = 给元素加上 show 这个 class
.remove('show') = 去掉 show 这个 class
.toggle('show') = 有就去掉,没有就加上
CSS 里写 .spinner.show { display: block }
JS 加上 show → CSS 生效 → 显示出来
21 / 24
⚡ JS ⑩ — 事件绑定(第227-228行)
// 和上节课一样
fetchBtn.addEventListener('click', fetchQuote);
和上节课一样!只是函数名不同而已
22 / 24
🛠️ 动手步骤(60分钟)
基于上节课的 index.html 改造:
1️⃣ 改 CSS(15min)
通用重置 → 毛玻璃卡片 → spinner → 调试区
2️⃣ 改 HTML(5min)
加 spinner / api-badge / 调试区 / 错误提示
3️⃣ 写 JS 初始(10min)
const 获取6个元素
4️⃣ 写 fetch(15min)
async function → await fetch → 展示数据
5️⃣ 加 try/catch(10min)
包裹整个 fetch → 错误提示
6️⃣ 加 finally(5min)
恢复按钮 + 隐藏加载
23 / 24
🎉
这节课学会了
✅ API 是什么(请求→响应)
✅ JSON 数据格式
✅ fetch(url) 调用 API
✅ await 等待异步结果
✅ response.json() 解析 JSON
✅ async function 声明异步函数
✅ try/catch/finally 错误处理
✅ classList.add/remove 操作 CSS 类
✅ backdrop-filter 毛玻璃效果
✅ @keyframes CSS 动画
🎊 现在你的金句来自互联网!每次点击都不一样 ✨
下节课预告 🐍 自己写一个 API!
24 / 24