☁️

第2课:云上金句

调用公网 API 获取实时数据

2 课时 JavaScript 进阶 需要网络

← → 键切换 · 点击底部导航跳转

1 / 24

🎯 这节课做什么

☁️ 点击按钮 → 从互联网拿到一句金句
不用自己存数据了,全世界都是你的数据库!
🧠

理解 API

API 是什么?请求和响应是怎么回事?

📦

认识 JSON

服务器返回的数据长什么样子

📡

fetch()

浏览器内置函数,用来调用 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(应用程序接口)

🧑 你(前端)
→ "我要炒饭" →
👨‍🍳 服务员(API)
→ "做炒饭" →
👨‍🍳 厨房(服务器)
← "炒饭好了" ←
🧑 你(吃上了)
💡

翻译成编程语言

你(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-18body { ... }和之前一样:flex居中 + 渐变色背景
21background: rgba(255,255,255,0.05)半透明白色背景
22backdrop-filter: blur(20px)⚠️ 新知识!背景模糊=毛玻璃效果
23border: 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行)

42display: inline-block只占自己内容宽
43padding: 4px 12px内间距小圆片
44border-radius: 20px完全圆角
45background: rgba(167,139,250,0.15)紫色半透明背景
46color: #a78bfa紫色文字

.quote-display(第52-59行)

53min-height: 140px比上节课高一些
54-56display: flex; flex-direction: column; justify-content: centerflex垂直排列+纵向居中
57align-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 标签的可点击标题
127cursor: 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);
}
192await fetch(url)⚠️ await + fetch = 发送请求到服务器
等服务器返回响应
193await response.json()⚠️ 把响应数据转成 JS 对象
response 是原始响应,.json() 解析它
197JSON.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 可能用 quotetext
每次换 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 = '🌐 从云端获取';
}

try

可能出错的代码
正常时顺序执行

🛡️

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