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

Gerald Monaco
Gerald Monaco

คำค้นหาคอนเทนเนอร์เป็นฟีเจอร์ใหม่ของ 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