เมื่อไม่กี่ปีที่แล้ว ผมลองสร้างแดชบอร์ดขึ้นมาอันหนึ่ง ไม่ได้มีอะไรซับซ้อน—แค่มีแถบด้านข้าง (sidebar) แบบคงที่, พื้นที่เนื้อหาหลัก และฟุตเตอร์ (footer) ตอนแรกผมคิดว่า Flexbox น่าจะเป็นตัวเลือกที่ชัดเจนที่สุด และในช่วงแรกมันก็ทำงานได้ดี จนกระทั่งผมใส่แถวของการ์ด (cards) ลงไปในส่วนเนื้อหาหลัก ทันใดนั้น เลย์เอาต์ทั้งหมดก็พังทลายลง ผมใช้เวลาสามชั่วโมงถัดมาต่อสู้กับอัตราส่วน flex-grow และความกว้างแบบเปอร์เซ็นต์ที่ซ้อนกันหลายชั้น คอยเฝ้าดูคอนเทนเนอร์ที่ไม่ยอมจัดเรียงให้ตรงกันไม่ว่าจะลด padding ลงไปมากแค่ไหนก็ตาม หน้าเว็บนั้นให้ความรู้สึกเหมือนหอคอยที่กำลังจะถล่มลงมา
ประสบการณ์อันเจ็บปวดนั้นสอนบทเรียนที่สำคัญที่สุดอย่างหนึ่งเกี่ยวกับเลย์เอาต์ CSS สมัยใหม่ให้แก่ผม นั่นคือ Flexbox และ Grid ไม่ใช่คู่แข่งกัน พวกมันเป็นเครื่องมือสำหรับงานที่ต่างกัน และเลย์เอาต์ที่ล้มเหลวส่วนใหญ่ก็เกิดจากการเลือกใช้เครื่องมือผิดประเภท
ความแตกต่างอยู่ที่มิติ
ให้ลองนึกภาพ Flexbox เป็นเหมือนโถงทางเดิน ไอเทมต่างๆ จะไหลไปในทิศทางเดียว ไม่ว่าจะเป็นแถว (row) หรือคอลัมน์ (column) มันถูกสร้างมาเพื่อการจัดสรรแบบหนึ่งมิติ (one-dimensional) เบราว์เซอร์จะดูที่เนื้อหา แล้วจึงตัดสินใจว่าจะขยาย ย่อ หรือจัดวางมันอย่างไรตามแกนเดียวนั้น สิ่งนี้ทำให้ Flexbox สมบูรณ์แบบสำหรับสถานการณ์ที่เนื้อหาเป็นตัวกำหนดเลย์เอาต์ เช่น แถบนำทาง (navigation bar), กลุ่มของฟิลด์ในฟอร์ม (form fields) หรือชุดของแท็ก (tags) ที่ต้องการระยะห่างเท่าๆ กัน ทั้งหมดนี้สามารถวางอยู่ใน flex container ได้อย่างเป็นธรรมชาติเพราะพวกมันใช้ตรรกะในบรรทัดเดียวกัน
ในทางกลับกัน Grid เปรียบเสมือนผังพื้น (floor plan) มันเป็นแบบสองมิติ (two-dimensional) อย่างเคร่งครัด โดยจัดการทั้งแถวและคอลัมน์ไปพร้อมๆ กัน คุณกำหนดโครงสร้างก่อน แล้วจึงวางไอเทมลงไปข้างใน สิ่งนี้ทำให้ Grid เหมาะอย่างยิ่งสำหรับสถาปัตยกรรมโดยรวมของหน้าเว็บ—ไม่ว่าจะเป็นจุดไหนที่คุณต้องการควบคุมการจัดวางทั้งแนวตั้งและแนวนอนไปพร้อมกัน เมื่อคุณพยายามฝืนใช้การออกแบบสองมิติที่ซับซ้อนผ่านคอนเทนเนอร์ Flexbox ที่ซ้อนกันหลายชั้น คุณจะลงเอยด้วยการมี markup ที่ลึกเกินไป, มี wrapper ที่เปราะบาง และการคำนวณที่ซับซ้อนซึ่งจะพังทลายลงทันทีที่คุณเพิ่มองค์ประกอบเพียงอย่างเดียวเข้าไป
Flexbox ยังคงมีประโยชน์ในแบบของมัน
อย่ารีบร้อนที่จะแทนที่ flex container ทุกตัวด้วย Grid เพราะภายในคอมโพเนนต์ (component) แต่ละตัว Flexbox มักจะสะอาดตาและตรงไปตรงมามากกว่า หากคุณมีฟุตเตอร์ของการ์ดที่มีปุ่มสองปุ่ม และคุณต้องการให้ปุ่มหนึ่งชิดซ้ายและอีกปุ่มชิดขวา justify-content: space-between จะทำงานได้ตรงตามที่คุณต้องการพอดี หากคุณต้องการจัดไอคอนให้อยู่กึ่งกลางในแนวตั้งภายในปุ่มที่มีความสูงไม่แน่นอน align-items: center คือวิธีที่ง่ายที่สุด เนื้อหาที่มีความแปรผัน—เช่น แถวของป้ายชื่อผู้ใช้ (user badges) ที่มีความยาวชื่อต่างกัน—สามารถวางอยู่ใน Flexbox ได้อย่างสบายๆ เพราะมันยึดตามเนื้อหาเป็นหลัก (content-first) คอนเทนเนอร์จะปรับตัวตามสิ่งที่บรรจุอยู่ภายใน
ส่วน Grid นั้นยึดตามคอนเทนเนอร์เป็นหลัก (container-first) คุณบอกเบราว์เซอร์ว่ามี track จำนวนเท่าใดและควรมีขนาดเท่าไหร่ จากนั้นคุณจึงวางไอเทมลงในเซลล์เหล่านั้น หากเลย์เอาต์ของคุณเป็นเพียงแค่แถวเดียวของไอเทมที่แชร์พื้นที่ร่วมกัน การใช้ Grid จะเป็นการเพิ่มขั้นตอนที่ไม่จำเป็น
เลย์เอาต์ของหน้าเว็บเป็นหน้าที่ของ Grid
นี่คือจุดที่ Grid เข้ามามีบทบาท จำฝันร้ายเรื่องแดชบอร์ดของผมได้ไหม? นี่คือวิธีที่โครงสร้างควรจะเป็นตั้งแต่เริ่มต้น:
.page {
display: grid;
min-height: 100vh;
grid-template-rows: auto 1fr auto;
grid-template-columns: 200px 1fr;
}
.header { grid-row: 1; grid-column: 1 / -1; }
.sidebar { grid-row: 2; grid-column: 1; }
.main { grid-row: 2; grid-column: 2; }
.footer { grid-row: 3; grid-column: 1 / -1; }
ลองดูสิ่งที่เกิดขึ้น คอนเทนเนอร์ .page กำหนดแถวสามแถวและสองคอลัมน์ แถวแรกกำหนดขนาดตามเนื้อหาที่อยู่ใน header ด้วยการใช้ auto แถวกลางจะครองพื้นที่ว่างที่เหลือทั้งหมดด้วย 1fr ส่วนฟุตเตอร์จะอยู่ในแถวสุดท้าย โดยมีขนาดตามเนื้อหาของมัน ในขณะเดียวกัน แถบด้านข้าง (sidebar) จะถูกล็อกความกว้างไว้ที่ 200 พิกเซล และพื้นที่หลักจะเติมเต็มส่วนที่เหลือ
ตรรกะการจัดวางคือสิ่งที่ช่วยให้ชีวิตง่ายขึ้นมาก Header จะครอบคลุมตั้งแต่เส้นคอลัมน์ที่ 1 ไปจนถึงเส้นที่ -1 ซึ่งหมายความว่ามันจะยืดออกครอบคลุมทั้งสองคอลัมน์ ไม่ว่าคุณจะเพิ่มคอลัมน์เข้าไปภายหลังกี่คอลัมน์ก็ตาม ส่วน sidebar และเนื้อหาหลักจะแชร์แถวที่ 2 ร่วมกันแต่แยกคอลัมน์กัน ฟุตเตอร์จะกลับมามีความกว้างเต็มหน้าในแถวที่ 3 คุณไม่จำเป็นต้องใช้การคำนวณเปอร์เซ็นต์กับ element ลูกๆ และไม่จำเป็นต้องมี wrapper ซ้อนกันเพื่อแย่งชิงความสูง เพียงแค่เปลี่ยนความกว้างของ sidebar ในที่เดียว พื้นที่หลักก็จะปรับเปลี่ยนโดยอัตโนมัติ เลย์เอาต์ทั้งหมดจะทำงานสอดประสานกันเป็นระบบเดียว
ความอ่านง่ายด้วย grid-template-areas
เพื่อความชัดเจนยิ่งขึ้น คุณสามารถตั้งชื่อส่วนต่างๆ ของคุณได้ แทนที่จะต้องจัดการกับหมายเลขเส้น คุณสามารถเขียนแผนผังแบบภาพ (visual map) ได้ดังนี้:
.page {
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
}
.header { grid-area: header; }
.main { grid-area: main; }
ตอนนี้ stylesheet ของคุณจะอ่านง่ายเหมือนกับ wireframe หากคุณต้องการให้ sidebar ไปปรากฏทางด้านขวาสำหรับ breakpoint อื่น คุณก็แค่สลับคำสองคำใน template string ความสามารถในการอ่านโครงสร้างแบบนี้เป็นสิ่งที่ทำไม่ได้เลยหากคุณใช้คอนเทนเนอร์ Flexbox ซ้อนกันเป็นชั้นๆ
บทเรียนที่ยากลำบากสองประการ
Grid ไม่ใช่เวทมนตร์ มีข้อผิดพลาดสองอย่างที่จะทำให้เลย์เอาต์ของคุณพังได้อย่างรวดเร็ว
อย่างแรกคือการใช้หน่วยอย่างไม่รอบคอบ หากคุณกำหนด grid-template-columns: 1fr 1fr 1fr แต่ดีไซน์ของคุณต้องการเนื้อหาเพียงสองคอลัมน์ track ที่สามนั้นก็ยังคงอยู่ มันอาจจะว่างเปล่าและสร้างช่องว่างที่คุณไม่ได้คาดคิดไว้ หรือไม่ก็บีบให้เนื้อหาจริงของคุณต้องเบียดเสียดจนทำให้ดีไซน์ของคุณเสียรูปทรง จงมีความรอบคอบ เมื่อคุณต้องการสองคอลัมน์ ให้เขียน repeat(2, 1fr)
