บทความ
จอมือถือกับตัวอักษรไทย สองอย่างที่เบราว์เซอร์วัดผิดมาตลอด
ความสูงของหน้าจอบนมือถือ กับขนาดตัวอักษรไทยเทียบกับอังกฤษ คือสองค่าที่ดูเรียบร้อยดีในไฟล์ออกแบบ แต่พอหยิบเครื่องจริงขึ้นมาถือเมื่อไร คุณจะเห็นเลยครับว่ามันไม่ตรง
เผยแพร่
ลองนึกภาพตามนะครับ คุณกดลิงก์จากแชตบนมือถือ หน้าแรกเป็นภาพเต็มจอสวยงามอย่างที่ตั้งใจไว้ แต่ปุ่มที่ควรจะอยู่ล่างสุดกลับโดนตัดหายไปครึ่งหนึ่ง ต้องเลื่อนนิดหนึ่งถึงจะเห็น พอเลื่อนปุ๊บ แถบเครื่องมือของเบราว์เซอร์ก็หุบลง ทั้งหน้าขยับตาม ข้อความที่กำลังอ่านอยู่เลื่อนหนีสายตาไปดื้อ ๆ
สองอาการนี้มาจากการตัดสินใจคนละเรื่องกันเลยครับ และทั้งคู่เป็นค่าเริ่มต้นที่ดูเหมือนถูกต้องทุกอย่าง จนกว่าคุณจะเอาเครื่องจริงมาถือ
ความสูงหน้าจอที่คุณเห็น ไม่ใช่ความสูงที่เบราว์เซอร์วัด
100vh ไม่ได้แปลว่าความสูงของพื้นที่ที่คุณมองเห็นอยู่ตอนนี้นะครับ ข้อกำหนดระบุไว้ว่า vh วัดจากวิวพอร์ตที่ ใหญ่ที่สุด เท่าที่จะเป็นไปได้ คือสภาพที่แถบที่อยู่ด้านบนและแถบเครื่องมือด้านล่างหุบไปหมดแล้ว
บนเดสก์ท็อปเรื่องนี้ไม่มีปัญหาเลย เพราะแถบเครื่องมือไม่เคยขยับไปไหน แต่บนมือถือนี่คนละเรื่องครับ ตอนที่ผู้ใช้เพิ่งเปิดหน้าเว็บ แถบทั้งสองยังกางอยู่เต็ม พื้นที่ที่เห็นจริงจึงน้อยกว่า 100vh อยู่หลายสิบพิกเซล ส่วนหัวที่เราตั้งไว้ 100vh ก็เลยถูกตัดท้ายเสมอในวินาทีแรกที่คนเห็นหน้าเว็บ ซึ่งบังเอิญเป็นวินาทีที่สำคัญที่สุดพอดี
ข่าวดีคือทุกวันนี้เรามีหน่วยที่บอกได้ตรงกว่านั้นแล้วครับ
lvh คือวิวพอร์ตใหญ่ที่สุด ค่าเดียวกับ vh นั่นแหละ แต่เขียนออกมาให้เห็นชัด ๆ ว่าเราตั้งใจเลือกมัน
svh คือวิวพอร์ตเล็กที่สุด คือสภาพที่แถบเครื่องมือกางอยู่ครบ
dvh คือค่าที่เปลี่ยนไปตามจริง แถบหุบลงเมื่อไร ค่าก็โตขึ้นเมื่อนั้น แถบกลับมาก็หดลงตาม
แล้วทำไมเราถึงเลือก svh
ส่วนหัวของหน้าแรกเว็บนี้ พวกเราเขียนว่า min-h-[calc(100svh-6rem)] ไม่ใช่ dvh ครับ ทั้งที่เมื่อมองเผิน ๆ แล้ว dvh ดูเหมือนจะเป็นคำตอบที่แม่นยำกว่าเยอะ
เหตุผลอยู่ที่คำว่า "เปลี่ยนไปตามจริง" นั่นแหละครับ ถ้าเราใช้ dvh ส่วนหัวจะพอดีกับหน้าจอทุกขณะจริง แต่ระหว่างที่ผู้ใช้เลื่อนหน้าลงไป แถบเครื่องมือค่อย ๆ หุบ ความสูงของส่วนหัวก็ค่อย ๆ โตขึ้นตาม เนื้อหาที่อยู่ถัดลงมาจึงถูกดันลงพร้อม ๆ กับที่ผู้ใช้กำลังเลื่อนขึ้น ผลคือข้อความไหลหนีสายตาในจังหวะที่คนกำลังพยายามอ่านมันอยู่พอดี ท่านผู้อ่านคงพอนึกความรำคาญตรงนี้ออกใช่มั้ยครับ
svh เลือกอีกทางหนึ่ง คือยึดค่าที่เล็กที่สุดไว้ตายตัวไปเลย ส่วนหัวจึงมองเห็นครบทั้งแผงตั้งแต่วินาทีแรกโดยไม่ต้องเลื่อน และไม่ขยับอีกเลยไม่ว่าแถบเครื่องมือจะทำอะไรก็ตาม ราคาที่เราจ่ายก็คือ พอแถบหุบไปแล้วจะมีที่ว่างเหลือด้านล่างนิดหน่อย
เราว่านี่เป็นการแลกที่คุ้มมากครับ และมันเป็นหลักการที่ใช้ได้กว้างกว่าเรื่องหน่วยความสูงเยอะ นั่นคือ เลือกเลย์เอาต์ที่นิ่ง มากกว่าเลย์เอาต์ที่พอดีที่สุดในแต่ละขณะ ที่ว่างเกินมาสองสามสิบพิกเซลนั้นไม่มีใครสังเกตหรอกครับ แต่หน้าเว็บที่ขยับอยู่ใต้สายตาคนอ่านนี่รู้สึกได้ทุกครั้งจริง ๆ
ตัวอักษรไทยที่เล็กลงเองโดยไม่มีใครสั่ง
เรื่องที่สองนี่สนุกครับ เพราะมันอยู่ในย่อหน้าที่คุณกำลังอ่านอยู่นี่เอง
เว็บนี้เราใช้ Poppins สำหรับอักษรละติน และ Noto Sans Thai สำหรับภาษาไทย ทั้งสองฟอนต์ถูกสั่งด้วย font-size ค่าเดียวกันเป๊ะ แต่พอขึ้นจอจริง ตัวไทยกลับดูเล็กกว่าอย่างเห็นได้ชัด ลองกวาดตามองบรรทัดนี้เทียบกับคำว่า Poppins ดูก็ได้ครับ
สาเหตุคือ font-size ไม่ได้กำหนดขนาดของตัวอักษรที่ตาเราเห็น มันกำหนดขนาดของ em ซึ่งเป็นกล่องนามธรรมที่ผู้ออกแบบฟอนต์แต่ละคนตัดสินใจเองว่าจะวางตัวอักษรใหญ่แค่ไหนอยู่ข้างใน สัดส่วนที่สำคัญจริง ๆ คือ x-height หรือความสูงของตัวพิมพ์เล็กเทียบกับ em และฟอนต์สองตระกูลนี้ให้ค่าไม่เท่ากันครับ คนอ่านอย่างเรา ๆ ตัดสินขนาดจาก x-height ไม่ได้ตัดสินจากกล่อง em ที่มองไม่เห็น
แล้วปัญหาก็ไม่ได้จบแค่ความกว้างของตัวอักษรด้วย ภาษาไทยยังวางสระบนกับวรรณยุกต์ซ้อนขึ้นไปเหนือบรรทัด และวางสระล่างลงไปใต้บรรทัดอีก คำอย่าง ที่ ผู้ โค้ จึงกินพื้นที่แนวตั้งมากกว่าคำภาษาอังกฤษที่มีจำนวนตัวอักษรเท่ากัน ถ้าเราปล่อยให้ line-height: normal ทำงานไปตามยถากรรม แต่ละฟอนต์จะตีความค่านั้นด้วยเมตริกของตัวเอง ผลคือมาร์กอัปชุดเดียวกันแท้ ๆ กลับให้หน้าเว็บภาษาไทยที่สูงกว่าภาษาอังกฤษ ปุ่มไม่ตรงกัน การ์ดในกริดเดียวกันสูงไม่เท่ากัน และทุกอย่างที่เคยจัดไว้ลงตัวก็เพี้ยนไปทีละนิด
วิธีที่คนส่วนใหญ่ใช้ แล้วทำไมมันถึงเอาไม่อยู่
ทางแก้ที่เราเจอบ่อยที่สุดคือตั้ง font-size ให้ภาษาไทยใหญ่กว่าอังกฤษ เช่นบวกเพิ่มอีกหนึ่งหรือสองพิกเซลเมื่อสลับภาษา
วิธีนี้ใช้ได้ครับ ตราบใดที่ทั้งประโยคเป็นภาษาเดียวล้วน ๆ แต่เพื่อน ๆ ที่ทำงานสายนี้ย่อมรู้ดีว่างานเขียนธุรกิจไทยแทบไม่เคยเป็นแบบนั้นเลย ประโยคจริงหน้าตาประมาณว่า "ทีม backend ส่ง API ให้ QA แล้ว" ทันทีที่สองสคริปต์มาอยู่ในบรรทัดเดียวกัน กฎที่ผูกไว้กับภาษาของทั้งหน้าก็ช่วยอะไรไม่ได้แล้ว เพราะขนาดต้องต่างกันภายในประโยคเดียว ไม่ใช่ต่างกันทั้งหน้า
font-size-adjust ตัวช่วยที่แก้ถูกจุด
font-size-adjust: from-font แก้ที่ต้นเหตุเลยครับ
มันจะอ่านค่า aspect value ของฟอนต์ตัวแรกในสแตก ซึ่งในที่นี้คือ Poppins แล้วบังคับให้ฟอนต์ที่เหลือในสแตกถูกย่อขยายจนกระทั่ง x-height ตรงกับค่านั้น Noto Sans Thai จึงถูกขยายขึ้นให้เองโดยอัตโนมัติ ไม่ว่ามันจะไปโผล่อยู่ตรงไหนก็ตาม คำไทยคำเดียวที่แทรกกลางประโยคภาษาอังกฤษก็ได้ขนาดที่ถูกต้อง เพราะการปรับเกิดขึ้นที่ระดับฟอนต์ ไม่ใช่ระดับย่อหน้าหรือระดับหน้า น่าสนใจมากเลยแหละครับ
พอขนาดที่ตาเห็นตรงกันแล้ว ความสูงบรรทัดก็ตรงตามไปด้วย เพราะเรากำหนด line-height เป็นตัวเลขเปล่า คือ 1.6 สำหรับเนื้อความและ 1.3 สำหรับหัวข้อ ทั้งสองสคริปต์จึงคำนวณจากฐานเดียวกัน ไม่ได้ปล่อยให้แต่ละฟอนต์ตัดสินใจกันเอง หน้าไทยกับหน้าอังกฤษก็เลยสูงเท่ากันจริง ๆ
ทั้งบล็อกนี้เราห่อไว้ใน @supports (font-size-adjust: from-font) เบราว์เซอร์ที่ยังไม่รองรับจะไม่เห็นกฎนี้เลย และได้หน้าเว็บแบบเดิมที่ยังพออ่านได้สบาย ไม่ใช่หน้าเว็บที่พัง
เรื่องที่เว็บไทยเจอกันทุกเว็บ แต่แทบไม่มีใครแก้
เรื่องนี้ไม่ใช่กรณีพิเศษของเว็บนี้นะครับ ทุกเว็บที่มีทั้งไทยและอังกฤษเจอมันหมด แต่ส่วนใหญ่ก็ลงเอยด้วยการยอมรับว่า "ตัวไทยมันก็เป็นแบบนี้แหละ" แล้วเดินหน้าทำงานต่อ
ผลลัพธ์ที่ได้คือหน้าเว็บภาษาไทยจำนวนมากที่อ่านยากกว่าที่ควรจะเป็นอยู่นิดเดียว มากพอที่คนอ่านจะเหนื่อยเร็วขึ้น แต่ไม่มากพอที่ใครจะชี้ได้ว่าปัญหามันอยู่ตรงไหนกันแน่
ที่ตลกก็คือ การแก้ใช้โค้ดแค่สี่บรรทัดเองครับ สิ่งที่ยากไม่ใช่การเขียน แต่คือการสังเกตเห็นว่ามันมีปัญหาอยู่ตั้งแต่แรกต่างหาก
แล้วที่ GIPSIC เราทำอะไรตรงนี้
พวกเราทำเว็บไซต์และระบบที่ต้องรองรับสองภาษากันแบบจริงจัง ไม่ใช่แค่แปลข้อความแล้วสลับไฟล์ เพราะการรองรับภาษาไทยที่ดีคืองานเรื่องเมตริกของตัวอักษร ความสูงบรรทัด การตัดคำ และการเรียงลำดับ ซึ่งเป็นงานที่ต้องตัดสินใจกันตั้งแต่ชั้นล่างสุดของระบบออกแบบ
รายละเอียดที่เล่ามาทั้งหมดในบทความนี้ทำงานอยู่จริงในเว็บที่คุณกำลังอ่านอยู่ตอนนี้แหละครับ ถ้าอยากดูโค้ดจริงตรงไหน ติดต่อเราได้ เลยครับ คงจะสนุกมาก ๆ ถ้าได้คุยเรื่องพวกนี้กัน
เกี่ยวกับผู้เขียน
ฟิล์ม — วิสิฏฐ์ ครับ ทำธุรกิจเป็นหลัก แต่ยังลงมือทำ BA เองอยู่บ่อยกว่าที่ควร และเขียนโค้ดได้ประปรายทั้งหน้าบ้านหลังบ้าน มีธุรกิจเล็ก ๆ อยู่สองสามบริษัท ติดตามวงการเทคโนโลยีและธุรกิจแบบคลั่งไคล้ ทั้งในไทยและต่างประเทศ นอกเวลางานชอบฟิสิกส์ ดาราศาสตร์ งาน DIY และเรื่องเน็ตเวิร์ก เปิดเพลงฟังตลอดเวลาแต่ร้องไม่เป็น ชอบเล่นดนตรีทั้งที่เล่นได้ไม่ค่อยดี เล่นกีฬาหลายอย่าง โดยเฉพาะกีฬาแร็กเกต เสียอยู่อย่างเดียวคือแทบไม่เล่นเกม
เขียนร่วมกับ Claude Opus 5