CSS Containment คืออะไร รู้จักกับการใช้ contain เพื่อเพิ่มประสิทธิภาพการแสดงผลเว็บ

Nuttavut Thongjor

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

  • Parse: กระบวนการแปลงโค้ดจากไฟล์ HTML สู่ออบเจ็กต์ที่เบราว์เซอร์เข้าใจได้พร้อมจัดเก็บในหน่วยความจำ ขั้นตอนนี้หละที่ DOM ได้ถือกำเนิดขึ้นเพื่อเป็นตัวแทนของอีลีเมนต์ต่าง ๆ บนหน้าเพจ
  • Style: แม้ DOM จะเกิดขึ้นแล้วแต่ถ้าแปลง DOM เพื่อไปเฉิดฉายบนหน้าจอโดยไม่จับแต่งหน้าซะก่อนมันก็คงศพเดินได้ดี ๆ นี่เอง เหตุนี้เราจึงต้องขอความร่วมมือจากเอนจินของ CSS เพื่อนำกฎต่าง ๆ ที่ต้องการมาคำนวณผลพร้อมแปะไว้ให้กับแต่ละโหนดใน DOM
  • Layout: เอาหละตอนนี้โหนดต่าง ๆ ใน DOM ก็ดูสวยด้วยมีดหมอ CSS แล้ว แต่ถ้าจะให้ไปแสดงผลบนหน้าจอก็ต้องทราบตำแหน่งที่จะแสดงผลก่อนรวมถึงต้องทราบด้วยว่าโหนดนั้น ๆ อ้วนผอมอย่างไรเพื่อให้จองพื้นที่บนจอได้อย่างถูกต้อง เราเรียกขั้นตอนนี้ว่าการจัดเลย์เอาต์นั่นเอง
  • Paint Composite และ Render: กล่องที่ถูกจัดเลย์เอาต์ในขั้นตอนก่อนหน้าจะถูกนำมาวาดเป็นหลาย ๆ เลเยอร์เพื่อเตรียมพร้อมแสดงผล จากนั้นจึงนำภาพที่ซ้อนเป็นเลเยอร์มาผนวกรวมกับพร็อพเพอร์ตี้ของ CSS ที่ใช้สำหรับจัดองค์ประกอบ เช่น transforms แล้วจึงฉายภาพของทุก ๆ เลเยอร์ออกมาเป็นภาพสุดท้ายบนจอ

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

พิจารณาการแสดงผลกล่องที่บรรจุตัวเลข 10,000 กล่องต่อไปนี้

Recalculate Layout

กำหนดให้การแสดงผลดังกล่าวเกิดจากส่วน HTML ที่มีซอร์จโค้ดคือ

HTML
1<!-- ส่วนของ CSS -->
2<style>
3 #container {
4 display: grid;
5 grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
6 gap: 10px;
7 padding: 10px;
8 background-color: black;
9 }
10
11 .item {
12 height: 20px;
13 padding: 10px;
14 background-color: #666;
15 word-break: break-all;
16 }
17</style>
18
19<div id="container">
20 <div class="item">0.02</div>
21 <!-- อีลีเมนต์ของคลาส item มีทั้งหมด 10,000 ตัว -->
22</div>

เมื่อพิจารณาจากโค้ดข้างต้น จะเกิดอะไรขึ้นบ้างเมื่อ item ตัวแรกเปลี่ยนการแสดงผลจาก 0.02 เป็นเลขอื่น?

เราอาจคิดว่าเมื่อเฉพาะ item แรกที่เปลี่ยนค่าเว็บเบราว์เซอร์ก็น่าจะคำนวณการจัด style, layout และ paint ใหม่เฉพาะ item แรก ส่วนกล่องที่เหลืออีก 9,999 ชิ้นนั้นควรจะอยู่เฉย ๆ โดยเบราว์เซอร์ไม่ต้องจัดการอะไรเพิ่มเติม

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

ภาพแสดงการขยายขนาดอีลีเมนต์ข้างเคียง

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

