บทความ

ตรึงหัวตารางและคอลัมน์แรกใน HTML table ด้วย CSS sticky ยังไง

ใช้ position: sticky ล็อกคอลัมน์แรกและหัวตาราง HTML ไว้ตอนเลื่อนตารางยาว ๆ ทั้งแนวนอนและแนวตั้ง พร้อมจัดลำดับ z-index และแก้เส้นขอบที่หายไปเพราะ border-collapse

เผยแพร่
อัปเดต
— เพิ่มล็อกหัวตาราง ลำดับ z-index และแก้เส้นขอบหาย
เวลาอ่าน
3 นาที
แท็ก
#CSS#Tailwind CSS

ตารางที่มีคอลัมน์เยอะ ๆ อย่างยอดขายรายเดือน 12 เดือน พอเปิดบนมือถือก็ต้องเลื่อนไปทางขวา เลื่อนไปสักพักเราก็ลืมแล้วว่าแถวนี้เป็นของสาขาไหน ใน Excel เราแก้ด้วย Freeze Panes บนเว็บเราทำแบบเดียวกันได้ด้วย CSS position: sticky โดยไม่ต้องเปลี่ยนตารางไปใช้ div หรือ layout แบบอื่น

ตัวอย่างในบทความนี้เขียนด้วยคลาสของ Tailwind CSS v4 และมีเวอร์ชัน CSS ธรรมดาไว้ท้ายบทความ

ใส่ wrapper ที่เลื่อนแนวนอนได้

เริ่มจากหุ้มตารางด้วย div ที่ใส่ overflow-x-auto กับ w-full ถ้าตารางกว้างกว่าจอ wrapper จะโชว์ scrollbar ให้เลื่อนซ้ายขวาเฉพาะในกรอบนี้ ส่วนอื่นของหน้าไม่เลื่อนตาม

<div class="w-full overflow-x-auto">
    <table>
        <!-- thead, tbody -->
    </table>
</div>

ล็อกคอลัมน์แรกด้วย sticky left-0

ทีนี้ใส่ sticky left-0 ให้ช่องแรกของทุกแถว ช่องนั้นจะติดอยู่ที่ขอบซ้ายของ wrapper ตอนเราเลื่อนตาราง

<tr>
    <th scope="row" class="sticky left-0 z-10 bg-white">ร้านสาขาสยาม</th>
    <td>245,000</td>
    <td>198,000</td>
    <!-- คอลัมน์อื่น ๆ -->
</tr>
  • sticky กับ left-0 ให้ช่องแรกของแต่ละแถวติดอยู่ที่ขอบซ้าย
  • bg-white ใส่สีพื้นให้ช่องนั้น ไม่อย่างนั้นตัวเลขของคอลัมน์ถัดไปจะเลื่อนมาซ้อนใต้ชื่อสาขาให้เห็น จะเปลี่ยนเป็นสีอื่นให้ตรงกับดีไซน์ของตารางก็ได้
  • z-10 ให้ช่องที่ sticky ลอยอยู่เหนือช่องอื่นที่เลื่อนผ่านไปข้างใต้

ช่องแรกเป็นชื่อของแถว เราจึงใช้ <th scope="row"> แทน <td> screen reader จะได้อ่านชื่อสาขาให้ทุกครั้งที่ผู้ใช้ไล่ตัวเลขในแถวนั้น

ไม่ต้องใส่ relative ให้ table นะ (บทความนี้เวอร์ชันแรกบอกว่าต้องใส่ ซึ่งไม่จำเป็น) element ที่เป็น sticky จะยึดกับกล่องที่ scroll ได้ที่อยู่ใกล้ที่สุด ซึ่งในที่นี้คือ wrapper ที่ใส่ overflow-x-auto ไว้

เลื่อนตารางไปทางขวา คอลัมน์แรกติดอยู่ที่ขอบซ้าย เลื่อนตารางไปทางขวา คอลัมน์แรกยังติดอยู่ที่ขอบซ้าย

ล็อกหัวตารางด้วย sticky top-0

ถ้าตารางยาวลงไปหลายแถวด้วย เราก็อยากให้หัวตารางติดอยู่ข้างบนเหมือนกัน ใส่ sticky top-0 ที่ th ในแถวหัวตาราง แล้วเปลี่ยน wrapper เป็น overflow-auto กับกำหนดความสูงสูงสุด เช่น max-h-[70vh]

