跳转到内容

脚本和事件处理

你可以在不使用 UI 框架(如 React、Svelte、Vue 等)的情况下,通过标准的 HTML <script> 标签为你的 Astro 组件添加交互性。这允许你发送 JavaScript 在浏览器中运行,并为你的 Astro 组件添加功能。

脚本可以用来添加事件监听器、发送分析数据、播放动画,以及 JavaScript 在 web 上能做的所有事情。

src/components/ConfettiButton.astro
<button data-confetti-button>Celebrate!</button>
<script>
// Import npm modules.
import confetti from 'canvas-confetti';
// Find our component DOM on the page.
const buttons = document.querySelectorAll('[data-confetti-button]');
// Add event listeners to fire confetti when a button is clicked.
buttons.forEach((button) => {
button.addEventListener('click', () => confetti());
});
</script>

默认情况下,Astro 会处理和打包 <script> 标签,增加了对导入 npm 模块、编写 TypeScript 等功能的支持。

.astro 文件中,你可以通过添加一个(或多个)<script> 标签来添加客户端 JavaScript。

在此示例中,将 <Hello /> 组件添加到页面会在浏览器控制台中打印一条消息。

src/components/Hello.astro
<h1>Welcome, world!</h1>
<script>
console.log('Welcome, browser console!');
</script>

默认情况下,<script> 标签由 Astro 处理。

  • 任何导入都将被打包,允许你导入本地文件或 Node 模块。
  • 处理后的脚本将以type="module"的形式注入到其声明的位置。
  • 完全支持 TypeScript,包括导入 TypeScript 文件。
  • 如果你的组件在页面上多次使用,该脚本将只被包含一次。
src/components/Example.astro
<script>
// Processed! Bundled! TypeScript-supported!
// Importing local scripts and Node modules works.
</script>

type="module" 属性使浏览器将脚本视为 JavaScript 模块。这有几个性能优势:

  • 渲染不会被阻塞。在模块脚本及其依赖项加载时,浏览器会继续处理 HTML 的其余部分。
  • 浏览器会等待 HTML 处理完毕后再执行模块脚本。你不需要监听 “load” 事件。
  • asyncdefer 属性是不必要的。模块脚本总是被延迟执行。

要阻止 Astro 处理脚本,请添加 is:inline 指令。

src/components/InlineScript.astro
<script is:inline>
// Will be rendered into the HTML exactly as written!
// Local imports are not resolved and will not work.
// If in a component, repeats each time the component is used.
</script>
有关 <script> 标签上可用指令的更多信息,请参阅我们的指令参考页面。

你可能希望将脚本编写为单独的 .js/.ts 文件,或者需要引用另一台服务器上的外部脚本。你可以通过在 <script> 标签的 src 属性中引用它们来实现。

何时使用: 当你的脚本位于 src/ 目录中时。

Astro 将为你构建、优化这些脚本并将其添加到页面中,遵循其脚本处理规则

src/components/LocalScripts.astro
<!-- relative path to script at `src/scripts/local.js` -->
<script src="../scripts/local.js"></script>
<!-- also works for local TypeScript files -->
<script src="./script-with-types.ts"></script>

何时使用: 当你的 JavaScript 文件位于 public/ 目录或 CDN 上时。

要加载项目 src/ 文件夹之外的脚本,请包含 is:inline 指令。这种方法会跳过上述导入脚本时 Astro 提供的 JavaScript 处理、打包和优化。

src/components/ExternalScripts.astro
<!-- absolute path to a script at `public/my-script.js` -->
<script is:inline src="/my-script.js"></script>
<!-- full URL to a script on a remote server -->
<script is:inline src="https://my-analytics.com/script.js"></script>

一些 UI 框架使用自定义语法进行事件处理,例如 onClick={...} (React/Preact) 或 @click="..." (Vue)。Astro 更紧密地遵循标准 HTML,不使用自定义事件语法。

