import ReadMore from '~/components/ReadMore.astro'

Вы можете добавить интерактивность в компоненты Astro без использования UI-фреймворка, такого как React, Svelte, Vue и т. д., используя стандартный HTML тег <script>. Это позволяет отправлять JavaScript в браузер и добавлять функциональность в компоненты Astro.

Клиентские скрипты

Скрипты могут использоваться для того, чтобы добавить обработчики событий, отправить аналитические данные, анимировать элементы и многого другого, что JavaScript может делать в Интернете.

astro
<!-- src/components/ConfettiButton.astro --><button data-confetti-button>Celebrate!</button><script>  // Импорт модулей npm.  import confetti from 'canvas-confetti';  // Находим наш компонент DOM на странице.  const buttons = document.querySelectorAll('[data-confetti-button]');  // Добавляем обработчики событий, чтобы запускать конфетти при нажатии на кнопку.  buttons.forEach((button) => {    button.addEventListener('click', () => confetti());  });</script>

По умолчанию Astro обрабатывает и собирает теги <script>, добавляя поддержку импорта модулей npm, написания TypeScript и т. д.

Использование <script> в Astro

В файлы .astro можно добавить клиентский JavaScript, добавив один (или несколько) тегов <script>.

В этом примере добавление компонента <Hello /> на страницу приведет к выводу сообщения в консоль браузера.

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

Обработка скриптов

По умолчанию теги <script> обрабатываются Astro.

  • Любые импорты будут скомпонованы, что позволит вам импортировать локальные файлы или модули Node.
  • Обработанный скрипт будет вставлен в <head> вашей страницы с помощью type="module".
  • TypeScript поддерживается полностью, включая импорт файлов TypeScript.
  • Если ваш компонент используется на странице несколько раз, скрипт будет добавлен в сборку и на страницу только единожды.
astro
<script>  // Обработан! Собран! Поддерживает TypeScript!  // Импорт локальных скриптов и модулей Node работает.</script>

Атрибут type="module" заставляет браузер воспринимать скрипт как модуль JavaScript. Это дает несколько преимуществ в плане производительности:

  • Рендеринг не блокируется. Браузер продолжает обрабатывать остальную часть HTML, пока загружается скрипт модуля и его зависимости.
  • Браузер ожидает обработки HTML перед выполнением модульных скриптов. Вам не нужно прослушивать событие "load".
  • Атрибуты async и defer не нужны. Модульные скрипты всегда откладываются.

:::note Атрибут async полезен для обычных скриптов, поскольку он не позволяет им блокировать рендеринг. Однако модульные скрипты уже имеют такое поведение. Добавление async к модульному скрипту приведет к тому, что он будет выполняться до полной загрузки страницы. Вероятно, это не то, что вам нужно. :::

Отказ от обработки

Чтобы запретить Astro обрабатывать скрипт, добавьте директиву is:inline.

astro
<script is:inline>  // Будет выведено в HTML именно так, как написано!  // Локальные импорты не разрешены и не будут работать.  // Если в компоненте, то повторяется каждый раз, когда компонент используется.</script>

:::note В некоторых ситуациях Astro не будет обрабатывать теги скриптов. В частности, добавление type="module" или любого другого атрибута, кроме src, к тегу <script> приведет к тому, что Astro будет обрабатывать тег так, как если бы он содержал директиву is:inline. То же самое будет справедливо, если скрипт записан в выражении JSX. :::

Подробнее о доступных директивах на тегах <script> смотрите на странице справочника директив.

Включение файлов javascript на страницу

