ข้ามไปเนื้อหาหลัก

Category: reference

CSS Container Queries — Responsive ตาม Container ไม่ใช่ Viewport

Container queries ให้ component ปรับ layout ตามขนาดของ parent container แทน viewport — ทำให้ component เป็น truly reusable ในทุกที่ที่วาง

· อ่านประมาณ 2 นาที

สารบัญ

ปัญหาของ Media Queries

/* Media query ตาม viewport — component ไม่รู้ว่าตัวเองอยู่ที่ไหน */
@media (max-width: 640px) {
  .card { flex-direction: column; }
}

/* ปัญหา: ถ้า card อยู่ใน sidebar (narrow) บน desktop
   viewport กว้าง 1200px → media query ไม่ทำงาน
   แต่ card ในความเป็นจริงแคบมาก */

Container queries แก้ปัญหานี้ — component ตอบสนองต่อขนาดของ parent ของตัวเอง

Setup พื้นฐาน

/* 1. กำหนด container context บน parent */
.card-grid {
  container-type: inline-size;
  container-name: card-wrapper; /* optional แต่แนะนำ */
}

/* 2. ใช้ @container แทน @media */
@container card-wrapper (min-width: 400px) {
  .card {
    display: flex;
    flex-direction: row;
    gap: 1rem;
  }
}

@container card-wrapper (min-width: 600px) {
  .card {
    padding: 2rem;
  }
}

container-type Options

/* inline-size — ตรวจ width ของ container (ใช้บ่อยที่สุด) */
.parent { container-type: inline-size; }

/* size — ตรวจทั้ง width และ height */
.parent { container-type: size; }

/* normal — container แต่ไม่มี size containment (สำหรับ style queries) */
.parent { container-type: normal; }

ตัวอย่าง Card Component ที่ Reusable จริงๆ

.article-wrapper {
  container-type: inline-size;
  container-name: article;
}

.article-card {
  /* mobile-first default: vertical layout */
  display: grid;
  gap: 0.75rem;
}

.article-card img {
  width: 100%;
  aspect-ratio: 16/9;
  object-fit: cover;
  border-radius: 8px;
}

/* เมื่อ container กว้างพอ: horizontal layout */
@container article (min-width: 400px) {
  .article-card {
    grid-template-columns: 160px 1fr;
    align-items: start;
  }

  .article-card img {
    aspect-ratio: 1;
  }
}

@container article (min-width: 600px) {
  .article-card {
    grid-template-columns: 240px 1fr;
    gap: 1.5rem;
  }
}

Container Style Queries

ตรวจ CSS custom properties บน container:

.card-wrapper {
  container-type: normal;
  --variant: compact; /* เปลี่ยนได้ผ่าน JavaScript หรือ parent class */
}

@container style(--variant: compact) {
  .card { padding: 0.5rem; font-size: 0.85rem; }
}

@container style(--variant: featured) {
  .card { padding: 2rem; font-size: 1.1rem; font-weight: bold; }
}

เปรียบเทียบ: Container vs Media Query

Use Caseใช้เหตุผล
Card ใน gridContainerปรับตาม column width ที่เปลี่ยนตาม screen
Sidebar navContainernav อาจแคบหรือกว้างขึ้นกับ layout
Full-width bannerMedia queryสัมพันธ์กับ viewport โดยตรง
Print stylesMedia queryไม่มี container บน print
Font size body textMedia queryscale ตาม screen ใหญ่ขึ้น

ใช้ร่วมกันได้ — container queries ไม่ได้แทน media queries ทั้งหมด

Browser Support

Chrome 105+, Firefox 110+, Safari 16+ — รองรับดีแล้ว

ตรวจก่อนใช้ด้วย @supports:

@supports (container-type: inline-size) {
  .wrapper { container-type: inline-size; }

  @container (min-width: 400px) {
    .card { flex-direction: row; }
  }
}

ใช้กับ Astro Component

<!-- Card.astro -->
<div class="card-container">
  <article class="card">
    <img src={image} alt={title} />
    <div class="card-body">
      <h3>{title}</h3>
      <p>{description}</p>
    </div>
  </article>
</div>

<style>
  .card-container {
    container-type: inline-size;
  }

  .card {
    display: grid;
    gap: 0.75rem;
  }

  @container (min-width: 360px) {
    .card {
      grid-template-columns: 120px 1fr;
    }
  }
</style>

Component นี้ทำงานถูกต้องไม่ว่าจะวางไว้ใน main content หรือ sidebar — ปรับตาม parent เสมอ