โครงสร้าง DOM นั้นถูกจัดเก็บในหน่วยความจำด้วยโครงสร้างข้อมูลแบบต้นไม้ (Tree) เมื่อสิ่งหนึ่งเปลี่ยนแปลงโครงสร้างส่วนอื่นของต้นไม้จะถูกคำนวณใหม่ หากเราต้องการหลีกเลี่ยงการคำนวณที่ไม่จำเป็น เราต้องแยกส่วนนั้นออกเป็นโครงสร้างย่อย (Subtree) ที่เป็นอิสระจากส่วนอื่นของโครงสร้างหลัก เมื่อส่วนนี้ถูกแยกออกและเป็นอิสระ การเปลี่ยนแปลงใด ๆ ที่เกิดขึ้นใน subtree จึงยังผลให้ส่วนอื่น ๆ ไม่ต้องคำนวณใหม่

Subtree

CSS contain property

พร็อพเพอร์ตี้ contain ของ CSS เป็นส่วนหนึ่งของมาตรฐานที่เรียกว่า CSS Containment พร็อพเพอร์ตี้ดังกล่าวเป็นสื่อกลางให้นักพัฒนา แจ้งเว็บเบราว์เซอร์ทราบว่าส่วนที่กำหนดรวมถึงเนื้อหาด้านในของมันเป็นอิสระจากส่วนอื่น การเปลี่ยนแปลงใด ๆ ที่เกิดขึ้นจะมีขอบเขตอยู่ภายใต้ subtree ของมันเท่านั้น การระบุเช่นนี้จะช่วยให้เว็บเบราว์เซอร์สามารถเพิ่มประสิทธิภาพการทำงานของมันได้ (ขึ้นอยู่กับค่าของ contain ที่ระบุ) เช่น หลีกเลี่ยงการคำนวณเลย์เอาต์ของส่วนอื่นเมื่อส่วนที่เป็นอิสระนี้เปลี่ยนแปลงเนื้อหาภายในอีลีเมนต์ของมันเอง ค่าที่สามารถระบุให้กับ contain ได้คือ none, strict, content, size (หรือ inline-size หรือ block-size), layout, style และ paint

อีลีเมนต์ใด ๆ ที่ถูกระบุค่าของ contain ในบทความนี้จะเรียกอีลีเมนต์นั้นว่า Containment Box

Layout Containment

การจัดเลย์เอาต์นั้นเป็นการคำนวณใหม่ทั้ง document นั่นหมายความว่าเวลาที่ใช้กับการคำนวณย่อมแปรผันกับขนาดของ DOM การใช้ตัวช่วยที่ระบุให้ส่วนของอีลีเมนต์ที่เราสนใจเป็นอิสระ โดยการเปลี่ยนแปลงใด ๆ ในอีลีเมนต์จะไม่กระทบเลย์เอาต์ด้านนอก รวมถึงการเปลี่ยนแปลงของเลย์เอาต์ด้านนอกไม่กระทบเลย์เอาต์ของ subtree คือสิ่งที่สามารถกระทำได้ด้วย Layout Containment ผ่านการระบุ contain: layout เช่น

CSS
1.item {
2 contain: layout;
3}

Layout Containment นั้นเป็นตัวช่วยเพิ่มประสิทธิภาพได้อย่างดีเยี่ยม เว็บเบราว์เซอร์สามารถปรับปรุงประสิทธิภาพได้โดย

  • เมื่ออีลีเมนต์ดังกล่าวเป็นอิสระจากส่วนที่เหลือ เว็บเบราว์เซอร์จึงสามารถจัดเลย์เอาต์ของอีลีเมนต์ย่อยภายใต้ containment boxes ไปพร้อมกับการจัดเลย์เอาต์ส่วนอื่น ๆ ได้แบบขนาน
  • เว็บเบราว์เซอร์อาจดีเลย์การแสดงผลหรือจัดลำดับความสำคัญอย่างต่ำให้กับการแสดงผล containment boxes เมื่ออีลีเมนต์เหล่านั้นไม่ได้แสดงผลบนหน้าจอขณะนั้นและส่วนของอีลีเมนต์อื่นที่กำลังแสดงผลอยู่ไม่ได้อิงกับขนาดของ containment boxes เช่น containment boxes อยู่ท้ายหน้าจอและผู้ใช้งานกำลังดูส่วนอื่นที่อยู่เหนือกล่อง containment boxes นั้น ดังนั้นแล้วส่วนที่กำลังแสดงผลอยู่จึงไม่ได้คำนวณตำแหน่งโดยอิงจาก containment boxes ด้วยเหตุนี้ containment boxes จึงสามารถถูกดีเลย์การแสดงผลออกไปได้โดยไม่กระทบการแสดงผลปัจจุบัน (ดูเพิ่มเติมในบทความของการใช้งาน content-visibility)