相反,你可以在 <script> 标签中使用addEventListener来处理用户交互。

src/components/AlertButton.astro
<button class="alert">Click me!</button>
<script>
// Find all buttons with the `alert` class on the page.
const buttons = document.querySelectorAll('button.alert');
// Handle clicks on each button.
buttons.forEach((button) => {
button.addEventListener('click', () => {
alert('Button was clicked!');
});
});
</script>

你可以使用 Web Components 标准创建具有自定义行为的 HTML 元素。在 .astro 组件中定义一个自定义元素,可以让你在不需要 UI 框架库的情况下构建交互式组件。

在这个例子中,我们定义了一个新的 <astro-heart> HTML 元素,它会跟踪你点击心形按钮的次数,并用最新的计数更新 <span>

src/components/AstroHeart.astro
<!-- Wrap the component elements in our custom element “astro-heart”. -->
<astro-heart>
<button aria-label="Heart">💜</button> × <span>0</span>
</astro-heart>
<script>
// Define the behaviour for our new type of HTML element.
class AstroHeart extends HTMLElement {
connectedCallback() {
let count = 0;
const heartButton = this.querySelector('button');
const countSpan = this.querySelector('span');
// Each time the button is clicked, update the count.
heartButton.addEventListener('click', () => {
count++;
countSpan.textContent = count.toString();
});
}
}
// Tell the browser to use our AstroHeart class for <astro-heart> elements.
customElements.define('astro-heart', AstroHeart);
</script>

在这里使用自定义元素有两个优点:

  1. 你可以使用 this.querySelector(),它只在当前自定义元素实例中搜索,而不是使用 document.querySelector() 搜索整个页面。这使得一次只处理一个组件实例的子元素变得更加容易。

  2. 虽然 <script> 只运行一次,但浏览器每次在页面上找到 <astro-heart> 时都会运行我们自定义元素的 connectedCallback() 方法。这意味着你可以安全地一次只为一个组件编写代码,即使你打算在页面上多次使用这个组件。

你可以在 web.dev 的可复用 Web 组件指南MDN 的自定义元素介绍中了解更多关于自定义元素的信息。

在 Astro 组件中,frontmatter 中位于 --- 之间的代码在服务器上运行,在浏览器中不可用。要将变量从服务器发送到客户端,我们需要一种方法来存储我们的变量,然后在浏览器中运行 JavaScript 时读取它们。

一种方法是使用 data-* 属性将变量的值存储在 HTML 输出中。脚本(包括自定义元素)可以在 HTML 加载到浏览器后,使用元素的 dataset 属性读取这些属性。

在这个示例组件中,一个 message prop 被存储在一个 data-message 属性中,因此自定义元素可以读取 this.dataset.message 并在浏览器中获取该 prop 的值。

src/components/AstroGreet.astro
---
const { message = 'Welcome, world!' } = Astro.props;
---
<!-- Store the message prop as a data attribute. -->
<astro-greet data-message={message}>
<button>Say hi!</button>
</astro-greet>
<script>
class AstroGreet extends HTMLElement {
connectedCallback() {
// Read the message from the data attribute.
const message = this.dataset.message;
const button = this.querySelector('button');
button.addEventListener('click', () => {
alert(message);
});
}
}
customElements.define('astro-greet', AstroGreet);
</script>

现在我们可以多次使用我们的组件,并且每次都会收到不同的问候消息。

src/pages/example.astro
---
import AstroGreet from '../components/AstroGreet.astro';
---
<!-- Use the default message: “Welcome, world!” -->
<AstroGreet />
<!-- Use custom messages passed as a props. -->
<AstroGreet message="Lovely day to build components!" />
<AstroGreet message="Glad you made it! 👋" />

<script> 标签执行时,由 UI 框架渲染的元素可能还不可用。如果你的脚本还需要处理UI 框架组件,建议使用自定义元素。

贡献 社区 赞助