วิธีใช้การค้นหาคอนเทนเนอร์เลย

เมื่อเร็วๆ นี้ Chris Coyier ได้เขียนบล็อกโพสต์ที่ตั้งคำถามไว้ว่า

ในตอนนี้ เครื่องมือเบราว์เซอร์ทั้งหมดรองรับการค้นหาคอนเทนเนอร์แล้ว เหตุใดนักพัฒนาซอฟต์แวร์จึงไม่ใช้คอนเทนเนอร์เหล่านี้

โพสต์ของคณิตแสดงสาเหตุที่เป็นไปได้หลายประการ (เช่น ขาดการรับรู้ นิสัยเดิมๆ ตายยาก) แต่มีเหตุผลหนึ่งข้อที่โดดเด่น

นักพัฒนาซอฟต์แวร์บางคนกล่าวว่าต้องการใช้การค้นหาคอนเทนเนอร์เลย แต่ก็คิดว่าทำไม่ได้เพราะยังต้องรองรับเบราว์เซอร์รุ่นเก่าอยู่

จากที่คุณอาจเดาได้จากชื่อแล้ว เราคิดว่านักพัฒนาส่วนใหญ่สามารถใช้คำค้นหาคอนเทนเนอร์ส่วนใหญ่ในการใช้งานจริง แม้จะต้องสนับสนุนเบราว์เซอร์รุ่นเก่าก็ตาม โพสต์นี้จะอธิบายวิธีการที่เราแนะนำ

แนวทางที่ปฏิบัติได้จริง

หากต้องการใช้การค้นหาคอนเทนเนอร์ในโค้ดตอนนี้ แต่ต้องการให้ประสบการณ์การใช้งานดูเหมือนกันในทุกเบราว์เซอร์ คุณสามารถใช้ทางเลือกสำรองที่ใช้ JavaScript สำหรับเบราว์เซอร์ที่ไม่รองรับการค้นหาคอนเทนเนอร์ได้

จากนั้นคำถามก็คือ วิดีโอสำรองควรมีความครอบคลุมมากน้อยเพียงใด

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

แต่ "ชุดฟังก์ชันหลัก" ที่นักพัฒนาซอฟต์แวร์ส่วนใหญ่ต้องการสำหรับการค้นหาคอนเทนเนอร์คืออะไร ในการตอบคำถามนี้ ให้พิจารณาวิธีที่นักพัฒนาซอฟต์แวร์ส่วนใหญ่สร้างเว็บไซต์ที่ปรับเปลี่ยนตามอุปกรณ์ซึ่งขณะนี้ใช้คำค้นหาสื่อ

ไลบรารีคอมโพเนนต์และระบบการออกแบบที่ทันสมัยส่วนใหญ่ได้มาตรฐานตามหลักการที่เน้นอุปกรณ์เคลื่อนที่เป็นหลัก ซึ่งนำไปใช้งานโดยใช้ชุดเบรกพอยท์ที่กำหนดไว้ล่วงหน้า (เช่น SM, MD, LG, XL) คอมโพเนนต์ต่างๆ ได้รับการเพิ่มประสิทธิภาพให้แสดงผลได้ดีในหน้าจอขนาดเล็กโดยค่าเริ่มต้น จากนั้นรูปแบบจะกำหนดเลเยอร์อย่างมีเงื่อนไขเพื่อรองรับชุดความกว้างของหน้าจอที่ใหญ่ขึ้นแบบคงที่ (ดูตัวอย่างเอกสาร Bootstrap และ Tailwind)

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

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

ส่วนถัดไปจะอธิบายวิธีการทำงานทั้งหมด พร้อมกับคำแนะนำทีละขั้นตอนที่จะแสดงวิธีการนำเทคโนโลยีนี้ไปใช้ในเว็บไซต์ที่มีอยู่แล้ว

วิธีการทำงาน

ขั้นตอนที่ 1: อัปเดตรูปแบบคอมโพเนนต์เพื่อใช้กฎ @container แทนกฎ @media ข้อ

ในขั้นตอนแรกนี้ ให้ระบุคอมโพเนนต์ในเว็บไซต์ที่คุณคิดว่าจะได้รับประโยชน์จากการปรับขนาดตามคอนเทนเนอร์แทนที่จะเป็นการปรับขนาดตามวิวพอร์ต

คุณควรเริ่มต้นด้วยคอมโพเนนต์เพียง 1-2 รายการเพื่อดูว่ากลยุทธ์นี้ทำงานอย่างไร แต่หากคุณต้องการแปลงคอมโพเนนต์ทั้ง 100% เป็นการจัดรูปแบบตามคอนเทนเนอร์ ก็ไม่มีปัญหาเช่นกัน ข้อดีของกลยุทธ์นี้คือคุณจะนำไปปรับใช้ทีละน้อยได้หากต้องการ