Возможно, вы захотите написать свои скрипты в виде отдельных файлов .js/.ts или вам понадобится сослаться на внешний скрипт на другом сервере. Вы можете сделать это, сославшись на них в атрибуте <script> тега ``src`.

Импорт локальных скриптов

Когда это использовать: когда ваш скрипт находится внутри src/.

Astro создаст, оптимизирует и добавит эти скрипты на страницу за вас, следуя своим правилам обработки скриптов.

astro
<!-- относительный путь к скрипту в `src/scripts/local.js` --><script src="../scripts/local.js"></script><!-- также работает для локальных файлов TypeScript --><script src="./script-with-types.ts"></script>

Загрузка внешних скриптов

Когда использовать: когда ваш JavaScript-файл находится внутри public/ или на CDN.

Чтобы загрузить скрипты вне папки src/ вашего проекта, включите директиву is:inline. Этот подход позволяет обойтись без обработки, сборки и оптимизации JavaScript, которые обеспечивает Astro, когда вы импортируете скрипты, как описано выше.

astro
<!-- абсолютный путь к скрипту по адресу `public/my-script.js` --><script is:inline src="/my-script.js"></script><!-- полный URL к скрипту на удаленном сервере --><script is:inline src="https://my-analytics.com/script.js"></script>

Общие шаблоны скриптов

Обработка onclick и других событий

Некоторые UI-фреймворки используют собственный синтаксис для обработки событий, например onClick={...} (React/Preact) или @click="..." (Vue). Astro больше следует стандартному HTML и не использует пользовательский синтаксис для событий.

Вместо этого вы можете использовать addEventListener в теге <script> для обработки взаимодействия с пользователем.

astro
<button class="alert">Click me!</button><script>  // Находит все кнопки с классом `alert` на странице.  const buttons = document.querySelectorAll('button.alert');  // Обрабатывает клик на всех кнопках.  buttons.forEach((button) => {    button.addEventListener('click', () => {      alert('Button was clicked!');    });  });</script>

:::note Если на странице имеется несколько компонентов <AlertButton />, Astro не будет запускать скрипт несколько раз. Скрипты собираются и добавляются на страницу единожды. Использование querySelectorAll гарантирует, что скрипт добавит обработчики событий к каждой кнопке с классом alert, найденной на странице. :::

Веб-компоненты с пользовательскими элементами

Вы можете создавать собственные HTML-элементы с пользовательским поведением, используя стандарт Web Components. Определение пользовательского элемента в компоненте .astro позволяет создавать интерактивные компоненты без использования библиотеки UI-фреймворка.

В этом примере мы определяем новый HTML-элемент <astro-heart>, который отслеживает количество нажатий на кнопку сердца и обновляет <span> актуальным значением.

astro
<!-- Оборачиваем компонент в наш пользовательский элемент "astro-heart". --><astro-heart>  <button aria-label="Heart">💜</button> × <span>0</span></astro-heart><script>  // Определяем поведение для нашего нового типа HTML-элемента.  class AstroHeart extends HTMLElement {    constructor() {			super();      let count = 0;      const heartButton = this.querySelector('button');      const countSpan = this.querySelector('span');      // При каждом нажатии на кнопку обновляем счетчик.			heartButton.addEventListener('click', () => {        count++;        countSpan.textContent = count.toString();      });		}  }  // Сообщаем браузеру, чтобы он использовал наш класс AstroHeart для элементов <astro-heart>.  customElements.define('astro-heart', AstroHeart);</script>

Использование пользовательского элемента здесь имеет два преимущества:

  1. Вместо поиска по всей странице с помощью document.querySelector(), вы можете использовать this.querySelector(), который ищет только в пределах текущего экземпляра пользовательского элемента. Это позволяет упростить работу с дочерними элементами внутри элемента.

  2. Хотя <script> выполняется только один раз, браузер будет запускать метод constructor() нашего пользовательского элемента каждый раз, когда найдет на странице <astro-heart>. Это означает, что вы можете смело писать код для одного компонента, даже если собираетесь использовать этот компонент несколько раз на странице.

Подробнее о пользовательских элементах вы можете узнать из руководства по многоразовым веб-компонентам web.dev и введения в пользовательские элементы MDN.

Передача переменных frontmatter в скрипты

В компонентах Astro код в frontmatter между кодовым забором --- выполняется на сервере и недоступен в браузере. Чтобы передавать переменные с сервера на клиент, нам нужен способ хранить наши переменные и затем считывать их при запуске JavaScript в браузере.

Один из способов сделать это - использовать атрибуты data-* для хранения значений переменных в HTML-выводах. Скрипты, включая пользовательские элементы, могут считывать эти атрибуты с помощью свойства dataset элемента, когда HTML загружается в браузере.

В этом примере компонента свойство message хранится в атрибуте data-message, поэтому пользовательский элемент может прочитать this.dataset.message и получить значение этого свойства в браузере.

astro
---const { message = 'Welcome, world!' } = Astro.props;---<!-- Храним реквизит сообщения как атрибут данных. --><astro-greet data-message={message}>  <button>Say hi!</button></astro-greet><script>  class AstroGreet extends HTMLElement {    constructor() {			super();      // Считываем сообщение из атрибута данных.      const message = this.dataset.message;      const button = this.querySelector('button');      button.addEventListener('click', () => {        alert(message);      });		}  }  customElements.define('astro-greet', AstroGreet);</script>

Теперь мы можем использовать наш компонент несколько раз, и каждый раз нас будет приветствовать разное сообщение.

astro
---import AstroGreet from '../components/AstroGreet.astro';---<!-- Используйте сообщение по умолчанию: “Welcome, world!” --><AstroGreet /><!-- Использовать пользовательские сообщения, передаваемые в качестве пропса. --><AstroGreet message="Lovely day to build components!" /><AstroGreet message="Glad you made it! 👋" />

:::tip[Знаете ли вы?] Это как раз то, что Astro делает под капотом, когда вы передаете пропсы компоненту, написанному с использованием UI-фреймворка, например React! Для компонентов с директивой client:* Astro создаёт кастомный элемент <astro-island> с атрибутом props, который и хранит все серверные данные в HTML. :::