<div class="max-h-[70vh] w-full overflow-auto">
    <table>
        <thead>
            <tr>
                <th class="sticky top-0 left-0 z-30 bg-gray-100">สาขา</th>
                <th class="sticky top-0 z-20 bg-gray-100">ม.ค. 2569</th>
                <th class="sticky top-0 z-20 bg-gray-100">ก.พ. 2569</th>
                <!-- หัวคอลัมน์อื่น ๆ -->
            </tr>
        </thead>
        <!-- tbody -->
    </table>
</div>

ช่องมุมซ้ายบนต้องมี z-index สูงสุด

ช่อง "สาขา" ที่มุมซ้ายบนทั้ง sticky ซ้ายและ sticky บน ตอนเลื่อนทแยงจะมีทั้งหัวตารางและคอลัมน์แรกวิ่งผ่านใต้ช่องนี้ เราจึงไล่ z-index เป็นสามชั้น

ช่อง คลาส ชั้น
คอลัมน์แรกใน tbody sticky left-0 z-10 ล่างสุด
หัวตาราง sticky top-0 z-20 ทับคอลัมน์แรกตอนเลื่อนลง
มุมซ้ายบน sticky top-0 left-0 z-30 บนสุด

ถ้าหน้าเว็บมี dropdown หรือ navbar ที่ fix ไว้ด้วย ให้ตั้ง z-index ของ element พวกนั้นให้สูงกว่า z-30 ไม่อย่างนั้นหัวตารางจะลอยทับ dropdown

เส้นขอบหายตอนเลื่อน เพราะ border-collapse

ตารางที่มีเส้นขอบมักเจอปัญหานี้ พอเลื่อนแล้วเส้นขอบของช่องที่ sticky หายไป หรือค้างอยู่ที่เดิมแทนที่จะเลื่อนตามช่อง สาเหตุคือ border-collapse: collapse ซึ่ง Tailwind ใส่ให้ทุก table ใน preflight อยู่แล้ว โหมดนี้รวมเส้นขอบของช่องที่ติดกันเป็นเส้นเดียว และเส้นนั้นไม่ได้เลื่อนไปกับช่องที่ sticky

แก้ได้ด้วยการเปลี่ยนตารางเป็น border-separate border-spacing-0 แล้วใส่เส้นขอบที่แต่ละช่องเองด้วย border-r border-b ช่องที่ติดกันจะไม่มีช่องว่างระหว่างกัน หน้าตาเหมือนเดิม แต่เส้นขอบจะเลื่อนไปพร้อมช่องของตัวเอง

ใน v4 สีเส้นขอบตั้งต้นเปลี่ยนจาก gray-200 เป็น currentColor (upgrade guide) เราจึงใส่ border-gray-200 กำกับไว้ด้วย ไม่อย่างนั้นเส้นจะเข้มเท่าสีตัวอักษร

โค้ดทั้งหมด

<div class="max-h-[70vh] w-full overflow-auto">
    <table class="border-separate border-spacing-0">
        <thead>
            <tr>
                <th class="sticky top-0 left-0 z-30 border-r border-b border-gray-200 bg-gray-100">สาขา</th>
                <th class="sticky top-0 z-20 border-r border-b border-gray-200 bg-gray-100">ม.ค. 2569</th>
                <!-- หัวคอลัมน์อื่น ๆ -->
            </tr>
        </thead>
        <tbody>
            <tr>
                <th scope="row" class="sticky left-0 z-10 border-r border-b border-gray-200 bg-white">ร้านสาขาสยาม</th>
                <td class="border-r border-b border-gray-200">245,000</td>
                <!-- คอลัมน์อื่น ๆ -->
            </tr>
        </tbody>
    </table>
</div>

ตารางยอดขายรายเดือนที่เลื่อนลงและเลื่อนไปทางขวาจนสุด หัวตารางกับคอลัมน์สาขายังอยู่ที่เดิม เลื่อนตารางลงและไปทางขวาจนถึงเดือน ธ.ค. แล้ว แถวหัวตารางยังติดอยู่ข้างบน คอลัมน์ชื่อสาขายังติดอยู่ทางซ้าย และช่อง "สาขา" ที่มุมอยู่บนสุด

อ่านต่อ

ล่าสุด

ดูทั้งหมด →