เมื่อระบุคอมโพเนนต์ที่ต้องการอัปเดตแล้ว คุณจะต้องเปลี่ยนกฎ @media ทุกกฎใน CSS ของคอมโพเนนต์เหล่านั้นเป็นกฎ @container คุณกำหนดเงื่อนไขของขนาดให้เหมือนเดิมได้

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

ต่อไปนี้คือตัวอย่างรูปแบบสำหรับคอมโพเนนต์ .photo-gallery ซึ่งโดยค่าเริ่มต้นจะเป็นคอลัมน์เดียว จากนั้นจะมีการอัปเดตรูปแบบกลายเป็น 2 และ 3 คอลัมน์ในเบรกพอยท์ MD และ XL (ตามลำดับ)

.photo-gallery {
  display: grid;
  grid-template-columns: 1fr;
}

/* Styles for the `MD` breakpoint */
@media (min-width: 768px) {
  .photo-gallery {
    grid-template-columns: 1fr 1fr;
  }
}

/* Styles for the `XL` breakpoint */
@media (min-width: 1280px) {
  .photo-gallery {
    grid-template-columns: 1fr 1fr 1fr;
  }
}

หากต้องการเปลี่ยนรูปแบบคอมโพเนนต์เหล่านี้จากการใช้กฎ @media เป็นการใช้กฎ @container ให้ค้นหาแล้วแทนที่ในโค้ด ดังนี้

/* Before: */
@media (min-width: 768px) { /* ... */ }
@media (min-width: 1280px) { /* ... */ }

/* After: */
@container (min-width: 768px) { /* ... */ }
@container (min-width: 1280px) { /* ... */ }

เมื่อคุณอัปเดตรูปแบบคอมโพเนนต์จากกฎ @media ข้อเป็นกฎ @container ที่อิงตามเบรกพอยท์แล้ว ขั้นตอนถัดไปคือการกำหนดค่าองค์ประกอบคอนเทนเนอร์

ขั้นตอนที่ 2: เพิ่มองค์ประกอบคอนเทนเนอร์ลงใน HTML

ขั้นตอนก่อนหน้านี้ได้กำหนดรูปแบบคอมโพเนนต์โดยอิงตามขนาดขององค์ประกอบคอนเทนเนอร์ ขั้นตอนถัดไปคือกำหนดว่าองค์ประกอบใดในหน้าเว็บควรเป็นองค์ประกอบคอนเทนเนอร์ที่ขนาดของกฎ @container จะสัมพันธ์กับ

คุณประกาศให้องค์ประกอบใดก็ตามเป็นองค์ประกอบคอนเทนเนอร์ใน CSS ได้โดยตั้งค่าพร็อพเพอร์ตี้ container-type ขององค์ประกอบเป็น size หรือ inline-size ถ้ากฎคอนเทนเนอร์เป็นแบบอิงตามความกว้าง โดยทั่วไป inline-size จะเป็นตัวเลือกที่คุณต้องการใช้

พิจารณาเว็บไซต์ที่มีโครงสร้าง HTML พื้นฐานต่อไปนี้

<body>
  <div class="sidebar">...</div>
  <div class="content">...</div>
</body>

หากต้องการทำให้องค์ประกอบ .sidebar และ .content บนcontainersของเว็บไซต์นี้ ให้เพิ่มกฎนี้ลงใน CSS ของคุณ ดังนี้

.content, .sidebar {
  container-type: inline-size;
}

สำหรับเบราว์เซอร์ที่รองรับการค้นหาคอนเทนเนอร์ CSS นี้จะช่วยคุณสร้างรูปแบบคอมโพเนนต์ที่กำหนดไว้ในขั้นตอนก่อนหน้าโดยสัมพันธ์กับพื้นที่เนื้อหาหลักหรือแถบด้านข้าง ทั้งนี้ขึ้นอยู่กับองค์ประกอบที่อยู่ภายใน

อย่างไรก็ตาม สำหรับเบราว์เซอร์ที่ไม่รองรับการค้นหาคอนเทนเนอร์ จะมีสิ่งที่ต้องทำเพิ่มเติม

คุณต้องเพิ่มโค้ดบางส่วนที่ตรวจจับเมื่อขนาดขององค์ประกอบของคอนเทนเนอร์เปลี่ยนแปลง แล้วอัปเดต DOM ตามการเปลี่ยนแปลงเหล่านั้นในลักษณะที่ CSS ของคุณเชื่อมโยงได้

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

