คิวรีคอนเทนเนอร์คือฟีเจอร์ CSS ใหม่ที่ให้คุณเขียนตรรกะการจัดรูปแบบที่กำหนดเป้าหมายไปยังฟีเจอร์ขององค์ประกอบหลัก (เช่น ความกว้างหรือความสูง) เพื่อจัดรูปแบบองค์ประกอบย่อย เมื่อเร็วๆ นี้ เราได้ปล่อยการอัปเดตครั้งใหญ่สำหรับ polyfill ซึ่งสอดคล้องกับหน้าการสนับสนุนในเบราว์เซอร์
ในโพสต์นี้ คุณจะได้ดูวิธีการทํางานของ polyfill, ปัญหาที่ polyfill แก้ปัญหาได้ และแนวทางปฏิบัติแนะนําเมื่อใช้ polyfill เพื่อมอบประสบการณ์การใช้งานที่ยอดเยี่ยมแก่ผู้เข้าชม
กลไกภายใน
การแปลง
เมื่อโปรแกรมแยกวิเคราะห์ CSS ภายในเบราว์เซอร์พบ at-rule ที่ไม่รู้จัก เช่น กฎ @container
ใหม่ล่าสุด ระบบจะทิ้งกฎนั้นราวกับว่าไม่เคยมีมาก่อน ดังนั้น สิ่งแรกและสำคัญที่สุดที่ polyfill ต้องทำคือการแปลงคำสั่ง @container
ให้เป็นรูปแบบที่ระบบจะไม่ทิ้ง
ขั้นตอนแรกในการแปลงภาษาคือแปลงกฎ @container
ระดับบนสุดเป็นข้อความค้นหา @media วิธีนี้ช่วยให้มั่นใจได้ว่าเนื้อหาจะยังคงจัดกลุ่มไว้ด้วยกัน เช่น เมื่อใช้ CSSOM API และเมื่อดูแหล่งที่มาของ CSS
@container (width > 300px) { /* content */ }
@media all { /* content */ }
ก่อนที่จะมีคำถามที่กรองคอนเทนเนอร์ CSS ยังไม่มีวิธีที่ผู้เขียนจะเปิดหรือปิดใช้กลุ่มกฎได้ตามอำเภอใจ หากต้องการใช้การแปลงข้อมูลพฤติกรรมนี้ กฎภายในการค้นหาคอนเทนเนอร์จะต้องได้รับการเปลี่ยนรูปแบบด้วย @container
แต่ละรายการจะได้รับรหัสที่ไม่ซ้ำกัน (เช่น 123
) ซึ่งจะใช้เพื่อเปลี่ยนรูปแบบตัวเลือกแต่ละรายการเพื่อให้มีผลเฉพาะเมื่อองค์ประกอบมีแอตทริบิวต์ cq-XYZ
ที่มีรหัสนี้ โพลีฟิลล์จะตั้งค่าแอตทริบิวต์นี้เมื่อรันไทม์
@container (width > 300px) { .card { /* ... */ } }
@media all { .card:where([cq-XYZ~="123"]) { /* ... */ } }
สังเกตการใช้คลาสจำลอง :where(...)
โดยทั่วไปแล้ว การเพิ่มตัวเลือกแอตทริบิวต์อีกรายการจะเพิ่มความเฉพาะเจาะจงของตัวเลือก เมื่อใช้คลาสจำลอง คุณจะใช้เงื่อนไขเพิ่มเติมได้ในขณะที่ยังคงความเฉพาะเจาะจงเดิมไว้ ลองดูตัวอย่างต่อไปนี้เพื่อทําความเข้าใจความสำคัญของเรื่องนี้
@container (width > 300px) {
.card {
color: blue;
}
}
.card {
color: red;
}
เมื่อใช้ CSS นี้ องค์ประกอบที่มีคลาส .card
ควรมี color: red
เสมอ เนื่องจากกฎที่เขียนขึ้นภายหลังจะลบล้างกฎก่อนหน้าที่มีเครื่องมือเลือกและความเฉพาะเจาะจงเดียวกันเสมอ ดังนั้น การแปลงกฎแรกและใส่ตัวเลือกแอตทริบิวต์เพิ่มเติมโดยไม่มี :where(...)
จะเพิ่มความเฉพาะเจาะจงและทําให้ใช้ color: blue
อย่างไม่ถูกต้อง
อย่างไรก็ตาม คลาสจำลอง :where(...)
ค่อนข้างใหม่ สําหรับเบราว์เซอร์ที่ไม่รองรับ โพลีฟีลจะมอบวิธีแก้ปัญหาที่ปลอดภัยและง่ายดาย คุณสามารถจงใจเพิ่มความเฉพาะเจาะจงของกฎได้โดยการเพิ่มตัวเลือก :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
แทนที่จะเปลี่ยนรูปแบบเป็น #foo::before:where([cq-XYZ~="123"])
(ซึ่งไม่ถูกต้อง)
แต่ยังมีสิ่งอื่นๆ ที่จำเป็นด้วย คอนเทนเนอร์ไม่ได้รับอนุญาตให้แก้ไขสิ่งที่ไม่ได้อยู่ภายในคอนเทนเนอร์ (และคอนเทนเนอร์ไม่สามารถอยู่ภายในตัวคอนเทนเนอร์เองได้) แต่ให้คิดว่าสิ่งที่จะเกิดขึ้นคือ #foo
เป็นองค์ประกอบคอนเทนเนอร์ที่ระบบค้นหา ระบบจะเปลี่ยนแปลงแอตทริบิวต์ #foo[cq-XYZ]
อย่างไม่ถูกต้อง และจะใช้กฎ #foo
อย่างไม่ถูกต้อง
ในการแก้ไขข้อบกพร่องนี้ โพลีฟิลล์จึงใช้แอตทริบิวต์ 2 รายการ ได้แก่ แอตทริบิวต์ที่องค์ประกอบระดับบนสุดใช้ได้กับองค์ประกอบเท่านั้น และแอตทริบิวต์ที่องค์ประกอบใช้ได้กับตนเอง แอตทริบิวต์หลังใช้สำหรับตัวเลือกที่กำหนดเป้าหมายไปยังองค์ประกอบจำลอง
@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 เพื่อรองรับรายการเหล่านี้ โพลีฟิลล์จะตั้งค่าสำหรับพร็อพเพอร์ตี้เหล่านี้ผ่านสไตล์แบบแทรกในองค์ประกอบคอนเทนเนอร์
.card { width: 10cqw; height: 10cqh; }
.card { width: calc(10 * --cq-XYZ-cqw); height: calc(10 * --cq-XYZ-cqh); }
นอกจากนี้ยังมีหน่วยเชิงตรรกะ เช่น cqi
และ cqb
สำหรับขนาดในบรรทัดและขนาดบล็อก (ตามลำดับ) การดำเนินการเหล่านี้มีความซับซ้อนกว่าเล็กน้อย เนื่องจากแกนแนวนอนและแกนแนวตั้งจะกำหนดโดย writing-mode
ขององค์ประกอบที่ใช้หน่วย ไม่ใช่องค์ประกอบที่ค้นหา ในการรองรับกรณีนี้ โพลีฟีลจะใช้สไตล์แบบแทรกในบรรทัดกับองค์ประกอบที่มี 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; }
ระบบจะเปลี่ยนรูปแบบพร็อพเพอร์ตี้เหล่านี้ทุกครั้งที่ค้นพบ ซึ่งช่วยให้โพลีฟีลทำงานร่วมกับฟีเจอร์อื่นๆ ของ 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
ซึ่งไม่ใช่ลักษณะการทำงานของพร็อพเพอร์ตี้เนทีฟ ในการแก้ปัญหานี้ โพลีฟีลจะแทรกกฎต่อไปนี้ไว้ก่อนสไตล์ของผู้ใช้ เพื่อให้แน่ใจว่าองค์ประกอบทุกรายการจะได้รับค่าเริ่มต้น เว้นแต่ว่าจะมีกฎอื่นเขียนทับไว้
* {
--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 ไม่ได้รับประกันว่าจะมีการประเมินการค้นหาคอนเทนเนอร์ก่อน First Paint ซึ่งหมายความว่าคุณต้องซ่อนเนื้อหาที่มีขนาดหรือตําแหน่งจะได้รับผลกระทบจากการใช้การค้นหาคอนเทนเนอร์ไว้จนกว่า polyfill จะโหลดและแปลง CSS ของคุณแล้ว เพื่อให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่ดีที่สุด วิธีหนึ่งในการทำเช่นนี้คือการใช้กฎ @supports
@supports not (container-type: inline-size) {
#content {
visibility: hidden;
}
}
เราขอแนะนำให้คุณรวมสิ่งนี้เข้ากับภาพเคลื่อนไหวการโหลด CSS ล้วนๆ ซึ่งวางไว้เหนือเนื้อหา (ที่ซ่อนอยู่) อย่างสมบูรณ์ เพื่อบอกผู้เข้าชมว่ากำลังมีบางอย่างเกิดขึ้น ดูการสาธิตวิธีการนี้แบบเต็มได้ที่นี่
เราขอแนะนําแนวทางนี้ด้วยเหตุผลหลายประการดังนี้
- โปรแกรมโหลด CSS ล้วนจะลดค่าใช้จ่ายเพิ่มเติมสำหรับผู้ใช้ที่มีเบราว์เซอร์รุ่นใหม่ๆ ในขณะเดียวกันก็ให้การแสดงผลที่เบาสำหรับผู้ใช้ที่มีเบราว์เซอร์รุ่นเก่าและเครือข่ายที่ช้ากว่า
- การรวมตำแหน่งสัมบูรณ์ของโปรแกรมโหลดกับ
visibility: hidden
จะช่วยป้องกันไม่ให้เลย์เอาต์เปลี่ยนแปลง - หลังจากโหลด polyfill แล้ว เงื่อนไข
@supports
นี้จะหยุดทำงาน และระบบจะแสดงเนื้อหาของคุณ - ในเบราว์เซอร์ที่รองรับการค้นหาคอนเทนเนอร์ในตัว เงื่อนไขจะไม่ผ่านเลย ดังนั้นหน้าเว็บจะแสดงใน First Paint ตามปกติ
บทสรุป
หากสนใจที่จะใช้การค้นหาคอนเทนเนอร์ในเบราว์เซอร์รุ่นเก่า ให้ลองใช้ polyfill โปรดแจ้งปัญหาหากพบปัญหา
เราอดใจรอที่จะได้เห็นและสัมผัสประสบการณ์การใช้งานที่ยอดเยี่ยมที่คุณสร้างขึ้นด้วยเครื่องมือนี้