ข้ามไปยังเนื้อหา

Styles & Scripts

บล็อก <style> ในไฟล์ .astro จะถูก scope ไว้เฉพาะ component นั้น โดยอัตโนมัติ Astro เขียน selector ของคุณใหม่ (เติม hash ต่อ component) ดังนั้น rule ของ h1 ตรงนี้จะไปเลอะ h1 ของอีก component ไม่ได้

<h1>Title</h1>
<p>Body</p>
<style>
h1 { color: rebeccapurple; } /* only THIS component's h1 */
p { line-height: 1.6; }
</style>

คุณเขียน selector ธรรมดาตรงไปตรงมา แล้วได้ CSS เฉพาะ component — ไม่ต้องมี naming convention ไม่ต้อง CSS-in-JS นี่คือ default ที่คุณจะใช้แทบทุกที่

บางครั้งคุณอยากให้ rule มีผลทั้งไซต์จริง ๆ — CSS reset, typography พื้นฐาน, theme เติม is:global เพื่อ opt ทั้งบล็อกออกจาก scoping

<style is:global>
:root { --brand: #883AEA; }
body { margin: 0; font-family: system-ui; }
</style>

คุณยัง escape scope ทีละ selector ได้ด้วยฟังก์ชัน :global() ภายในบล็อกที่ scoped ตามปกติ

<style>
/* scoped, except this one rule reaches into slotted/child content */
:global(.markdown-body a) { color: var(--brand); }
</style>

ค่าใน frontmatter อยู่ฝั่ง server ส่วน CSS เป็น text นิ่ง ๆ เพื่อเชื่อมสองอย่างนี้ define:vars จะฉีดค่า JavaScript เข้าไปเป็น CSS custom property บนบล็อก style

---
const accent = "#883AEA";
const gap = "1.5rem";
---
<div class="card">Styled by props</div>
<style define:vars={{ accent, gap }}>
.card {
border: 2px solid var(--accent);
padding: var(--gap);
}
</style>

Astro จะออก inline style ที่พก --accent กับ --gap มาด้วย ดังนั้น prop ขับ style ได้โดยไม่ต้องมี JavaScript ฝั่ง client เลย เป็นวิธีสะอาด ๆ ในการทำให้สีหรือ spacing ของ component ปรับได้ผ่าน props

<script> ในไฟล์ .astro รันบน browser — แต่ Astro มองเป็น module: script จะถูก process, bundle, compile TypeScript และ resolve import เหมือน JS อื่น ๆ ใน build ของคุณ

<button id="menu">Menu</button>
<script>
// Bundled module script. Runs in the browser.
import { toggle } from '../scripts/menu';
document.getElementById('menu')?.addEventListener('click', toggle);
</script>

นี่คือ client JavaScript จริง ๆ — จุดเดียวในโมดูลนี้ที่โค้ดไปถึง browser เหมาะกับงาน DOM แบบ imperative เล็ก ๆ: ต่อ event listener, อ่าน localStorage, init widget จากภายนอก

ถ้าคุณต้องการให้ script ออกมา ตรงตามที่เขียนเป๊ะ ๆ — ไม่ถูก bundle หรือ process (เช่น snippet analytics แบบ inline หรือ script ที่ต้องรันก่อน hydration) ให้เติม is:inline

<script is:inline>
window.dataLayer = window.dataLayer || [];
</script>

inline script ข้าม bundling ดังนั้น import ในนั้นจะไม่ถูก resolve และไม่ถูก dedupe ข้ามทั้งหน้า ใช้เฉพาะตอนที่คุณต้องการ tag ดิบ ๆ ไม่ถูกแตะจริง ๆ

จุดนี้ทำมือใหม่งงบ่อย <script> รันบน browser แต่ ไม่ ทำให้ component รอบ ๆ กลายเป็น island ไม่มีการ hydrate component ไม่มี framework ไม่มี re-render — เป็นแค่ script ธรรมดาที่แก้ DOM ที่ Astro ผลิตออกมาแล้ว

<!-- This runs, but nothing is "hydrated". It's a script, not an island. -->
<div id="counter">0</div>
<script>
let n = 0;
const el = document.getElementById('counter');
document.getElementById('inc')?.addEventListener('click', () => {
el.textContent = String(++n);
});
</script>

เมื่อคุณอยากได้ความ interactive ระดับ component — component React/Vue/Svelte ที่มี state แล้ว hydrate — นั่นคือ island สร้างด้วย client directive และเป็นเนื้อหาทั้งโมดูลถัดไป กฎง่าย ๆ: หยิบ <script> มาใช้กับงาน DOM แบบ imperative เล็ก ๆ; หยิบ island มาใช้เมื่ออยากได้ UI component ที่มี state รันบน client

flowchart TB
  scr["plain script tag"] --> dom["runs in browser, manipulates existing DOM"]
  dom --> nohy["no component hydration"]
  isl["island with a client directive"] --> hyd["framework component hydrates"]
  hyd --> state["has its own state and re-renders"]
script กับ island — คนละเรื่องกัน
โดย default บล็อก `<style>` ใน component .astro มี scope แค่ไหน?
คุณส่งค่าจาก frontmatter เข้า CSS ของ component ยังไง?
`<script>` tag ธรรมดาใน component .astro...
การเติม `is:inline` ให้ script ทำอะไร?