โค้ดต่อไปนี้จะกำหนดองค์ประกอบ <responsive-container> ที่ใช้ซ้ำได้ ซึ่งจะรับข้อมูลการเปลี่ยนแปลงขนาดโดยอัตโนมัติและเพิ่มคลาสเบรกพอยท์ที่ CSS จัดรูปแบบได้โดยอิงตามสิ่งต่อไปนี้

// A mapping of default breakpoint class names and min-width sizes.
// Redefine these as needed based on your site's design.
const defaultBreakpoints = {SM: 512, MD: 768, LG: 1024, XL: 1280};

// A resize observer that monitors size changes to all <responsive-container>
// elements and calls their `updateBreakpoints()` method with the updated size.
const ro = new ResizeObserver((entries) => {
  entries.forEach((e) => e.target.updateBreakpoints(e.contentRect));
});

class ResponsiveContainer extends HTMLElement {
  connectedCallback() {
    const bps = this.getAttribute('breakpoints');
    this.breakpoints = bps ? JSON.parse(bps) : defaultBreakpoints;
    this.name = this.getAttribute('name') || '';
    ro.observe(this);
  }
  disconnectedCallback() {
    ro.unobserve(this);
  }
  updateBreakpoints(contentRect) {
    for (const bp of Object.keys(this.breakpoints)) {
      const minWidth = this.breakpoints[bp];
      const className = this.name ? `${this.name}-${bp}` : bp;
      this.classList.toggle(className, contentRect.width >= minWidth);
    }
  }
}

self.customElements.define('responsive-container', ResponsiveContainer);

โค้ดนี้ทำงานโดยสร้าง ResizeObserver ที่จะรับข้อมูลการเปลี่ยนแปลงขนาดขององค์ประกอบ <responsive-container> ใน DOM โดยอัตโนมัติ หากการเปลี่ยนแปลงขนาดตรงกับขนาดเบรกพอยท์ที่กำหนดไว้อย่างใดอย่างหนึ่ง คลาสที่มีชื่อเบรกพอยท์ดังกล่าวจะถูกเพิ่มลงในองค์ประกอบ (และลบออกหากเงื่อนไขไม่ตรงกันแล้ว)

ตัวอย่างเช่น หาก width ขององค์ประกอบ <responsive-container> อยู่ระหว่าง 768 ถึง 1024 พิกเซล (ตามค่าเบรกพอยท์เริ่มต้นที่กำหนดไว้ในโค้ด) ระบบจะเพิ่มคลาส SM และ MD ในลักษณะนี้

<responsive-container class="SM MD">...</responsive-container>

คลาสเหล่านี้ให้คุณกำหนดรูปแบบสำรองสำหรับเบราว์เซอร์ที่ไม่รองรับการค้นหาคอนเทนเนอร์ (ดูขั้นตอนที่ 3: เพิ่มรูปแบบสำรองลงใน CSS)

หากต้องการอัปเดตโค้ด HTML ก่อนหน้าเพื่อใช้องค์ประกอบคอนเทนเนอร์นี้ ให้เปลี่ยนแถบด้านข้างและเนื้อหาหลัก <div> เป็นองค์ประกอบ <responsive-container> แทน ดังนี้

<body>
  <responsive-container class="sidebar">...</responsive-container>
  <responsive-container class="content">...</responsive-container>
</body>

ในกรณีส่วนใหญ่ คุณสามารถใช้องค์ประกอบ <responsive-container> โดยไม่ต้องมีการปรับแต่งใดๆ แต่ถ้าต้องการปรับแต่ง คุณสามารถใช้ตัวเลือกต่อไปนี้ได้

  • ขนาดเบรกพอยท์ที่กำหนดเอง: โค้ดนี้ใช้ชุดชื่อคลาสเบรกพอยท์เริ่มต้นและขนาดความกว้างขั้นต่ำ แต่คุณเปลี่ยนค่าเริ่มต้นเหล่านี้ให้เป็นได้ตามต้องการ นอกจากนี้คุณยังลบล้างค่าเหล่านี้แบบทีละองค์ประกอบได้โดยใช้แอตทริบิวต์ breakpoints
  • คอนเทนเนอร์ที่มีชื่อ: โค้ดนี้ยังรองรับคอนเทนเนอร์ที่มีชื่อโดยการส่งแอตทริบิวต์ name ด้วย ซึ่งเป็นสิ่งสำคัญหากคุณต้องการซ้อนองค์ประกอบคอนเทนเนอร์ ดูรายละเอียดเพิ่มเติมในส่วนข้อจํากัด

