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

Gerald Monaco
Gerald Monaco

คิวรีคอนเทนเนอร์คือฟีเจอร์ 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 โปรดแจ้งปัญหาหากพบปัญหา

เราอดใจรอที่จะได้เห็นและสัมผัสประสบการณ์การใช้งานที่ยอดเยี่ยมที่คุณสร้างขึ้นด้วยเครื่องมือนี้