mdx でモーダルウィンドウ

(2025-12-16)

astro は静的なものなので基本的には JavaScript が使えません。

astro を始めた 1 年半前には、modal window をやってみようと思って挑戦してみたのですが全然できませんでした。

今回、chatGPT の助けを借りてもう一度挑戦してみたところ割と簡単にできました。

モーダルウィンドウ(modal window)

まずは体験。以下の画像をクリックして下さい。拡大されます。

サムネイル画像を表示しておいて、それをクリックすると拡大されるので画像閲覧にはとても便利で、私はこれまでホームページやサーバー上で多用してきました。

でも、本来は注意や警告を出すことに使われることが多いようです。

md と mdx

astro は通常は md(マークダウン)で記事を書くことが多いのですが、この md は静的なものなので動的なモーダルウィンドウを設定することはできません。

ブラウザに文字を表示するためには その文字を html のいろいろな要素でお化粧する必要がありますが、 md ではただ単に文字をどんどん書いていけば html に組み込まれてそれなりの体裁に変換されます。

これはとても便利なもので、wordpress はこの方式で記事をどんどん書いていくことができます。

でも、mdx という拡張子を持つファイルで書いて、いろいろと設定するとモーダルウィンドウを始めいろいろな JSX を使うことができるようです。

JSX とは、「JavaScriptの中でHTMLみたいな構文を書ける仕組み」です。つまり、拡張子を mdx でファイルを作っていけば動的なサイトを作成できるのです。

設定方法

4 つのファイルを設定する必要があります。

tsconfig.json

ルートディレクトリにある tsconfig.json を以下のようにします。

{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}

public/scripts/modal.js の新規作成

document.addEventListener('click', (e) => {
// モーダルを開く
const trigger = e.target.closest('[data-modal-id]');
if (trigger) {
const modal = document.getElementById(trigger.dataset.modalId);
modal.classList.remove('hidden');
modal.offsetWidth;
modal.classList.remove('opacity-0');
modal.querySelector('.modal-content').classList.remove('scale-95');
}
// モーダルを閉じる(ボタン or 背景クリック)
const closeBtn = e.target.closest('[data-close]');
const modalWrapper = e.target.closest('.modal-wrapper');
if (closeBtn || (modalWrapper && e.target === modalWrapper)) {
const target = closeBtn ? document.getElementById(closeBtn.dataset.close) : modalWrapper;
target.classList.add('opacity-0');
target.querySelector('.modal-content').classList.add('scale-95');
setTimeout(() => target.classList.add('hidden'), 300);
}
});

src/components/SimpleModal.astro の新規作成

---
const { id = "modal", button = "Open modal" } = Astro.props;
---
<button data-open={id}>
{button}
</button>
<div
id={id}
class="modal-wrapper hidden fixed inset-0 transition-opacity duration-300 opacity-0 bg-black/50 flex items-center justify-center"
on:click
>
<div
class="modal-content bg-white p-4 transform transition-transform duration-300 scale-95"
on:click={(e) => e.stopPropagation()}
>
<slot />
</div>
</div>

src/layouts/MarkdownPostLayout.astro の新規作成

---
import BaseLayout from './BaseLayout.astro';
const { frontmatter } = Astro.props;
---
<BaseLayout pageTitle={frontmatter.title}>
<div class="mb-8 text-right">
({frontmatter.pubDate})
</div>
<slot />
<!-- モーダル用JS:ここに1回だけ -->
<script src="/src/scripts/modal.js" is:inline></script>
</BaseLayout>

mdx の作成

あとは、mdx ファイルを以下のように作成します。

import SimpleModal from '@/components/SimpleModal.astro';
<SimpleModal id="photo1" button="">
<img src="/assets/202512/HIC01.jpg" alt="写真1" class="max-w-full h-auto rounded" />
</SimpleModal>
<img src="/assets/202512/HIC01.jpg" data-modal-id="photo1" class="cursor-pointer max-w-xs rounded shadow-md mr-2" />
<SimpleModal id="photo2" button="">
<img src="/assets/202512/HIC02.jpg" alt="写真2" class="max-w-full h-auto rounded" />
</SimpleModal>
<img src="/assets/202512/HIC02.jpg" data-modal-id="photo2" class="cursor-pointer max-w-xs rounded shadow-md mb-8" />

このコードは少し面倒な感じがしますがコピペで動くはずです。