คำค้นหาคอนเทนเนอร์เป็นฟีเจอร์ใหม่ของ CSS ที่ให้คุณเขียนตรรกะการจัดรูปแบบที่กำหนดเป้าหมายไปยังฟีเจอร์ขององค์ประกอบระดับบนสุด (เช่น ความกว้างหรือความสูงขององค์ประกอบ) เพื่อจัดรูปแบบองค์ประกอบย่อยได้ และเมื่อเร็วๆ นี้ก็ได้มีการเผยแพร่การอัปเดตครั้งใหญ่สำหรับ polyfill ซึ่งตรงกับการสนับสนุนในเบราว์เซอร์
ในโพสต์นี้ คุณจะได้เจาะลึกวิธีการทำงานของ Polyfill, ปัญหาที่ต้องเอาชนะ และแนวทางปฏิบัติที่ดีที่สุดเมื่อใช้ เพื่อให้ผู้เข้าชมได้รับประสบการณ์ที่ยอดเยี่ยม
ขั้นสูง
การแปล
เมื่อโปรแกรมแยกวิเคราะห์ CSS ภายในเบราว์เซอร์พบกฎที่ไม่รู้จัก เช่น กฎ @container
ใหม่ ระบบจะทิ้งกฎนั้นเสมือนว่าไม่เคยมีอยู่ ดังนั้น สิ่งแรกและสำคัญที่สุดที่ Polyfill จะต้องทำคือแปลงการค้นหา @container
เป็นข้อมูลที่ระบบจะไม่ทิ้ง
ขั้นตอนแรกในการเปลี่ยนรูปแบบคือการแปลงกฎ @container
ระดับบนสุดเป็นคำค้นหา @media ซึ่งจะช่วยให้มั่นใจได้ว่าเนื้อหาจะยังคงจัดกลุ่มไว้ด้วยกัน เช่น เมื่อใช้ CSSOM API และเมื่อดูซอร์สโค้ด CSS
@container (width > 300px) { /* content */ }
@media all { /* content */ }
ก่อนการค้นหาคอนเทนเนอร์ CSS ไม่มีวิธีให้ผู้เขียนเปิดหรือปิดใช้กลุ่มกฎโดยพลการ หากต้องการทำให้ลักษณะการทำงานนี้มีรูปแบบใหม่ ก็ต้องเปลี่ยนรูปแบบกฎภายในการค้นหาคอนเทนเนอร์ด้วย @container
แต่ละรายการจะมีรหัสที่ไม่ซ้ำกัน (เช่น 123
) ซึ่งใช้ในการเปลี่ยนรูปแบบตัวเลือกแต่ละรายการเพื่อให้ตัวเลือกนั้นมีผลเฉพาะเมื่อองค์ประกอบมีแอตทริบิวต์ cq-XYZ
รวมถึงรหัสนี้ Polyfill จะตั้งค่าแอตทริบิวต์นี้ระหว่างรันไทม์
@container (width > 300px) { .card { /* ... */ } }
@media all { .card:where([cq-XYZ~="123"]) { /* ... */ } }
โปรดสังเกตการใช้คลาส Pseudo ของ :where(...)
โดยปกติแล้ว การรวมตัวเลือกแอตทริบิวต์เพิ่มเติมจะเพิ่มความเฉพาะเจาะจงของตัวเลือก เมื่อใช้คลาสเทียม จะมีการใช้เงื่อนไขพิเศษโดยคงความเฉพาะเจาะจงเดิมไว้ ลองพิจารณาตัวอย่างต่อไปนี้เพื่อดูว่าเหตุใดจึงมีความสำคัญ
@container (width > 300px) {
.card {
color: blue;
}
}
.card {
color: red;
}
ด้วย CSS นี้ องค์ประกอบที่มีคลาส .card
ควรมี color: red
เสมอ เนื่องจากกฎในภายหลังจะลบล้างกฎก่อนหน้าที่มีตัวเลือกและความเฉพาะเจาะจงเดียวกันเสมอ การเปลี่ยนรูปแบบกฎแรกและการรวมตัวเลือกแอตทริบิวต์เพิ่มเติมโดยไม่มี :where(...)
จะเป็นการเพิ่มความเฉพาะเจาะจง และทำให้ระบบใช้ color: blue
อย่างไม่ถูกต้อง
แต่คลาส Pseudo ของ :where(...)
นั้นค่อนข้างใหม่ สำหรับเบราว์เซอร์ที่ไม่รองรับ Polyfill จะมีวิธีหลีกเลี่ยงปัญหาที่ปลอดภัยและใช้งานง่าย ซึ่งคุณเพิ่มความจำเพาะของกฎโดยตั้งใจได้โดยการเพิ่มตัวเลือก :not(.container-query-polyfill)
จำลองลงในกฎ @container
ด้วยตนเอง ดังนี้
@container (width > 300px) { .card { color: blue; } } .card { color: red; }
@container (width > 300px) { .card:not(.container-query-polyfill) { color: blue; } } .card { color: red; }
ซึ่งมีประโยชน์หลายประการดังนี้
- ตัวเลือกใน CSS แหล่งที่มามีการเปลี่ยนแปลง ดังนั้นจึงสามารถมองเห็นความแตกต่างในความจำเพาะได้อย่างชัดเจน ข้อมูลนี้ยังทำหน้าที่เป็นเอกสารประกอบเพื่อให้คุณทราบสิ่งที่ได้รับผลกระทบเมื่อไม่จำเป็นต้องใช้วิธีแก้ปัญหาชั่วคราวหรือ Polyfill อีกต่อไป
- ความจำเพาะของกฎจะยังคงเหมือนเดิมเสมอ เนื่องจาก Polyfill จะไม่เปลี่ยนแปลง
ในระหว่างการเปลี่ยนรูปแบบ โพลีฟิลจะแทนที่หุ่นนี้ด้วยตัวเลือกแอตทริบิวต์ที่มีความเฉพาะเจาะจงเหมือนกัน Polyfill จะใช้ตัวเลือกทั้ง 2 ประเภทเพื่อหลีกเลี่ยงเรื่องน่าประหลาดใจ โดยจะใช้ตัวเลือกแหล่งที่มาเดิมเพื่อระบุว่าองค์ประกอบควรได้รับแอตทริบิวต์ Polyfill หรือไม่ และใช้ตัวเลือกที่เปลี่ยนรูปแบบแล้วสำหรับการจัดรูปแบบ
องค์ประกอบที่ไม่ระบุตัวบุคคล
คุณอาจสงสัยว่าหาก Polyfill ตั้งค่าแอตทริบิวต์ cq-XYZ
บางรายการในองค์ประกอบให้รวมรหัสคอนเทนเนอร์ 123
ที่ไม่ซ้ำกัน จะรองรับองค์ประกอบจำลองที่ตั้งค่าแอตทริบิวต์ไม่ได้ได้อย่างไร
องค์ประกอบจำลองจะเชื่อมโยงกับองค์ประกอบจริงใน DOM เสมอ ซึ่งเรียกว่าองค์ประกอบต้นทาง ระหว่างการเปลี่ยนรูปแบบ ระบบจะใช้ตัวเลือกแบบมีเงื่อนไขกับองค์ประกอบจริงนี้แทน ดังนี้
@container (width > 300px) { #foo::before { /* ... */ } }
@media all { #foo:where([cq-XYZ~="123"])::before { /* ... */ } }
แทนที่จะแปลงเป็น #foo::before:where([cq-XYZ~="123"])
(ซึ่งจะใช้ไม่ได้) ระบบจะย้ายตัวเลือกแบบมีเงื่อนไขไปท้ายองค์ประกอบต้นทาง #foo
อย่างไรก็ตาม ยังไม่จำเป็น คอนเทนเนอร์ไม่ได้รับอนุญาตให้แก้ไขสิ่งที่ไม่ได้อยู่ภายใน (และคอนเทนเนอร์ต้องไม่อยู่ภายในตัวเอง) แต่ให้คำนึงถึงสิ่งที่จะเกิดขึ้นหาก #foo
เป็นองค์ประกอบคอนเทนเนอร์ที่ค้นหาเอง แอตทริบิวต์ #foo[cq-XYZ]
จะมีการเปลี่ยนแปลงอย่างไม่ถูกต้อง และระบบจะใช้กฎ #foo
อย่างไม่ถูกต้อง
เพื่อแก้ไขปัญหานี้ โพลีฟิลจะใช้แอตทริบิวต์ 2 รายการจริงๆ โดย 1 แอตทริบิวต์ใช้ได้กับองค์ประกอบหลักเท่านั้น และอีกแอตทริบิวต์ที่องค์ประกอบหนึ่งใช้ได้กับตัวเอง แอตทริบิวต์หลังใช้สำหรับตัวเลือกที่กำหนดเป้าหมายองค์ประกอบจำลอง
@container (width > 300px) { #foo, #foo::before { /* ... */ } }
@media all { #foo:where([cq-XYZ-A~="123"]), #foo:where([cq-XYZ-B~="123"])::before { /* ... */ } }
เนื่องจากคอนเทนเนอร์จะไม่ใช้แอตทริบิวต์แรก (cq-XYZ-A
) กับตัวเอง ตัวเลือกแรกจึงจะจับคู่ก็ต่อเมื่อคอนเทนเนอร์หลักอื่นตรงกับเงื่อนไขคอนเทนเนอร์และนำไปใช้เท่านั้น
หน่วยสัมพัทธ์ของคอนเทนเนอร์
การค้นหาคอนเทนเนอร์ยังมาพร้อมกับหน่วยโฆษณาใหม่ 2-3 หน่วยที่คุณสามารถใช้ใน CSS ได้ เช่น cqw
และ cqh
สำหรับ 1% ของความกว้างและความสูง (ตามลำดับ) ของคอนเทนเนอร์หลักที่ใกล้เคียงที่สุด เพื่อรองรับฟังก์ชันเหล่านี้ หน่วยจะเปลี่ยนเป็นนิพจน์ calc(...)
โดยใช้พร็อพเพอร์ตี้ที่กำหนดเองของ CSS Polyfill จะตั้งค่าสำหรับพร็อพเพอร์ตี้เหล่านี้ผ่านรูปแบบอินไลน์ในองค์ประกอบคอนเทนเนอร์
.card { width: 10cqw; height: 10cqh; }
.card { width: calc(10 * --cq-XYZ-cqw); height: calc(10 * --cq-XYZ-cqh); }
นอกจากนี้ ยังมีหน่วยเชิงตรรกะ เช่น cqi
และ cqb
สำหรับขนาดในหน้าและขนาดบล็อก (ตามลำดับ) ขั้นตอนเหล่านี้จะซับซ้อนขึ้นเล็กน้อย เนื่องจากแกนในบรรทัดและบล็อกจะกำหนดโดย writing-mode
ขององค์ประกอบที่ใช้หน่วย ไม่ใช่องค์ประกอบที่ค้นหา Polyfill จะใช้รูปแบบอินไลน์กับองค์ประกอบที่มี writing-mode
ต่างจากองค์ประกอบหลักเพื่อรองรับวัตถุประสงค์นี้
/* Element with a horizontal writing mode */
--cq-XYZ-cqi: var(--cq-XYZ-cqw);
--cq-XYZ-cqb: var(--cq-XYZ-cqh);
/* Element with a vertical writing mode */
--cq-XYZ-cqi: var(--cq-XYZ-cqh);
--cq-XYZ-cqb: var(--cq-XYZ-cqw);
ตอนนี้ หน่วยดังกล่าวยังสามารถแปลงเป็นพร็อพเพอร์ตี้ที่กำหนดเองของ CSS ที่เหมาะสมเหมือนเดิม
พร็อพเพอร์ตี้
การค้นหาคอนเทนเนอร์ยังเพิ่มพร็อพเพอร์ตี้ CSS ใหม่ๆ อีก 2-3 รายการ เช่น container-type
และ container-name
เนื่องจาก API เช่น getComputedStyle(...)
ใช้ไม่ได้กับพร็อพเพอร์ตี้ที่ไม่รู้จักหรือไม่ถูกต้อง ระบบจึงจะเปลี่ยนรูปแบบ API เหล่านี้เป็นพร็อพเพอร์ตี้ที่กำหนดเองของ CSS ด้วยหลังจากแยกวิเคราะห์แล้ว หากแยกวิเคราะห์พร็อพเพอร์ตี้ไม่ได้ (เช่น เนื่องจากมีค่าที่ไม่ถูกต้องหรือไม่ทราบ) ปล่อยให้เบราว์เซอร์จัดการต่อไป
.card { container-name: card-container; container-type: inline-size; }
.card { --cq-XYZ-container-name: card-container; --cq-XYZ-container-type: inline-size; }
คุณสมบัติเหล่านี้จะเปลี่ยนรูปแบบเมื่อใดก็ตามที่ระบบค้นพบ ทำให้ Polyfill สามารถทำงานกับฟีเจอร์อื่นๆ ของ CSS เช่น @supports
ได้อย่างสวยงาม ฟังก์ชันการทำงานนี้เป็นพื้นฐานของแนวทางปฏิบัติแนะนำในการใช้ Polyfill ตามที่ระบุไว้ด้านล่าง
@supports (container-type: inline-size) { /* ... */ }
@supports (--cq-XYZ-container-type: inline-size) { /* ... */ }
โดยค่าเริ่มต้น พร็อพเพอร์ตี้ที่กำหนดเองของ CSS จะรับค่าเดิมมา เช่น หน่วยย่อยของ .card
จะใช้ค่า --cq-XYZ-container-name
และ --cq-XYZ-container-type
นั่นไม่ใช่ลักษณะการทำงานของพร็อพเพอร์ตี้เนทีฟอย่างแน่นอน หากต้องการแก้ไขปัญหานี้ Polyfill จะแทรกกฎต่อไปนี้ก่อนสไตล์โฆษณาของผู้ใช้ เพื่อให้มั่นใจว่าทุกองค์ประกอบได้รับค่าเริ่มต้น เว้นแต่จะถูกกฎอื่นลบล้างโดยเจตนา
* {
--cq-XYZ-container-name: none;
--cq-XYZ-container-type: normal;
}
แนวทางปฏิบัติแนะนำ
แม้ว่าจะมีการคาดหวังว่าผู้เข้าชมส่วนใหญ่จะใช้เบราว์เซอร์ที่มีการสนับสนุนการค้นหาคอนเทนเนอร์ในตัวเร็วกว่าภายหลัง แต่ยังคงเป็นสิ่งสำคัญที่จะต้องให้ประสบการณ์ที่ดีแก่ผู้เข้าชมที่เหลือของคุณ
ระหว่างการโหลดครั้งแรก มีหลายสิ่งที่ต้องเกิดขึ้นก่อนที่ Polyfill จะจัดวางหน้าเว็บได้
- ต้องโหลดและเริ่มต้น Polyfill
- สไตล์ชีตต้องได้รับการแยกวิเคราะห์และเปลี่ยนรูปแบบ เนื่องจากไม่มี API ใดๆ ที่จะเข้าถึงแหล่งดิบของสไตล์ชีตภายนอกได้ จึงอาจจำเป็นต้องถูกดึงข้อมูลใหม่แบบไม่พร้อมกัน แม้ว่าจะเป็นการดีหากทำได้จากแคชของเบราว์เซอร์เท่านั้น
หาก Polyfill ไม่ได้แก้ไขข้อกังวลเหล่านี้อย่างรอบคอบ อาจทำให้ Core Web Vitals ทำงานถดถอย
เพื่อให้คุณมอบประสบการณ์การใช้งานที่น่าพึงพอใจให้แก่ผู้เข้าชมได้ง่ายขึ้น Polyfill ได้รับการออกแบบมาเพื่อให้ความสำคัญกับ First Input Delay (FID) และ Cumulative Layout Shift (CLS) ซึ่งอาจลดทอนค่า Largest Contentful Paint (LCP) กล่าวอย่างชัดเจนคือ โพลีฟิลไม่ได้รับประกันว่าจะมีการประเมินคำค้นหาคอนเทนเนอร์ของคุณก่อนการลงสีครั้งแรก ซึ่งหมายความว่าเพื่อให้ผู้ใช้ได้รับประสบการณ์ที่ดีที่สุด คุณต้องตรวจสอบว่าเนื้อหาที่มีขนาดหรือตำแหน่งจะได้รับผลกระทบจากการใช้การค้นหาคอนเทนเนอร์จะถูกซ่อนไว้จนกว่า Polyfill จะโหลดและแปลง CSS แล้ว วิธีหนึ่งที่จะทำเช่นนี้ได้คือการใช้กฎ @supports
ดังนี้
@supports not (container-type: inline-size) {
#content {
visibility: hidden;
}
}
เราขอแนะนำให้คุณรวมสิ่งนี้เข้ากับภาพเคลื่อนไหวการโหลด CSS อย่างเดียว โดยวางไว้บนเนื้อหา (ที่ซ่อนไว้) จริงๆ เพื่อบอกให้ผู้เข้าชมรู้ว่ามีบางอย่างเกิดขึ้น คุณดูการสาธิตวิธีการนี้แบบเต็มได้ที่นี่
เราแนะนำให้ใช้วิธีนี้ด้วยเหตุผลหลายประการดังนี้
- ตัวโหลด CSS เพียงอย่างเดียวช่วยลดค่าใช้จ่ายให้กับผู้ใช้ที่ใช้เบราว์เซอร์รุ่นใหม่ๆ และให้ฟีดแบ็กที่ใช้เบราว์เซอร์รุ่นเก่าและเครือข่ายที่ช้า
- การรวมตําแหน่งแบบสัมบูรณ์ของตัวโหลดกับ
visibility: hidden
จะช่วยป้องกันการเปลี่ยนเลย์เอาต์ - หลังจากโหลด Polyfill แล้ว เงื่อนไข
@supports
นี้จะหยุดส่งและจะแสดงเนื้อหาของคุณ - ในเบราว์เซอร์ที่มีการสนับสนุนในตัวสำหรับการค้นหาคอนเทนเนอร์ เงื่อนไขจะไม่ผ่านเลย และดังนั้นหน้าเว็บจึงแสดงเป็นสีแรกตามที่คาดไว้
บทสรุป
หากคุณสนใจใช้การค้นหาคอนเทนเนอร์ในเบราว์เซอร์รุ่นเก่า ให้ลองใช้ polyfill คุณสามารถแจ้งปัญหาได้ทันทีหากพบปัญหา
เราแทบอดใจรอไม่ไหวที่จะได้เห็นและสัมผัสกับสิ่งดีๆ ที่คุณจะสร้างขึ้นจากบริการนี้
ข้อความแสดงการยอมรับ
รูปภาพหลักของ Dan Cristian Pădureในเร็วๆ นี้ใน Unsplash