Layout Containment เป็นการการันตีว่า containment boxes และเนื้อหาด้านในเป็นอิสระจากรอบข้าง พฤติกรรมการแสดงผลบางอย่างจึง ต้องเปลี่ยนแปลงไปด้วยเพื่อให้ได้ผลของการทำงานที่ถูกต้อง

Independent Formatting Context

Layout Containment ทำให้เกิดการสร้าง Formatting Context ใหม่ ดังนั้นการใช้งาน position: absolute และ position: fixed จึงไม่มีทางย้ายอีลีเมนต์ไปแสดงผลนอกขอบเขตของ containment boxes

Block Formating Context คือพื้นที่แสดงผลที่กำหนดขอบเขตให้อีลีเมนต์ใด ๆ ก็ตามที่สร้างขึ้นมาภายใต้ตัวมันจะต้องถูกแสดงผลได้อย่างครอบคลุมภายใต้พื้นที่ของมันเอง

พิจารณาโค้ดของอีลีเมนต์ที่มีคลาสเป็น container และอีลีเมนต์ที่มีคลาสเป็น content ตัวท้ายสุด ดังต่อไปนี้

HTML
1<!-- ส่วนของ CSS -->
2<style>
3 .container {
4 border: 1px solid black;
5 padding: 1rem;
6 }
7
8 .content:last-child {
9 position: fixed;
10 top: 0;
11 }
12</style>
13
14<p>Lorem Ipsum is simply dummy text of ...</p>
15<div class="container">
16 <div class="content">My Content 1</div>
17 <div class="content">My Content 2</div>
18</div>

การแสดงผลจากการทำงานดังกล่าวเป็นการย้ายอีลีเมนต์ My Content 2 ไปไว้ด้านบนสุดของเพจ

Container Without Layout Containment

เมื่อกำหนด container ให้เป็น Layout Containment จะเป็นการการันตีว่าอีลีเมนต์ต่าง ๆ ใน container จะต้องไม่กระทบเลย์เอาต์ภายนอก ดังนั้นการย้ายการแสดงผล My Content 2 ไปไว้บนสุดของเพจซึ่งถือว่าเป็นการไปยุ่งเกี่ยวกับเลย์เอาต์นอก อีลีเมนต์ภายใต้ containment boxes จะกระทำไม่ได้ ผลลัพธ์จากการระบุ contain: layout จึงทำให้บลอคของ container สร้าง Formatting Context ขึ้นมาใหม่เป็นผลให้ My Content 2 แสดงผลอยู่บนสุดของ container แทนที่จะออกไปนอก container แทนนั่นเอง

CSS
1.container {
2 contain: layout;
3 border: 1px solid black;
4 padding: 1rem;
5}

Container With Layout Containment

Vertical Alignment

Layout Containment จะทำให้ vertical-align: baseline ไม่สามารถทำงานได้อย่างถูกต้อง เหตุเพราะเมื่อ containment boxes เป็นอิสระจากเลย์เอาต์รอบข้างมันจึงไม่สามารถถูกใช้เป็นตัวเทียบของ baseline ได้

HTML
1<style>
2 .content {
3 vertical-align: baseline;
4 }
5
6 .containment {
7 border: 1px solid black;
8 padding: 10px;
9 display: inline-block;
10 }
11</style>
12
13<div class="container">
14 <span class="content">My Content 1</span>
15 <span class="containment">
16 My Content<br />
17 2
18 </span>
19</div>

กรณีไม่ระบุ contain: layout ผลลัพธ์ยังคงจัดตำแหน่งตามแนวตั้งด้วย baseline ได้อยู่

Vertical Alignment Without Layout Containment

แต่เมื่อระบุ contain: layout ให้กับ .containment จะพบว่า vertical-align: baseline ได้ผลลัพธ์ไม่เหมือนเช่นเดิม

Vertical Alignment  With Layout Containment

Stacking Context

Layout Containment นั้นยังทำให้เกิดการสร้าง Stacking Context ใหม่ด้วยเช่นกัน เราจึงสามารถใช้ z-index บนอีลีเมนต์นี้ได้