นี่คือตัวอย่างที่ตั้งค่าตัวเลือกการกำหนดค่าทั้ง 2 รายการนี้

<responsive-container
  name='sidebar'
  breakpoints='{"bp1":500,"bp2":1000,"bp3":1500}'>
</responsive-container>

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

if (!CSS.supports('container-type: inline-size')) {
  import('./path/to/responsive-container.js');
}

ขั้นตอนที่ 3: เพิ่มรูปแบบสำรองลงใน CSS

ขั้นตอนสุดท้ายในกลยุทธ์นี้คือการเพิ่มรูปแบบสำรองสำหรับเบราว์เซอร์ที่ไม่รู้จักสไตล์ที่กำหนดไว้ในกฎ @container ซึ่งทำได้โดยการทำซ้ำกฎเหล่านั้นโดยใช้คลาสเบรกพอยท์ที่ตั้งค่าไว้ในองค์ประกอบ <responsive-container>

ต่อจากตัวอย่าง .photo-gallery ก่อนหน้านี้ รูปแบบสำรองสำหรับกฎ @container 2 ข้ออาจมีลักษณะดังนี้

/* Container query styles for the `MD` breakpoint. */
@container (min-width: 768px) {
  .photo-gallery {
    grid-template-columns: 1fr 1fr;
  }
}

/* Fallback styles for the `MD` breakpoint. */
@supports not (container-type: inline-size) {
  :where(responsive-container.MD) .photo-gallery {
    grid-template-columns: 1fr 1fr;
  }
}

/* Container query styles for the `XL` breakpoint. */
@container (min-width: 1280px) {
  .photo-gallery {
    grid-template-columns: 1fr 1fr 1fr;
  }
}

/* Fallback styles for the `XL` breakpoint. */
@supports not (container-type: inline-size) {
  :where(responsive-container.XL) .photo-gallery {
    grid-template-columns: 1fr 1fr 1fr;
  }
}

ในโค้ดนี้ สำหรับแต่ละกฎ @container จะมีกฎที่เทียบเท่าซึ่งจับคู่องค์ประกอบ <responsive-container> อย่างมีเงื่อนไขหากมีคลาสเบรกพอยท์ที่เกี่ยวข้องอยู่

ส่วนของตัวเลือกที่ตรงกับองค์ประกอบ <responsive-container> จะรวมไว้ในตัวเลือกคลาสเทียมที่มีฟังก์ชันการทำงาน :where() เพื่อรักษาความจำเพาะของตัวเลือกสำรองให้เทียบเท่ากับความจำเพาะของตัวเลือกเดิมภายในกฎ @container

กฎสำรองแต่ละข้อจะรวมอยู่ในการประกาศ @supports ด้วย แม้ว่าวิธีนี้จะไม่จำเป็นอย่างมากเพื่อให้ตัวเลือกสำรองทำงานได้ แต่หมายความว่าเบราว์เซอร์จะไม่สนใจกฎเหล่านี้โดยสิ้นเชิงหากกฎรองรับการค้นหาคอนเทนเนอร์ ซึ่งสามารถปรับปรุงประสิทธิภาพการจับคู่รูปแบบโดยทั่วไปได้ นอกจากนี้ยังอาจอนุญาตให้เครื่องมือสร้างหรือ CDN นำการประกาศเหล่านั้นออกหากทราบว่าเบราว์เซอร์รองรับการค้นหาคอนเทนเนอร์และไม่จำเป็นต้องใช้รูปแบบสำรองเหล่านั้น

ข้อเสียหลักของกลยุทธ์ทางเลือกนี้คือคุณต้องประกาศสไตล์ซ้ำ 2 ครั้ง ซึ่งเป็นเรื่องที่น่าเบื่อหน่ายและเกิดข้อผิดพลาดได้ง่าย อย่างไรก็ตาม หากคุณใช้ CSS Preprocessor คุณอาจนำโค้ดดังกล่าวมารวมเข้าด้วยกันซึ่งจะสร้างทั้งกฎ @container และโค้ดสำรองให้คุณ ตัวอย่างการใช้ Sass มีดังนี้

@use 'sass:map';

$breakpoints: (
  'SM': 512px,
  'MD': 576px,
  'LG': 1024px,
  'XL': 1280px,
);

@mixin breakpoint($breakpoint) {
  @container (min-width: #{map.get($breakpoints, $breakpoint)}) {
    @content();
  }
  @supports not (container-type: inline-size) {
    :where(responsive-container.#{$breakpoint}) & {
      @content();
    }
  }
}

