0%

已完成 0/12 关

教学模式

你可以随时调整。

此设置调整教师的教学方式,无需重新生成课程。

第 8 关 含代码 含表格 约 22 分钟

客户端原生交互:无需前端框架的 <script> 标签与深色模式切换

客户端原生交互:无需前端框架的 <script> 标签与深色模式切换

很多初学者在学习网页制作时,常常被灌输一种错误观念:“如果网页需要点一下按钮弹出一个菜单,或者切换深色模式,就必须引入 React 或 Vue 这样的大型框架”。

Astro 官方教程在第六单元明确指出:“回到干燥的陆地——别忘了仅仅使用原始的 JavaScript 和 CSS,你就能走得很远!”

在很多场景下,我们只需要几十行原生 JavaScript 来监听点击事件,完全没必要为了一个展开按钮引入几百 KB 的框架运行时代码。Astro 对标准 HTML <script> 标签提供了极其强大的自动化打包和优化支持。

今天,我们将通过制作响应式汉堡菜单和深浅色主题切换按钮,掌握纯原生交互的极简魅力!


1. 核心概念与新术语白话解析

(1) 客户端脚本(Client-side Script)

  • 它是什么:在 .astro 模板中写的标准 HTML <script> 标签(或引入的 .js 文件)。
  • 为什么需要它:当用户在手机或电脑上点击、滑动、输入时,浏览器需要执行事件监听逻辑(例如 element.addEventListener('click', ...))来实时修改网页 DOM。
  • 与组件脚本(Frontmatter)的本质区别:
对比维度组件脚本(顶部代码围栏 ---)客户端脚本(<script> 标签)
执行地点服务端 / 本地构建机(Node.js 环境)用户的浏览器(Browser 环境)
执行时机构建静态页面时只执行一次用户在网页上交互时随时触发
是否有 DOM没有(试图访问 window 或 document 会直接报错)有(可以自由操作 document.getElementById)
代码传输代码计算完即丢弃,绝不发给浏览器代码会被 Astro 打包压缩后发送给浏览器

(2) Astro 对 <script> 的自动优化机制

  • 在 Astro 中,<script> 标签默认被视为 ES 模块(JavaScript Module)。
  • Astro 会自动为你处理所有 <script> 标签:自动压缩代码、自动分析依赖,最厉害的是:如果同一个组件在页面里被引用了 5 次,该组件自带的 <script> 也只会被打包加载一次,绝不会造成脚本重复执行!

(3) 本地存储(localStorage)

  • 它是什么:浏览器提供的一个微型持久化键值数据库。
  • 为什么需要它:即使用户刷新了网页或关闭浏览器第二天再打开,通过 localStorage.getItem("theme") 依然能记住用户选的是“深色模式”还是“浅色模式”。

2. 动手实操一:制作响应式移动端汉堡菜单

在小屏幕手机上,导航链接太多会挤压变形。标准的做法是隐藏导航链接,并在右上角显示一个三道杠的“汉堡图标”,点击后展开菜单。

第一步:创建汉堡菜单按钮组件 Hamburger.astro

在 src/components/ 目录下新建 Hamburger.astro:

---
// src/components/Hamburger.astro
---

<div class="hamburger">
  <span class="line"></span>
  <span class="line"></span>
  <span class="line"></span>
</div>

<style>
  .hamburger {
    padding-right: 20px;
    cursor: pointer;
  }
  .hamburger .line {
    display: block;
    width: 30px;
    height: 3px;
    margin-bottom: 7px;
    background-color: #ff9776;
    border-radius: 2px;
  }
  /* 在宽屏电脑上隐藏汉堡图标 */
  @media screen and (min-width: 636px) {
    .hamburger {
      display: none;
    }
  }
</style>

第二步:更新全局样式以支持菜单展开/折叠

打开 src/styles/global.css,在文件末尾追加以下响应式 CSS 样式:

/* 移动端默认隐藏导航链接,拥有 .expanded 类时显示 */
.nav-links {
  width: 100%;
  display: none;
}

.nav-links.expanded {
  display: unset;
}

/* 宽屏设备直接平铺展示导航 */
@media screen and (min-width: 636px) {
  .nav-links {
    display: block;
    position: static;
    width: auto;
    background: none;
  }
}

第三步:编写客户端交互脚本并抽离为模块

  1. 在 src/ 目录下新建 scripts 文件夹,并在其中创建 menu.js(路径:src/scripts/menu.js):
// src/scripts/menu.js
document.querySelector('.hamburger')?.addEventListener('click', () => {
  document.querySelector('.nav-links')?.classList.toggle('expanded');
});
  1. 打开 src/components/Header.astro,引入 <Hamburger />,并在底部加上 <script> 导入该交互模块:
---
// src/components/Header.astro
import Hamburger from './Hamburger.astro';
import Navigation from './Navigation.astro';
---