Ink Overflow

สำหรับค่าของ overflow แบบ visible และ clip จะถือว่าเป็น Ink Overflow

Ink Overflow คือส่วนการแสดงผลของอีลีเมนต์ที่แสดงผลนอกกล่องของอีลีเมนต์ โดยการวาดส่วนแสดงผลดังกล่าวจะไม่กระทบเลย์เอาต์อื่น เช่น การใช้ box shadows และ text decoration เป็นต้น

พิจารณาโค้ดต่อไปนี้

HTML
1<style>
2 .container {
3 overflow: auto;
4 }
5
6 .article {
7 overflow: visible;
8 height: 50px;
9 border: 1px solid black;
10 padding: 1rem;
11 background-color: darkgray;
12 }
13</style>
14
15<div class="container">
16 <article class="article">
17 <h2>Lorem Ipsum</h2>
18 <p>
19 Lorem Ipsum is simply dummy text of the printing and typesetting industry.
20 </p>
21 </article>
22</div>

เนื่องจากส่วนการแสดงผลของ article นั้นสูงแค่ 50px จึงเกิด overflow ตกทอดมาถึงอีลีเมนต์ตัวนอกคือ container เหตุเพราะ container อนุญาตให้แสดงสกอล์บาร์ผ่าน overflow: auto เราจึงเห็นการแสดงแถบสกอร์บาร์โผล่ขึ้นมา

หากเราระบุ contain: layout เข้าไปในคลาส article เนื่องจากคลาส article มีการระบุ overflow: auto ไว้ การแสดงผลส่วนเกินนี้จึงเป็นแบบ Ink Overflow ที่ไม่กระทบกับเลย์เอาต์ส่วนอื่น นั่นทำให้การแสดงผลของอีลีเมนต์ตัวนอก (container) ไม่ได้รับผลกระทบไปด้วย container เองจึงแสดงผลแค่ความสูง 50px ของ article พร้อมทั้งละการแสดงผลส่วนเกินจาก Ink Overflow โดยไม่มีการแสดงแถบสกอร์บาร์แต่อย่างใด Layout Containment จึงมีส่วนช่วยเพิ่มประสิทธิภาพโดยอีลีเมนต์แม่ไม่ต้องคอยกังวลใจว่าจะแสดงผล สกอร์บาร์หรือไม่เมื่อเนื้อหาภายในของอีลีเมนต์ลูกมีการเปลี่ยนแปลง

Ink Overflow

Size Containment

Layout Containment นั้นเป็นการประกาศเพื่อบอกเบราว์เซอร์ว่า containment boxes ของเรานั้นเป็นอิสระจากส่วนอื่นในหน้าเว็บ อีลีเมนต์ภายใน box จะไม่ถูกย้ายไปแสดงผลที่ส่วนอื่นหรือทำให้เลย์เอาต์ส่วนอื่นนอก containment boxes เสียไป

ทว่าเมื่อเนื้อหาภายใน containment boxes เปลี่ยนแปลง เช่นเปลี่ยนข้อความจาก hello เป็นคำว่า lorem Ipsum อีลีเมนต์ส่วนอื่นบนหน้าเพจอาจต้องถูกคำนวณเพื่อจัดตำแหน่งการแสดงผลใหม่อยู่ดีเพราะ lorem ipsum นั้นกินพื้นที่มากขึ้นจนอาจเบียนหรือเปลี่ยนวิถีการแสดงผลของอีลีเมนต์อื่นข้างเคียง

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

Size Containment กระทำได้ด้วยการระบุ contain: size เป็นการแจ้งเว็บเบราว์เซอร์ว่าเราในฐานะนักพัฒนาทราบดีว่า อีลีเมนต์ดังกล่าวที่เป็น containment boxes นั้นมีขนาดตายตัวโดยไม่สนใจขนาดของอีลีเมนต์ข้างใน containment boxes หรือพูดง่าย ๆ ก็คือ การใช้ Size Containment เสมือนการพิจารณาให้เนื้อหาด้านในมีขนาดเป็นศูนย์

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

HTML
1<style>
2 .content {
3 contain: size;
4 border: 1px solid red;
5 }
6</style>
7
8<div class="content">
9 My Content
10</div>

Size Containment Without Height