แล้วเมื่อมีมิกซ์นี้ คุณก็อัปเดตสไตล์คอมโพเนนต์ .photo-gallery เดิมให้มีลักษณะดังนี้ ซึ่งจะกำจัดการทำซ้ำออกไปทั้งหมด

.photo-gallery {
  display: grid;
  grid-template-columns: 1fr;

  @include breakpoint('MD') {
    grid-template-columns: 1fr 1fr;
  }

  @include breakpoint('XL') {
    grid-template-columns: 1fr 1fr 1fr;
  }
}

เท่านี้ก็เรียบร้อย

สรุป

สรุปก็คือวิธีอัปเดตโค้ดเพื่อใช้การค้นหาคอนเทนเนอร์ในตอนนี้พร้อมด้วยโฆษณาสำรองข้ามเบราว์เซอร์มีดังนี้

  1. คอมโพเนนต์ข้อมูลประจำตัวที่คุณต้องการจัดรูปแบบโดยสัมพันธ์กับคอนเทนเนอร์ และอัปเดตกฎ @media ใน CSS เพื่อใช้กฎ @container นอกจากนี้ (หากยังไม่ได้ทำ) ให้กำหนดมาตรฐานสำหรับชุดของชื่อเบรกพอยท์เพื่อให้ตรงกับเงื่อนไขของขนาดในกฎของคอนเทนเนอร์
  2. เพิ่ม JavaScript ที่ขับเคลื่อนองค์ประกอบ <responsive-container> ที่กำหนดเอง จากนั้นเพิ่มองค์ประกอบ <responsive-container> ไปยังพื้นที่เนื้อหาใดก็ได้ในหน้าเว็บที่คุณต้องการให้คอมโพเนนต์มีความเกี่ยวข้อง
  3. หากต้องการรองรับเบราว์เซอร์รุ่นเก่า ให้เพิ่มรูปแบบสำรองลงใน CSS ที่ตรงกับคลาสเบรกพอยท์ที่ระบบเพิ่มลงในองค์ประกอบ <responsive-container> ใน HTML โดยอัตโนมัติ โดยหลักการแล้วควรใช้ CSS Preprocessor Mixin เพื่อหลีกเลี่ยงการเขียนรูปแบบเดียวกัน 2 ครั้ง

ข้อดีของกลยุทธ์นี้คือมีค่าใช้จ่ายในการตั้งค่าแบบครั้งเดียว แต่หลังจากนั้นก็ไม่ต้องลงแรงเพิ่มเติมในการเพิ่มคอมโพเนนต์ใหม่และกำหนดสไตล์ที่สัมพันธ์กับคอนเทนเนอร์สำหรับคอมโพเนนต์เหล่านั้น

ดูของจริง

วิธีที่ดีที่สุดที่จะทำความเข้าใจว่าขั้นตอนทั้งหมดนี้สอดคล้องกันได้อย่างไรคือการดูตัวอย่างการใช้งานจริง

วิดีโอที่ผู้ใช้โต้ตอบกับการค้นหาคอนเทนเนอร์เว็บไซต์สาธิต ผู้ใช้กำลังปรับขนาดพื้นที่เนื้อหาเพื่อแสดงว่ารูปแบบคอมโพเนนต์จะอัปเดตอย่างไรตามขนาดของพื้นที่ที่มีเนื้อหาของผู้ใช้

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

เนื่องจากเว็บไซต์นี้มีการกำหนดสไตล์สำหรับ "คอมโพเนนต์ที่ปรับเปลี่ยนตามอุปกรณ์" จำนวนมากอยู่แล้ว จึงเป็นตัวเลือกที่เหมาะที่จะทดสอบกลยุทธ์ที่นำมาใช้ในเว็บไซต์ที่ไม่สำคัญมากนัก ปรากฏว่าการอัปเดตนั้นค่อนข้างง่ายและแทบไม่ต้องเปลี่ยนแปลงรูปแบบเว็บไซต์เดิมเลย

คุณดูซอร์สโค้ดเดโมฉบับเต็มใน GitHub และอย่าลืมดู CSS ของคอมโพเนนต์สาธิตอย่างเฉพาะเจาะจงเพื่อดูการกำหนดรูปแบบสำรอง หากต้องการทดสอบเฉพาะลักษณะการทำงานสำรอง เรามีการสาธิตเฉพาะสำรองเท่านั้นที่รวมตัวแปรนั้นเท่านั้น แม้แต่ในเบราว์เซอร์ที่รองรับการค้นหาคอนเทนเนอร์

