บทความ › เว็บและแอป

CSS anchor positioning ใช้ได้ทุก browser แล้ว: tooltip และเมนู ไม่ต้องเขียน JS

anchor positioning เป็น Baseline ตั้งแต่ Firefox 147 ผูก tooltip หรือเมนูกับปุ่มด้วย CSS ล้วน หลบขอบจอเองด้วย position-try-fallbacks พร้อมทางสำรองสำหรับ browser เก่า

กลาง อ่าน 9 นาที 23 ตุลาคม 2569

อ่านจบแล้วจะรู้
  • ตั้งแต่มกราคม 2026 anchor positioning เป็น Baseline ใช้ได้ใน Chrome/Edge 125, Safari 26 และ Firefox 147
  • สามส่วนหลักคือ anchor-name บนปุ่ม position-anchor บนกล่องที่ลอย และ position-area บอกว่าจะวางด้านไหน
  • popover ที่เปิดด้วย popovertarget ผูกกับปุ่มให้เองแบบ implicit ทำเมนูได้โดยไม่มี JavaScript สักบรรทัด
  • position-try-fallbacks ลองตำแหน่งสำรองตามลำดับจนกว่าจะพอดีจอ แทนโค้ดคำนวณตำแหน่งที่เคยต้องใช้ library

ทุกเว็บมี tooltip เมนูสามจุด หรือ dropdown ที่ต้องโผล่ติดกับปุ่ม และต้องหลบเมื่อปุ่มอยู่ชิดขอบจอ ที่ผ่านมางานนี้ต้องใช้ JavaScript วัดตำแหน่งปุ่ม คำนวณพิกัด และคอยฟัง scroll กับ resize CSS anchor positioning ทำเรื่องนี้ได้ใน CSS ล้วน และตั้งแต่มกราคม 2026 ใช้ได้ครบทุก browser หลักแล้ว

ทดสอบใน Chromium 152 ที่หน้าจอกว้าง 375 และ 800 px วัดตำแหน่งจริงด้วย getBoundingClientRect สถานะ browser ตรวจกับ MDN, caniuse และ web.dev ณ ตุลาคม 2026

สถานะ ณ ตุลาคม 2026

browserรองรับตั้งแต่
Chrome / Edge125
Safari (macOS, iOS)26.0
Firefox147 (มกราคม 2026)

Firefox 147 เป็นตัวสุดท้ายที่ตามมา ทำให้ฟีเจอร์นี้เป็น Baseline newly available ตั้งแต่มกราคม 2026 ข้อควรรู้คือ caniuse ยังติดป้าย “partial” ให้หลาย browser เพราะส่วนย่อยบางตัวยังรองรับไม่เท่ากัน ส่วนที่ใช้ในบทความนี้ (anchor-name, position-anchor, position-area, position-try-fallbacks, anchor-size()) คือส่วนแกนที่ใช้ได้แล้ว ส่วนย่อยที่ใหม่กว่าให้เช็กตาราง compat แยกทีละตัว

สามส่วนที่ต้องรู้

  1. anchor-name ตั้งชื่อให้องค์ประกอบที่เป็นจุดยึด ชื่อต้องขึ้นต้นด้วย --
  2. position-anchor บนกล่องที่จะลอย บอกว่ายึดกับชื่อไหน กล่องนี้ต้องเป็น position: absolute หรือ fixed
  3. ตำแหน่ง บอกได้สองแบบ: position-area แบบตาราง 3×3 รอบจุดยึด (top, bottom span-right, …) หรือฟังก์ชัน anchor() ใน top/left เมื่อต้องคุมระดับพิกเซล

anchor-size(width) ใช้ขนาดของจุดยึดได้ด้วย เช่นให้เมนูกว้างไม่น้อยกว่าปุ่ม

Tooltip ด้วย CSS ล้วน

<button class="help" aria-describedby="tip1">?</button>
<span class="tip" id="tip1" role="tooltip">Draft จะเก็บไว้ 30 วัน</span>
.help { anchor-name: --help; }

.tip {
  position: absolute;
  position-anchor: --help;
  position-area: top;            /* เหนือปุ่ม กึ่งกลาง */
  margin-bottom: 8px;
  position-try-fallbacks: flip-block;   /* ไม่มีที่ด้านบน ย้ายลงล่าง */
  width: max-content;
  max-width: 240px;
  opacity: 0;
  pointer-events: none;
}
.help:hover + .tip,
.help:focus-visible + .tip { opacity: 1; }

ผลที่วัดได้: ปุ่มอยู่กลางจอ (x 416–439, y 216–237) tooltip ลอยเหนือปุ่ม ขอบล่างที่ y 208 ห่าง 8 px ตาม margin และจุดกึ่งกลางตรงกับปุ่มพอดีที่ x 427.5 พอย้ายปุ่มไปชิดขอบบน (y 18–39) tooltip สลับลงไปใต้ปุ่มที่ y 47 = 39 + 8 เอง margin ก็กลับด้านให้ด้วย

