ภายใน Polyfill การค้นหาคอนเทนเนอร์

Gerald Monaco
Gerald Monaco

การค้นหาคอนเทนเนอร์เป็นฟีเจอร์ใหม่ของ CSS ที่ให้คุณเขียนตรรกะการจัดรูปแบบที่กำหนดเป้าหมายไปยังฟีเจอร์ขององค์ประกอบหลัก (เช่น ความกว้างหรือความสูง) เพื่อจัดรูปแบบองค์ประกอบย่อยได้ เมื่อเร็วๆ นี้ เราได้เปิดตัวการอัปเดตครั้งใหญ่เกี่ยวกับ polyfill ไปพร้อมกับการสนับสนุนในเบราว์เซอร์

ในโพสต์นี้ คุณจะได้เห็นวิธีการทำงานของ 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"]) {
    /* ... */
  }
}

โปรดสังเกตการใช้คลาสเทียม :where(...) โดยปกติแล้ว การรวมตัวเลือกแอตทริบิวต์เพิ่มเติมจะเพิ่มความจำเพาะของตัวเลือกดังกล่าว เมื่อใช้คลาสเทียม จะมีการใช้เงื่อนไขเพิ่มเติมในขณะที่คงความเจาะจงเดิมไว้ หากต้องการทราบว่าเหตุใดกระบวนการนี้จึงสำคัญ โปรดพิจารณาตัวอย่างต่อไปนี้

@container (width > 300px) {
  .card {
    color: blue;
  }
}

.card {
  color: red;
}

เนื่องจาก CSS นี้ องค์ประกอบที่มีคลาส .card ควรมี color: red เสมอ เนื่องจากกฎภายหลังจะลบล้างกฎก่อนหน้าด้วยตัวเลือกและความเฉพาะเจาะจงเดียวกันเสมอ ดังนั้น การแปลงกฎแรกและรวมตัวเลือกแอตทริบิวต์เพิ่มเติมโดยไม่มี :where(...) จะเพิ่มความเจาะจงและส่งผลให้มีการใช้ color: blue อย่างไม่ถูกต้อง

อย่างไรก็ตาม Pseudo-class ของ :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 หรือไม่ และระบบจะใช้ตัวเลือก Transpiled เพื่อจัดรูปแบบ

องค์ประกอบจำลอง

คำถามหนึ่งที่คุณอาจกำลังถามตัวเองคือ หาก Polyfill ตั้งค่าแอตทริบิวต์ cq-XYZ บางรายการในองค์ประกอบให้รวมรหัสคอนเทนเนอร์ที่ไม่ซ้ำกัน 123 จะรองรับองค์ประกอบจำลองที่ตั้งค่าแอตทริบิวต์ไม่ได้อย่างไร

องค์ประกอบ Pseudo จะเชื่อมโยงกับองค์ประกอบจริงใน 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 อย่างไม่ถูกต้อง

ในการแก้ไขปัญหานี้ Polyfill จะใช้แอตทริบิวต์ 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 ขององค์ประกอบที่ใช้หน่วย ไม่ใช่องค์ประกอบที่ค้นหา เพื่อรองรับความต้องการนี้ 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 ใหม่บางรายการ เช่น container-type และ container-name เนื่องจาก API อย่างเช่น getComputedStyle(...) ไม่สามารถใช้กับพร็อพเพอร์ตี้ที่ไม่รู้จักหรือไม่ถูกต้อง พร็อพเพอร์ตี้เหล่านี้จะเปลี่ยนรูปแบบเป็นพร็อพเพอร์ตี้ที่กำหนดเองของ 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ădurelesson ใน Unsplash