ข้อจำกัดและการปรับปรุงที่เป็นไปได้

อย่างที่กล่าวไว้ในตอนต้นของโพสต์นี้ กลยุทธ์ที่แสดงที่นี่ทำงานได้ดีสำหรับกรณีการใช้งานส่วนใหญ่ที่นักพัฒนาซอฟต์แวร์สนใจจริงๆ เมื่อเข้าถึงคำค้นหาคอนเทนเนอร์

อย่างไรก็ตาม มีกรณีการใช้งานขั้นสูงบางส่วนที่กลยุทธ์นี้จงใจไม่พยายามให้การสนับสนุน ซึ่งจะกล่าวถึงต่อไป

หน่วยการค้นหาคอนเทนเนอร์

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

อย่างไรก็ตาม หากคุณจำเป็นต้องใช้หน่วยการค้นหาคอนเทนเนอร์ คุณสามารถเพิ่มการสนับสนุนให้กับหน่วยดังกล่าวได้อย่างง่ายดายโดยใช้พร็อพเพอร์ตี้ที่กำหนดเอง โดยการกำหนดพร็อพเพอร์ตี้ที่กำหนดเองสำหรับแต่ละหน่วยโฆษณาที่ใช้ในองค์ประกอบคอนเทนเนอร์ ดังนี้

responsive-container {
  --cqw: 1cqw;
  --cqh: 1cqh;
}

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

.photo-gallery {
  font-size: calc(10 * var(--cqw));
}

จากนั้นเพื่อรองรับเบราว์เซอร์เวอร์ชันเก่า ให้ตั้งค่าสำหรับพร็อพเพอร์ตี้ที่กำหนดเองเหล่านั้นในองค์ประกอบคอนเทนเนอร์ภายใน Callback ResizeObserver

class ResponsiveContainer extends HTMLElement {
  // ...
  updateBreakpoints(contentRect) {
    this.style.setProperty('--cqw', `${contentRect.width / 100}px`);
    this.style.setProperty('--cqh', `${contentRect.height / 100}px`);

    // ...
  }
}

ซึ่งจะช่วยให้คุณ "ส่ง" ค่าเหล่านั้นจาก JavaScript ไปยัง CSS ได้อย่างมีประสิทธิภาพ และคุณจะควบคุม CSS ได้อย่างเต็มที่ (เช่น calc(), min(), max(), clamp()) เพื่อจัดการกับค่าเหล่านั้นตามต้องการ

การรองรับคุณสมบัติเชิงตรรกะและโหมดการเขียน

คุณอาจสังเกตเห็นการใช้ inline-size แทนที่จะเป็น width ในการประกาศ @container ในตัวอย่าง CSS เหล่านี้บางรายการ นอกจากนี้ คุณยังอาจสังเกตเห็นหน่วยโฆษณา cqi และ cqb ใหม่ (สำหรับขนาดในหน้าและขนาดบล็อกตามลำดับ) ฟีเจอร์ใหม่เหล่านี้สะท้อนถึงการเปลี่ยนแปลงของ CSS ไปสู่คุณสมบัติและค่าเชิงตรรกะ แทนที่จะเป็นฟีเจอร์ทางกายภาพหรือแบบชี้นำ

น่าเสียดายที่ API อย่าง Adjust Observer จะยังคงรายงานค่าใน width และ height ดังนั้น หากการออกแบบของคุณต้องการความยืดหยุ่นของคุณสมบัติเชิงตรรกะ คุณก็ต้องหาคำตอบเอง

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

ด้วยเหตุนี้ วิธีที่ดีที่สุดคือให้ตัวองค์ประกอบ <responsive-container> ยอมรับพร็อพเพอร์ตี้โหมดการเขียนที่เจ้าของเว็บไซต์จะตั้งค่า (และอัปเดต) ได้ตามต้องการ หากต้องการใช้งาน คุณต้องทำตามแนวทางเดียวกับที่แสดงในส่วนก่อนหน้า แล้วสลับ width และ height ตามความจำเป็น

คอนเทนเนอร์ที่ซ้อนกัน

พร็อพเพอร์ตี้ container-name ให้คุณตั้งชื่อคอนเทนเนอร์ซึ่งจะใช้อ้างอิงในกฎ @container ได้ คอนเทนเนอร์ที่มีชื่อจะมีประโยชน์หากคุณมีคอนเทนเนอร์ฝังอยู่ในคอนเทนเนอร์ และคุณต้องการกฎที่จะจับคู่คอนเทนเนอร์บางรายการเท่านั้น (ไม่ใช่แค่คอนเทนเนอร์ระดับบนที่ใกล้ที่สุด)

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