aria-describedby ทำให้ screen reader อ่านข้อความ tooltip ตอน focus ปุ่ม และใช้ :focus-visible ด้วยเพื่อให้คนใช้คีย์บอร์ดเห็น บนมือถือไม่มี hover ข้อมูลที่สำคัญจึงไม่ควรอยู่ใน tooltip อย่างเดียว

เมนูด้วย popover: ไม่มี JS สักบรรทัด

Popover API เปิดปิดกล่องด้วย attribute popovertarget และเมื่อเปิดแบบนี้ browser ผูกปุ่มกับ popover เป็นจุดยึดแบบ implicit ให้เอง ไม่ต้องตั้ง anchor-name

<button popovertarget="actions">Actions</button>
<div popover id="actions" class="menu">
  <button>Edit</button>
  <button>Duplicate</button>
  <button>Delete</button>
</div>
[popover].menu {
  margin: 0;
  inset: auto;                   /* ล้างค่าเริ่มต้นที่จัด popover ไว้กลางจอ */
  position-anchor: auto;         /* ใช้จุดยึด implicit จาก popovertarget */
  position-area: bottom span-right;   /* ใต้ปุ่ม เริ่มที่ขอบซ้ายแล้วขยายไปขวา */
  position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
  min-width: anchor-size(width);
  margin-top: 4px;
}

ได้ทั้งการเปิดปิด กด Esc ปิด คลิกนอกกล่องปิด และวางตำแหน่ง โดยไม่ต้องเขียน JavaScript

ตัวอย่างตัวเลข: browser เลือกตำแหน่งสำรองยังไง

position-try-fallbacks คือรายการตำแหน่งสำรอง browser ใช้ตำแหน่งหลักก่อน ถ้าล้นจอจะลองตัวถัดไปตามลำดับ และเลือกตัวแรกที่อยู่ในจอได้ทั้งกล่อง ถ้าไม่มีตัวไหนพอดีเลย กลับไปใช้ตำแหน่งหลัก

วัดจริงบนจอ 375 × 640 px ปุ่ม Actions อยู่มุมขวาล่าง (x 299–359, y 599–620) เมนูขนาด 82 × 79 px ห่างปุ่ม 4 px:

ลองตำแหน่งแนวตั้งแนวนอนผล
หลักใต้ปุ่ม ขยายไปขวา624–703 เกิน 640299–381 เกิน 375ล้น
flip-blockเหนือปุ่ม ขยายไปขวา516–595299–381 เกิน 375ล้น
flip-inlineใต้ปุ่ม ขยายไปซ้าย624–703 เกิน 640277–359ล้น
flip-block flip-inlineเหนือปุ่ม ขยายไปซ้าย516–595277–359พอดี

ค่าที่ browser วางจริงคือ x 277–359, y 516–595 ตรงกับแถวสุดท้าย คำนวณได้จาก 599 − 4 − 79 = 516 และ 359 − 82 = 277 เมื่อย้ายปุ่มไปมุมขวาบน เมนูไปอยู่ใต้ปุ่ม (y 74) แต่ขยายไปซ้าย (x 277) คือได้ flip-inline

ใส่ flip-block flip-inline เป็นตัวเลือกสุดท้ายเสมอ ปุ่มที่อยู่มุมจอต้องกลับทั้งสองแกน ถ้าไม่มีตัวนี้ browser จะกลับไปใช้ตำแหน่งหลักที่ล้นจอ

ต้องการตำแหน่งสำรองที่ซับซ้อนกว่านี้ (เช่นวางซ้ายแล้วจำกัดความกว้าง) สร้างเองด้วย @position-try --ชื่อ { ... } แล้วใส่ชื่อในรายการ

ตำแหน่งหลักใต้ปุ่ม ขยายไปขวา: ล้นจอflip-blockเหนือปุ่ม ขยายไปขวา: ล้นจอflip-inlineใต้ปุ่ม ขยายไปซ้าย: ล้นจอกลับทั้งสองแกนเหนือปุ่ม ขยายไปซ้าย: พอดี
ปุ่มมุมขวาล่างบนจอ 375 px เบราว์เซอร์ลองทีละตัวจนพอดี

ซ่อนเมื่อปุ่มหายไปจากจอ

ถ้าปุ่มอยู่ในกล่องที่ scroll ได้ แล้วปุ่ม scroll หายไป tooltip อาจลอยค้าง ใช้ position-visibility

.tip { position-visibility: anchors-visible; }   /* ซ่อนเมื่อจุดยึดหายไปทั้งตัว */

อีกค่าคือ no-overflow ซ่อนเมื่อกล่องเริ่มล้นจอหรือล้นกล่องที่บรรจุ

ทางสำรองสำหรับ browser เก่า