เมื่อทำการระบุค่า height จึงเป็นการกำหนดความสูงให้กับ containment box เว็บเบราว์เซอร์จะสามารถทราบขนาดของกล่องได้ทันที โดยไม่ต้องคำนวณจากขนาดของเนื้อหาด้านในอีกทีนึง

HTML
1<style>
2 .content {
3 contain: size;
4 border: 1px solid red;
5 height: 20px;
6 }
7</style>
8
9<div class="content">
10 My Content
11</div>

Size Containment With Height

ไม่ใช่ทุกอีลีเมนต์ที่จะเป็น containment box แบบ Size Containment ได้ ตัวอย่างของอีลีเมนต์ที่การตั้งค่า contain: size จะไม่ทำงาน เช่น อีลีเมนต์ที่มี display: none และ แท็ก span เป็นต้น

นอกเหนือจาก contain: size แล้วเรายังมี inline-size และ block-size เราจะกล่าวถึงการใช้งานสองค่านี้อีกครั้ง ในบทความของการใช้งาน Container Queries

Paint Containment

Paint Containment นั้นจะทำให้ containment boxes มีคุณสมบัติเป็น formatting context พร้อมทั้งสร้าง stacking context เช่นเดียวกับการใช้งาน Layout Containment

Paint Containment เป็นเครื่องแสดงให้เว็บเบราว์เซอร์ทราบว่าอีลีเมนต์ใด ๆ ก็ตามที่อยู่ภายใต้ containment boxes จะไม่ถูกแสดงผลนอกพื้นที่ของ containment boxes หรือพูดง่าย ๆ คือส่วนที่เกินขอบเขตจะถูกตัดการแสดงผลออกไป

การที่หมึกจากอีลีเมนต์ข้างใน containment boxes จะไม่มีทางเลอะออกมาข้างนอกนี้เองจะเป็นตัวช่วยรองรับว่า หากอีลีเมนต์แม่ (containment boxes) พ้นขอบเขตการแสดงผลเช่นถูกเลื่อนออกพ้นหน้าจอ อีลีเมนต์ข้างในก็จะไม่มีทางถูกแสดงผลด้วยอย่างแน่นอน

พิจารณาโค้ดแสดงผลต่อไปนี้ เมื่อไม่กำหนด contain: paint เนื้อหาที่เกินความสูงที่กำหนดจะยังคงแสดงผลล้นกรอบออกมา

HTML
1<style>
2 .article {
3 height: 50px;
4 border: 1px solid black;
5 padding: 1rem;
6 background-color: darkgray;
7 }
8</style>
9
10<article class="article">
11 <h2>Lorem Ipsum</h2>
12 <p>
13 Lorem Ipsum is simply dummy text of the printing and typesetting industry.
14 Lorem Ipsum has been the industry's standard dummy text ever since the
15 1500s,
16 </p>
17</article>

Without Paint Containment

แต่เมื่อทำการระบุ contain: paint ให้กับคลาส .article ข้อความที่เกินกว่าความสูงของ containment box (ในที่นี้คือแท็ก article) จะไม่แสดงผล

WithPaint Containment

Style Containment

Style Containment เป็นเครื่องการันตีว่าพร็อพเพอร์ตี้ใด ๆ ที่กำหนดค่าแล้วมีตัวนับการแสดงผลเพื่อ ส่งผลเปลี่ยนแปลงทั้งอีลีเมนต์ของมันเองรวมถึงอีลีเมนต์อื่นที่ครอบมันอยู่ เช่น การใช้ CSS Counters และ Quotes พร็อพเพอร์ตี้เหล่านั้นจะต้องจำกัดผลการเปลี่ยนแปลงไว้ภายใต้ containment boxes เท่านั้น

จากโค้ดต่อไปนี้ผลลัพธ์ที่ได้จะแสดงแท็ก ol และ li โดยมีหมายเลขกำกับตามลำดับชั้น

HTML
1<style>
2 ol {
3 counter-reset: n;
4 list-style-type: none;
5 }
6
7 li::before {
8 counter-increment: n;
9 content: counters(n, '.') '. ';
10 }
11</style>
12
13<ol>
14 <li>Before:</li>
15 <li class="container">
16 <ol>
17 <li>Inside:</li>
18 </ol>
19 </li>
20 <li>After:</li>
21</ol>