ตัวอย่างเช่น มีองค์ประกอบ <responsive-container> 2 รายการที่รวมคอมโพเนนต์ .photo-gallery แต่เนื่องจากคอนเทนเนอร์ด้านนอกมีขนาดใหญ่กว่าคอนเทนเนอร์ด้านใน จึงมีการเพิ่มคลาสเบรกพอยท์ที่แตกต่างกัน

<responsive-container class="SM MD LG">
  ...
  <responsive-container class="SM">
    ...
    <div class="photo-gallery">...</div class="photo-gallery">
  </responsive-container>
</responsive-container>

ในตัวอย่างนี้ คลาส MD และ LG ในคอนเทนเนอร์ด้านนอกจะมีผลต่อกฎของรูปแบบที่ตรงกับคอมโพเนนต์ .photo-gallery ซึ่งไม่ตรงกับการทำงานของคำค้นหาคอนเทนเนอร์ (เนื่องจากตรงกับคอนเทนเนอร์ระดับบนที่ใกล้ที่สุดเท่านั้น)

หากต้องการจัดการกับเรื่องนี้ ให้ทำอย่างใดอย่างหนึ่งต่อไปนี้

  1. โปรดตั้งชื่อคอนเทนเนอร์ที่จะซ้อนเสมอ และตรวจสอบว่าคลาสเบรกพอยท์มีคำนำหน้าด้วยชื่อคอนเทนเนอร์ดังกล่าวเพื่อหลีกเลี่ยงข้อขัดแย้ง
  2. ใช้ชุดค่าผสมย่อยแทนชุดค่าผสมองค์ประกอบสืบทอดในตัวเลือกสำรอง (ซึ่งมีข้อจำกัดมากกว่าเล็กน้อย)

ส่วนคอนเทนเนอร์ที่ซ้อนกันของเว็บไซต์สาธิตมีตัวอย่างของการทำงานนี้โดยใช้คอนเทนเนอร์ที่มีชื่อ รวมถึงส่วนผสมของ Sass ที่ใช้ในโค้ดเพื่อสร้างรูปแบบสำรองสำหรับกฎ @container ทั้งที่มีชื่อและไม่ได้ตั้งชื่อ

จะเกิดอะไรกับเบราว์เซอร์ที่ไม่รองรับ :where(), องค์ประกอบที่กำหนดเอง หรือตัวสังเกตการปรับขนาด

แม้ว่า API เหล่านี้อาจดูค่อนข้างใหม่ แต่ทั้งหมดมีการรองรับในทุกเบราว์เซอร์มานานกว่า 3 ปีแล้ว และทั้งหมดเป็นส่วนหนึ่งของเกณฑ์พื้นฐานที่มีให้ใช้งานในวงกว้าง

ดังนั้นหากคุณไม่มีข้อมูลที่แสดงให้เห็นว่าผู้เข้าชมเว็บไซต์ของคุณส่วนใหญ่ใช้เบราว์เซอร์ที่ไม่รองรับหนึ่งในฟีเจอร์เหล่านี้ คุณก็ต้องไม่ใช้อย่างอิสระโดยไม่มีวิดีโอสำรอง

อย่างไรก็ตาม สำหรับกรณีการใช้งานเฉพาะนี้ สิ่งที่แย่ที่สุดที่อาจเกิดขึ้นคือวิดีโอสำรองไม่ทำงานสำหรับผู้ใช้ส่วนน้อยมาก ซึ่งหมายความว่าผู้ใช้จะเห็นมุมมองเริ่มต้นแทนที่จะเป็นมุมมองที่เพิ่มประสิทธิภาพสำหรับขนาดคอนเทนเนอร์

ฟังก์ชันการทำงานของเว็บไซต์จะยังใช้งานได้อยู่และนั่นคือสิ่งสำคัญอย่างยิ่ง

ทำไมไม่ใช้ polyfill ของการค้นหาคอนเทนเนอร์

ฟีเจอร์ CSS เป็นที่กล่าวกันมากว่าใช้ Polyfill ได้ยาก และโดยทั่วไปแล้วจำเป็นต้องนำโปรแกรมแยกวิเคราะห์ CSS ทั้งหมดและตรรกะแบบ Cascade ทั้งหมดใน JavaScript มาปรับใช้ใหม่ ด้วยเหตุนี้ ผู้เขียน CSS Polyfill จึงต้องมีข้อจำกัดหลายอย่าง ซึ่งมักจะมาพร้อมกับข้อจำกัดมากมายในด้านฟีเจอร์ รวมถึงค่าใช้จ่ายในการดำเนินการที่สำคัญ