<header>
  <nav>
    <Hamburger />
    <Navigation />
  </nav>
</header>

<script>
  import '../scripts/menu.js';
</script>

<style>
  header {
    margin-bottom: 2rem;
    padding-bottom: 1rem;
    border-bottom: 1px solid #e2e8f0;
  }
</style>

保存后切回浏览器,把浏览器窗口缩窄到类似手机屏幕的宽度:

  • 顶部的三个导航链接自动收起隐藏;
  • 右侧出现了漂亮的三条橙色短杠;
  • 点击汉堡图标,导航栏平滑展开;再次点击,导航栏收起折叠! 整个过程没有引入任何第三方库,纯原生代码,运行速度极快。

3. 动手实操二:实现深色/浅色模式切换器 ThemeIcon.astro

现在让我们跟随官方教程第六单元,制作一个酷炫的昼夜模式切换按钮。

第一步:创建 ThemeIcon.astro 组件

在 src/components/ 目录下新建 ThemeIcon.astro:

---
// src/components/ThemeIcon.astro
---

<button id="themeToggle" aria-label="切换深色/浅色主题">
  <svg width="24px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
    <path class="sun" fill-rule="evenodd" d="M12 17.5a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11zm0 1.5a7 7 0 1 0 0-14 7 7 0 0 0 0 14z" />
    <path class="moon" fill-rule="evenodd" d="M16.5 18a8.5 8.5 0 0 1-11-11 8.5 8.5 0 1 0 11 11z" />
  </svg>
</button>

<style>
  #themeToggle {
    border: 0;
    background: none;
    cursor: pointer;
    padding: 0.5rem;
  }
  .sun { fill: black; }
  .moon { fill: transparent; }

  /* 当 html 标签拥有 .dark 类时,反转日月图标显示 */
  :global(.dark) .sun { fill: transparent; }
  :global(.dark) .moon { fill: white; }
</style>

<script>
  // 1. 初始化读取本地偏好
  const theme = (() => {
    if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
      return localStorage.getItem('theme');
    }
    if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
      return 'dark';
    }
    return 'light';
  })();

  if (theme === 'light') {
    document.documentElement.classList.remove('dark');
  } else {
    document.documentElement.classList.add('dark');
  }

  window.localStorage.setItem('theme', theme);

  // 2. 绑定点击切换逻辑
  const handleToggleClick = () => {
    const element = document.documentElement;
    element.classList.toggle('dark');
    const isDark = element.classList.contains('dark');
    localStorage.setItem('theme', isDark ? 'dark' : 'light');
  };

  document.getElementById('themeToggle')?.addEventListener('click', handleToggleClick);
</script>

第二步:在全局样式中支持深色主题变量

打开 src/styles/global.css,在末尾加入深色模式的全局视觉规则:

/* 深色模式下的全局配色 */
html.dark {
  background-color: #0f172a;
  color: #f8fafc;
}

html.dark a {
  color: #38bdf8;
}

html.dark .post-meta {
  background-color: #1e293b;
}

第三步:把切换按钮放进 Header.astro

打开 src/components/Header.astro,在导航栏旁边加入切换图标:

---
// src/components/Header.astro
import Hamburger from './Hamburger.astro';
import Navigation from './Navigation.astro';
import ThemeIcon from './ThemeIcon.astro';
---

<header>
  <nav style="display: flex; align-items: center; justify-content: space-between;">
    <div style="display: flex; align-items: center;">
      <Hamburger />
      <Navigation />
    </div>
    <ThemeIcon />
  </nav>
</header>

保存后刷新网页,点击右上角的太阳图标:

  • 整个网站瞬间切换为炫酷的深空黑夜主题(#0f172a);
  • 图标平滑切换为白色月亮;
  • 刷新页面,或者跳转到博客文章页,依然保持深色模式,状态被 localStorage 牢牢记住!

4. 本关小结

  1. 分清战场:Frontmatter --- 在服务端执行,<script> 在用户浏览器中执行。
  2. 拒绝盲目依赖:很多轻量级交互,用标准 DOM 操作和原生 CSS 就能完成得非常出色。
  3. 性能零损耗:原生脚本无需框架加载器,打包体积几乎忽略不计,体验极致丝滑。

既然纯原生脚本这么好用,那如果我们的网站确实需要复杂的前端组件库(比如随机生成问候语、带交互状态的动态计数器、或者拖拽图表)时,Astro 又该如何应对呢? 下一关,我们将先学习 Astro 如何通过 API 和动态路由批量生成几十个页面,随后我们将正式揭开 Astro 最具革命性的招牌架构——群岛架构(Islands Architecture)!

下一站:动态路由与数据获取:getStaticPaths() 批量生成标签页与文章存档 →
© 2026 Astro 入门与基础