Without Style Containment

เมื่อทำการเพิ่ม contain: style สำหรับคลาส container ดังนี้

CSS
1.container {
2 contain: style;
3}

จะพบว่าลำดับตัวเลขด้านใน containment boxes จะไม่ถูกใช้ในการคำนวณค่าถัดไป

With Style Containment

counter-increment นั้นจะถูกกำหนดขอบเขตอยู่ภายใต้ containment boxes การเรียกใช้มันครั้งแรกภายใต้ subtree ของ containment boxes จะเสมือนการตั้งค่า counter ให้กลับเป็นศูนย์ไม่ว่า counter ตัวนั้นจะถูกเรียกใช้หรือคำนวณมาจากอีลีเมนต์ด้านนอก มาแล้วกี่ครั้งก็ตาม การเพิ่มหรือลดค่าของ counter ภายใต้ subtree ของ containment boxes ดังกล่าวจะไม่ส่งผลกระทบต่อ counter ภายนอกที่ใช้ชื่อเดียวกัน ทว่าฟังก์ชัน counter() และ counters() ของพร็อพเพอร์ตี้ content ยังคงสามารถเข้าถึง counter ที่ถูกสร้างภายนอก subtree ได้

นอกเหนือจาก CSS Counters แล้ว ค่าของพร็อพเพอร์ตี้ content ได้แก่ open-quote, close-quote, no-open-quote และ no-close-quote จะมีขอบเขตการทำงานอยู่แค่ภายใต้ subtree ของ containment boxes ด้วยเช่นกัน

Strict และ Content Containment

ในกรณีที่ต้องการกำหนดค่าของ contain หลายค่าพร้อมกัน เรามีคำสั่งสั้นคือ content และ strict ไว้ใช้งาน

กรณีของการใช้ contain: strict มีค่าเท่ากับการกำหนด contain: layout size paint ดังนั้นการใช้งาน Strict Containment จึงเหมาะสมเมื่อต้องการเพิ่มประสิทธิภาพโดยรู้ขนาดที่แน่นอนของอีลีเมนต์ที่เป็น containment boxes

อีกคำสั่งหนึ่งคือ Content Containment ที่มีรูปแบบการเรียกใช้คือ contain: content ใช้ในความหมายเดียวกับ contain: layout paint เมื่อ Content Containment ไม่ประกอบด้วย Size Containment คำสั่งนี้จึงเหมาะสมกว่า ในกรณีที่ไม่ทราบขนาดที่แน่นอนของอีลีเมนต์ที่เป็น containment boxes

ตัวอย่างการใช้งาน

ย้อนกลับมาที่ปัญหาที่เราตั้งไว้ต้นบทความกันครับ

Recalculate Layout

กำหนดให้ผลลัพธ์จากภาพข้างต้นแสดงออกมาได้โดยอาศัยโค้ดดังต่อไปนี้

HTML
1<script>
2 function buildItems() {
3 const container = document.getElementById('container')
4
5 for (let i = 1; i <= 10_000; i++) {
6 const item = document.createElement('div')
7 const text = document.createTextNode(Math.random().toFixed(2))
8
9 item.classList.add('item')
10 item.appendChild(text)
11 container.appendChild(item)
12 }
13 }
14
15 function onLoad(event) {
16 buildItems()
17 }
18
19 document.addEventListener('DOMContentLoaded', onLoad)
20</script>
21
22<style>
23 body {
24 margin: 0;
25 }
26
27 #container {
28 display: grid;
29 grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
30 gap: 10px;
31 padding: 10px;
32 background-color: black;
33 }
34
35 .item {
36 height: 20px;
37 padding: 10px;
38 background-color: #666;
39 word-break: break-all;
40 }
41</style>
42
43<div id="container"></div>

โค้ดของเรานั้นใช้ JavaScript เพื่อช่วยสร้างอีลีเมนต์ผ่านแท็ก div ที่มีคลาสเป็น item ภายใต้อีลีเมนต์ containet ทั้งหมด 10,000 อีลีเมนต์ ดังนั้นผลลัพธ์จากการทำงานจะเสมือนกำหนด HTML ก้อนนี้

HTML
1<div id="container">
2 <!-- มีทั้งหมด 10,000 อีลีเมนต์ -->
3 <div class="item"><!-- ตัวเลขสุ่ม --></div>
4</div>