ด้วยเหตุนี้ โดยทั่วไปเราจึงไม่แนะนําให้ใช้ CSS Polyfill ในเวอร์ชันที่ใช้งานจริง รวมถึง container-query-polyfill จาก Google Chrome Labs ซึ่งไม่มีอีกต่อไปแล้ว (และมีจุดประสงค์เพื่อการสาธิตเป็นหลัก)

กลยุทธ์ทางเลือกที่กล่าวถึงในที่นี้มีข้อจำกัดน้อยกว่า ใช้โค้ดน้อยกว่ามาก และมีประสิทธิภาพสูงกว่า Polyfill ของคอนเทนเนอร์คำค้นหาใดๆ ที่เคยทำได้อย่างมีนัยสำคัญ

คุณจำเป็นต้องใช้ทางเลือกสำรองสำหรับเบราว์เซอร์รุ่นเก่าหรือไม่

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

จากข้อมูลของ caniuse.com ผู้ใช้อินเทอร์เน็ตทั่วโลกสนับสนุนการค้นหาคอนเทนเนอร์ถึง 90% และสำหรับผู้คนจำนวนมากที่อ่านโพสต์นี้ จำนวนผู้ใช้มีแนวโน้มที่จะค่อนข้างสูงขึ้น ดังนั้นโปรดทราบว่าผู้ใช้ส่วนใหญ่จะเห็น UI เวอร์ชันคอนเทนเนอร์คำค้นหา และสำหรับผู้ใช้ 10% ที่จะไม่ดำเนินการ ก็ไม่น่าจะได้รับประสบการณ์การใช้งานที่ไม่ดี เมื่อทำตามกลยุทธ์นี้ ในกรณีที่แย่ที่สุด ผู้ใช้จะเห็นเลย์เอาต์เริ่มต้นหรือเลย์เอาต์ "อุปกรณ์เคลื่อนที่" สำหรับคอมโพเนนต์บางอย่าง ซึ่งไม่ใช่จุดสิ้นสุดของโลก

เมื่อต้องหาข้อดีข้อเสีย ควรเพิ่มประสิทธิภาพเพื่อผู้ใช้ส่วนใหญ่ของคุณแทนที่จะกำหนดค่าเริ่มต้นให้ใช้แนวทางแบบตัวหารที่ต่ำที่สุด ซึ่งจะทำให้ผู้ใช้ทุกคนได้รับประสบการณ์ที่สอดคล้องกันแต่ต่ำกว่ามาตรฐาน

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

เส้นทางต่อจากนี้

หวังว่าโพสต์นี้จะทำให้คุณเชื่อว่าคุณสามารถใช้การค้นหาคอนเทนเนอร์ในการใช้งานจริงได้ในตอนนี้ และคุณไม่จำเป็นต้องรอเป็นปีจนกว่าเบราว์เซอร์ที่ไม่สนับสนุนทั้งหมดจะหายไป

แม้ว่ากลยุทธ์ที่ระบุไว้ที่นี่จะต้องอาศัยการทำงานเพิ่มเติมเล็กน้อย แต่ก็ควรจะเรียบง่ายและตรงไปตรงมามากพอที่คนส่วนใหญ่สามารถนำไปใช้บนเว็บไซต์ได้ แต่ทั้งนี้ ก็ยังมีช่องทางที่คุณสามารถนำมาใช้งานได้ง่ายยิ่งขึ้น แนวคิดหนึ่งคือการรวมส่วนต่างๆ จำนวนมากเข้าด้วยกันเป็นคอมโพเนนต์เดียว เพื่อเพิ่มประสิทธิภาพสำหรับเฟรมเวิร์กหรือสแต็กที่เฉพาะเจาะจง ซึ่งช่วยจัดการงานกาวทั้งหมดให้คุณ หากคุณสร้างงานลักษณะนี้ โปรดแจ้งให้เราทราบแล้วเราจะช่วยโปรโมตได้

สุดท้ายนี้ นอกเหนือจากการค้นหาคอนเทนเนอร์แล้ว ยังมีฟีเจอร์ CSS และ UI ที่น่าทึ่งมากมายที่ตอนนี้ทำงานร่วมกันได้แล้วในเครื่องมือเบราว์เซอร์หลักๆ ทั้งหมด ในฐานะชุมชน ลองพิจารณาดูว่าเราจะใช้ฟีเจอร์เหล่านั้นได้อย่างไรบ้าง เพื่อให้ผู้ใช้ได้รับประโยชน์