ผู้ใช้บางส่วนยังใช้ browser รุ่นก่อนตารางข้างบน เช่น iPhone ที่ยังไม่อัปเดตเป็น iOS 26 ใช้ @supports เขียนทางสำรองให้ใช้งานได้ แม้จะไม่สวยเท่า

@supports not (anchor-name: --a) {
  [popover].menu { inset: 0; margin: auto; }   /* แสดงกลางจอแบบ dialog */
}

Popover API เองรองรับกว้างกว่า anchor positioning เมนูจึงยังเปิดปิดได้ แค่ไม่ติดกับปุ่ม

ข้อผิดพลาดที่เจอบ่อย

  • ลืมล้าง style เริ่มต้นของ popover popover มี inset: 0 และ margin: auto มาจาก browser ถ้าไม่ล้าง กล่องจะไม่ไปติดปุ่ม
  • ใช้ชื่อเก่าจากบทความปี 2024 inset-area ใช้ position-area แทน และ position-try-options ใช้ position-try-fallbacks แทน ชื่อเก่าใช้ไม่ได้แล้ว
  • จุดยึดเป็น display: none หรือ visibility: hidden ผูกไม่ได้ กล่องไปอยู่ผิดที่
  • ตั้ง anchor-name ชื่อเดียวกันหลายที่ เช่นปุ่มในทุกแถวของตาราง กล่องจะยึดกับตัวสุดท้ายในหน้า ใช้ popover แบบ implicit หรือ anchor-scope แยกขอบเขต
  • ใส่แค่ flip-block ปุ่มที่ชิดมุมจอยังล้นแนวนอน
  • ไม่ลองที่จอมือถือ ตำแหน่งสำรองทำงานจริงเฉพาะเมื่อพื้นที่ไม่พอ ต้องเปิดดูที่ 375 px แบบที่เล่าใน ให้ AI ตรวจหน้าเว็บบนมือถือ

แหล่งอ้างอิง

คำถามที่พบบ่อย

ยังต้องใช้ Floating UI หรือ Popper อยู่ไหม

สำหรับ tooltip เมนู และ dropdown ทั่วไป CSS ทำได้เองแล้ว ยังมีเหตุผลใช้ library ถ้าต้องรองรับ browser รุ่นก่อนที่ระบุในบทความให้หน้าตาเหมือนกันทุกเครื่อง หรือโค้ดเดิมพึ่งความสามารถเฉพาะของ library นั้นอยู่มาก

browser เก่าที่ไม่รองรับจะเห็นอะไร

กล่องที่ใช้ position absolute หรือ fixed จะไปอยู่ตำแหน่งตาม inset ปกติ ซึ่งมักผิดที่ ให้เขียนทางสำรองใน @supports not (anchor-name: --a) เช่นให้เมนู popover แสดงกลางจอแบบ dialog

บทความเก่าใช้ inset-area และ position-try-options ใช้ได้ไหม

เป็นชื่อเก่าก่อนสเปกนิ่ง Chrome เปลี่ยนเป็น position-area (Chrome 129) และ position-try-fallbacks (Chrome 128) แล้ว ให้ใช้ชื่อใหม่เท่านั้น

คำสั่งและค่าตั้งในบทความทดสอบบนระบบตัวอย่าง ลองบนเครื่องทดสอบก่อนใช้กับระบบจริงเสมอ

อ่านต่อ

#เว็บเริ่มต้น

เปิด dialog และ popover ไม่ต้องเขียน JS: command และ commandfor ใน HTML

ปุ่ม HTML สั่งเปิดปิด dialog และ popover ได้เองด้วย command กับ commandfor ใช้ได้ทุกเบราว์เซอร์หลักตั้งแต่ธันวาคม 2025 ค่าที่มีให้ คำสั่งของเราเอง และทางสำรอง

อ่าน 11 นาที
#เว็บกลาง

สี่เว็บจาก เอนจิน เดียว แชร์โค้ดด้วย Astro monorepo แต่หน้าตาไม่เหมือนกัน

แยก core/ กับโฟลเดอร์ต่อเว็บ ให้สี่เว็บใช้ layout i18n และ deploy ชุดเดียว ต่างกันแค่สี ฟอนต์ และหน้าแรก ผ่าน design tokens พร้อมบั๊กจริงที่แก้ครั้งเดียวได้ทั้งสี่เว็บ

อ่าน 15 นาที
#เว็บกลาง

ภาษาไทยบนเว็บ ตัดคำ ฟอนต์ เวลาอ่าน อะไรพัง และแก้ยังไงใน 5 เว็บ

เบราว์เซอร์ตัดคำไทยด้วยพจนานุกรม พลาดคำทับศัพท์ในหัวข้อใหญ่ keep-all ไม่ช่วย วิธีแก้ที่ใช้จริง เวลาอ่านนับตัวอักษร คู่ฟอนต์ไทย+อังกฤษ line-height และ lang

อ่าน 10 นาที