item ทั้งหมดนั้นแสดงภายใต้ Grid Container ถ้า Grid Item ตัวใดตัวหนึ่งมีเนื้อหาภายในยาวมากขึ้นจนล้นบรรทัด Grid Item ตัวอื่น ๆ ในแถวเดียวกันก็จะต้องมีความยาวมากขึ้น เมื่อความยาวแถวมากขึ้นแถวถัดไปก็จะต้องถูกขยับลงไปอีก

ภาพแสดงการขยายขนาดอีลีเมนต์ข้างเคียง

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

ทำการเพิ่มโค้ดเพื่อทดสอบเวลาการคำนวณเลย์เอาต์เมื่อ item ในกล่องแรกเปลี่ยนค่า

JavaScript
1function reassignNumber() {
2 const [firstItem] = document.getElementsByClassName('item')
3
4 firstItem.textContent = Math.random().toFixed(2)
5}
6
7function onLoad(event) {
8 buildItems()
9 setInterval(() => reassignNumber(), 2000)
10}

เมื่อทำการวัดประสิทธิภาพการทำงานพบว่าใช้เวลาคำนวณเลย์เอาต์ไปกว่า 700 มิลลิวินาที

Render Without Containment

ในฐานะนักพัฒนาเราย่อมทราบด้วยตัวของเราเองอยู่แล้วว่าขอบเขตขนาดของตัวเลขคือเท่าไร เราจึงกำหนดความสูงตายตัวให้กับแต่ละกล่อง item ของเราในที่นี้คือ 20px เมื่อขนาดถูกกำหนดแล้วเราจึงสามารถใช้ Layout Containment เพื่อแจ้งให้เว็บเบราว์เซอร์ทราบว่าอีลีเมนต์ภายในจะไม่สร้างความเปลี่ยนแปลงกับเลย์เอาต์ภายนอก (เช่น position: fixed จะไม่ย้ายอีลีเมนต์ไปไว้ที่ส่วนอื่นนอกขอบเขตของ item) และขนาดของ item ตายตัวอยู่แล้ว เมื่อเนื้อหาภายในกล่องเปลี่ยนไปจะไม่ดันกล่องอื่นรอบข้างให้เปลี่ยนขนาดไปด้วยจึงไม่ต้องคำนวณเลย์เอาต์ของกล่องอื่นใหม่ ตรงนี้กำหนดได้ผ่าน Size Contentment ด้วยเหตุนี้เราจึงควรระบุ contain: layout size หรือจะใช้ contain: strict (รวม paint containment ด้วย) ให้กับ item ได้

CSS
1.item {
2 contain: layout size;
3 height: 20px;
4 padding: 10px;
5 background-color: #666;
6 word-break: break-all;
7}

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

Render With Containment

Browser Support

CSS Containment สามารถใช้งานได้ในเว็บเบราว์เซอร์หลักทั่วไปยกเว้นเบราว์เซอร์เต่าตนุอย่าง Safari กรณีของการใช้ contain: style ให้ระมัดระวังเป็นพิเศษเนื่องจากฟีเจอร์นี้ถูกระบุเป็น "at-risk" ในเอกสารของ W3C

ฺBrowser Support

เอกสารอ้างอิง

CSS Containment Module Level 2. Retrieved Jul, 12, 2021, from https://www.w3.org/TR/css-contain-2

CSS Containment Module Level 3. Retrieved Jul, 12, 2021, from https://drafts.csswg.org/css-contain-3

CSS Containment in Chrome 52. Retrieved Jul, 12, 2021, from https://developers.google.com/web/updates/2016/06/css-containment

Containment. Retrieved Jul, 12, 2021, from https://css.oddbird.net/rwd/query/contain/

CSS Containment. Retrieved Jul, 12, 2021, from https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Containment

Improving Website Performance with CSS Containment by Manuel Rego | CSSconf EU 2019. Retrieved Jul, 12, 2021, from https://www.youtube.com/watch?v=iqcO-5_KkJ4

สารบัญ

สารบัญ

  • CSS contain property
  • Layout Containment
  • Size Containment
  • Paint Containment
  • Style Containment
  • Strict และ Content Containment
  • ตัวอย่างการใช้งาน
  • Browser Support
  • เอกสารอ้างอิง