CSS Containment คืออะไร รู้จักกับการใช้ contain เพื่อเพิ่มประสิทธิภาพการแสดงผลเว็บ
เราต่างทราบกันดีว่ากว่าเว็บเบราว์เซอร์จะแสดงผลข้อมูลจากโค้ดในไฟล์สู่หน้าจอได้นั้นต้องฟันฝ่ากระบวนท่าหลายท่วงทำนอง ได้แก่
- Parse: กระบวนการแปลงโค้ดจากไฟล์ HTML สู่ออบเจ็กต์ที่เบราว์เซอร์เข้าใจได้พร้อมจัดเก็บในหน่วยความจำ ขั้นตอนนี้หละที่ DOM ได้ถือกำเนิดขึ้นเพื่อเป็นตัวแทนของอีลีเมนต์ต่าง ๆ บนหน้าเพจ
- Style: แม้ DOM จะเกิดขึ้นแล้วแต่ถ้าแปลง DOM เพื่อไปเฉิดฉายบนหน้าจอโดยไม่จับแต่งหน้าซะก่อนมันก็คงศพเดินได้ดี ๆ นี่เอง เหตุนี้เราจึงต้องขอความร่วมมือจากเอนจินของ CSS เพื่อนำกฎต่าง ๆ ที่ต้องการมาคำนวณผลพร้อมแปะไว้ให้กับแต่ละโหนดใน DOM
- Layout: เอาหละตอนนี้โหนดต่าง ๆ ใน DOM ก็ดูสวยด้วยมีดหมอ CSS แล้ว แต่ถ้าจะให้ไปแสดงผลบนหน้าจอก็ต้องทราบตำแหน่งที่จะแสดงผลก่อนรวมถึงต้องทราบด้วยว่าโหนดนั้น ๆ อ้วนผอมอย่างไรเพื่อให้จองพื้นที่บนจอได้อย่างถูกต้อง เราเรียกขั้นตอนนี้ว่าการจัดเลย์เอาต์นั่นเอง
- Paint Composite และ Render: กล่องที่ถูกจัดเลย์เอาต์ในขั้นตอนก่อนหน้าจะถูกนำมาวาดเป็นหลาย ๆ เลเยอร์เพื่อเตรียมพร้อมแสดงผล จากนั้นจึงนำภาพที่ซ้อนเป็นเลเยอร์มาผนวกรวมกับพร็อพเพอร์ตี้ของ CSS ที่ใช้สำหรับจัดองค์ประกอบ เช่น transforms แล้วจึงฉายภาพของทุก ๆ เลเยอร์ออกมาเป็นภาพสุดท้ายบนจอ
แม้ขั้นตอนของการ Parse นั้นจะกระทำครั้งเดียวเมื่ออ่านซอร์จโค้ดเพื่อแปลงเป็น DOM แต่ขั้นตอนการจัด style, layout และ paint นั้น เกิดขึ้นซ้ำไปซ้ำมาหลายครั้ง อีลีเมนต์แต่ละส่วนบนหน้าจอนั้นไม่เป็นอิสระต่อกันเสียทีเดียว เมื่ออีลีเมนต์หนึ่งมีการเปลี่ยนแปลงย่อมส่งผลให้ อีลีเมนต์อื่นบนหน้าจอเกิดการคำนวณการแสดงผลใหม่ด้วยเช่นกัน
พิจารณาการแสดงผลกล่องที่บรรจุตัวเลข 10,000 กล่องต่อไปนี้
กำหนดให้การแสดงผลดังกล่าวเกิดจากส่วน 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 }1011 .item {12 height: 20px;13 padding: 10px;14 background-color: #666;15 word-break: break-all;16 }17</style>1819<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 จึงยังผลให้ส่วนอื่น ๆ ไม่ต้องคำนวณใหม่
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
เช่น
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 ตัวท้ายสุด ดังต่อไปนี้
1<!-- ส่วนของ CSS -->2<style>3 .container {4 border: 1px solid black;5 padding: 1rem;6 }78 .content:last-child {9 position: fixed;10 top: 0;11 }12</style>1314<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 ให้เป็น Layout Containment จะเป็นการการันตีว่าอีลีเมนต์ต่าง ๆ ใน container
จะต้องไม่กระทบเลย์เอาต์ภายนอก ดังนั้นการย้ายการแสดงผล My Content 2
ไปไว้บนสุดของเพจซึ่งถือว่าเป็นการไปยุ่งเกี่ยวกับเลย์เอาต์นอก อีลีเมนต์ภายใต้ containment boxes จะกระทำไม่ได้
ผลลัพธ์จากการระบุ contain: layout
จึงทำให้บลอคของ container
สร้าง Formatting Context ขึ้นมาใหม่เป็นผลให้ My Content 2
แสดงผลอยู่บนสุดของ container แทนที่จะออกไปนอก container แทนนั่นเอง
1.container {2 contain: layout;3 border: 1px solid black;4 padding: 1rem;5}
Vertical Alignment
Layout Containment จะทำให้ vertical-align: baseline
ไม่สามารถทำงานได้อย่างถูกต้อง เหตุเพราะเมื่อ containment boxes
เป็นอิสระจากเลย์เอาต์รอบข้างมันจึงไม่สามารถถูกใช้เป็นตัวเทียบของ baseline ได้
1<style>2 .content {3 vertical-align: baseline;4 }56 .containment {7 border: 1px solid black;8 padding: 10px;9 display: inline-block;10 }11</style>1213<div class="container">14 <span class="content">My Content 1</span>15 <span class="containment">16 My Content<br />17 218 </span>19</div>
กรณีไม่ระบุ contain: layout
ผลลัพธ์ยังคงจัดตำแหน่งตามแนวตั้งด้วย baseline ได้อยู่
แต่เมื่อระบุ contain: layout
ให้กับ .containment
จะพบว่า vertical-align: baseline
ได้ผลลัพธ์ไม่เหมือนเช่นเดิม
Stacking Context
Layout Containment นั้นยังทำให้เกิดการสร้าง Stacking Context ใหม่ด้วยเช่นกัน เราจึงสามารถใช้ z-index
บนอีลีเมนต์นี้ได้
Ink Overflow
สำหรับค่าของ overflow แบบ visible
และ clip
จะถือว่าเป็น Ink Overflow
Ink Overflow คือส่วนการแสดงผลของอีลีเมนต์ที่แสดงผลนอกกล่องของอีลีเมนต์ โดยการวาดส่วนแสดงผลดังกล่าวจะไม่กระทบเลย์เอาต์อื่น เช่น การใช้ box shadows และ text decoration เป็นต้น
พิจารณาโค้ดต่อไปนี้
1<style>2 .container {3 overflow: auto;4 }56 .article {7 overflow: visible;8 height: 50px;9 border: 1px solid black;10 padding: 1rem;11 background-color: darkgray;12 }13</style>1415<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 จึงมีส่วนช่วยเพิ่มประสิทธิภาพโดยอีลีเมนต์แม่ไม่ต้องคอยกังวลใจว่าจะแสดงผล
สกอร์บาร์หรือไม่เมื่อเนื้อหาภายในของอีลีเมนต์ลูกมีการเปลี่ยนแปลง
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 แล้วจะทำให้ตัวกล่องมีความสูงเป็นศูนย์
1<style>2 .content {3 contain: size;4 border: 1px solid red;5 }6</style>78<div class="content">9 My Content10</div>
เมื่อทำการระบุค่า height จึงเป็นการกำหนดความสูงให้กับ containment box เว็บเบราว์เซอร์จะสามารถทราบขนาดของกล่องได้ทันที โดยไม่ต้องคำนวณจากขนาดของเนื้อหาด้านในอีกทีนึง
1<style>2 .content {3 contain: size;4 border: 1px solid red;5 height: 20px;6 }7</style>89<div class="content">10 My Content11</div>
ไม่ใช่ทุกอีลีเมนต์ที่จะเป็น 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
เนื้อหาที่เกินความสูงที่กำหนดจะยังคงแสดงผลล้นกรอบออกมา
1<style>2 .article {3 height: 50px;4 border: 1px solid black;5 padding: 1rem;6 background-color: darkgray;7 }8</style>910<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 the15 1500s,16 </p>17</article>
แต่เมื่อทำการระบุ contain: paint
ให้กับคลาส .article
ข้อความที่เกินกว่าความสูงของ containment box
(ในที่นี้คือแท็ก article) จะไม่แสดงผล
Style Containment
Style Containment เป็นเครื่องการันตีว่าพร็อพเพอร์ตี้ใด ๆ ที่กำหนดค่าแล้วมีตัวนับการแสดงผลเพื่อ ส่งผลเปลี่ยนแปลงทั้งอีลีเมนต์ของมันเองรวมถึงอีลีเมนต์อื่นที่ครอบมันอยู่ เช่น การใช้ CSS Counters และ Quotes พร็อพเพอร์ตี้เหล่านั้นจะต้องจำกัดผลการเปลี่ยนแปลงไว้ภายใต้ containment boxes เท่านั้น
จากโค้ดต่อไปนี้ผลลัพธ์ที่ได้จะแสดงแท็ก ol และ li โดยมีหมายเลขกำกับตามลำดับชั้น
1<style>2 ol {3 counter-reset: n;4 list-style-type: none;5 }67 li::before {8 counter-increment: n;9 content: counters(n, '.') '. ';10 }11</style>1213<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>
เมื่อทำการเพิ่ม contain: style
สำหรับคลาส container ดังนี้
1.container {2 contain: style;3}
จะพบว่าลำดับตัวเลขด้านใน containment boxes จะไม่ถูกใช้ในการคำนวณค่าถัดไป
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
ตัวอย่างการใช้งาน
ย้อนกลับมาที่ปัญหาที่เราตั้งไว้ต้นบทความกันครับ
กำหนดให้ผลลัพธ์จากภาพข้างต้นแสดงออกมาได้โดยอาศัยโค้ดดังต่อไปนี้
1<script>2 function buildItems() {3 const container = document.getElementById('container')45 for (let i = 1; i <= 10_000; i++) {6 const item = document.createElement('div')7 const text = document.createTextNode(Math.random().toFixed(2))89 item.classList.add('item')10 item.appendChild(text)11 container.appendChild(item)12 }13 }1415 function onLoad(event) {16 buildItems()17 }1819 document.addEventListener('DOMContentLoaded', onLoad)20</script>2122<style>23 body {24 margin: 0;25 }2627 #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 }3435 .item {36 height: 20px;37 padding: 10px;38 background-color: #666;39 word-break: break-all;40 }41</style>4243<div id="container"></div>
โค้ดของเรานั้นใช้ JavaScript เพื่อช่วยสร้างอีลีเมนต์ผ่านแท็ก div ที่มีคลาสเป็น item ภายใต้อีลีเมนต์ containet ทั้งหมด 10,000 อีลีเมนต์ ดังนั้นผลลัพธ์จากการทำงานจะเสมือนกำหนด 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 ในกล่องแรกเปลี่ยนค่า
1function reassignNumber() {2 const [firstItem] = document.getElementsByClassName('item')34 firstItem.textContent = Math.random().toFixed(2)5}67function onLoad(event) {8 buildItems()9 setInterval(() => reassignNumber(), 2000)10}
เมื่อทำการวัดประสิทธิภาพการทำงานพบว่าใช้เวลาคำนวณเลย์เอาต์ไปกว่า 700 มิลลิวินาที
ในฐานะนักพัฒนาเราย่อมทราบด้วยตัวของเราเองอยู่แล้วว่าขอบเขตขนาดของตัวเลขคือเท่าไร
เราจึงกำหนดความสูงตายตัวให้กับแต่ละกล่อง item ของเราในที่นี้คือ 20px เมื่อขนาดถูกกำหนดแล้วเราจึงสามารถใช้
Layout Containment เพื่อแจ้งให้เว็บเบราว์เซอร์ทราบว่าอีลีเมนต์ภายในจะไม่สร้างความเปลี่ยนแปลงกับเลย์เอาต์ภายนอก
(เช่น position: fixed
จะไม่ย้ายอีลีเมนต์ไปไว้ที่ส่วนอื่นนอกขอบเขตของ item) และขนาดของ item ตายตัวอยู่แล้ว
เมื่อเนื้อหาภายในกล่องเปลี่ยนไปจะไม่ดันกล่องอื่นรอบข้างให้เปลี่ยนขนาดไปด้วยจึงไม่ต้องคำนวณเลย์เอาต์ของกล่องอื่นใหม่ ตรงนี้กำหนดได้ผ่าน
Size Contentment ด้วยเหตุนี้เราจึงควรระบุ contain: layout size
หรือจะใช้ contain: strict
(รวม paint containment ด้วย)
ให้กับ item ได้
1.item {2 contain: layout size;3 height: 20px;4 padding: 10px;5 background-color: #666;6 word-break: break-all;7}
ทำการวัดการจัดเลย์เอาต์อีกครั้งจะพบว่าเมื่อตัวเลขของกล่องแรกเปลี่ยนเว็บเบราว์เซอร์จะคำนวณเฉพาะเลย์เอาต์ของ item แรก จึงทำให้ใช้เวลาคำนวณเลย์เอาต์ลดลงเหลือราว 16 มิลลิวินาที!
Browser Support
CSS Containment สามารถใช้งานได้ในเว็บเบราว์เซอร์หลักทั่วไปยกเว้นเบราว์เซอร์เต่าตนุอย่าง Safari
กรณีของการใช้ contain: style
ให้ระมัดระวังเป็นพิเศษเนื่องจากฟีเจอร์นี้ถูกระบุเป็น "at-risk"
ในเอกสารของ W3C
เอกสารอ้างอิง
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
- เอกสารอ้างอิง