คุณสมบัติที่กำหนดเอง CSS ใน Cascade

เผยแพร่แล้ว: 2022-03-10
สรุปโดยย่อ ↬ ในบทความนี้ มิเรียมจะเจาะลึกลงไปในข้อกำหนด 'คุณสมบัติที่กำหนดเอง CSS สำหรับตัวแปรแบบเรียงซ้อน' เพื่อถามว่า "เหตุใดจึงเรียกว่าคุณสมบัติแบบกำหนดเอง พวกเขาทำงานอย่างไรในน้ำตก และเราจะทำอะไรกับพวกเขาได้อีก ?” ข้ามคำอุปมา "ตัวแปร" คุณสมบัติที่กำหนดเองสามารถให้วิธีใหม่ในการสร้างสมดุลของบริบทและการแยกในรูปแบบ CSS และส่วนประกอบ

เมื่อเดือนที่แล้ว ฉันได้สนทนาบน Twitter เกี่ยวกับความแตกต่างระหว่างสไตล์ "กำหนดขอบเขต" (สร้างขึ้นในกระบวนการสร้าง) และสไตล์ "ซ้อน" ดั้งเดิมของ CSS ฉันถามว่าทำไมโดยปกติแล้วนักพัฒนาจึงหลีกเลี่ยงความเฉพาะเจาะจงของตัวเลือก ID ในขณะที่ใช้ "รูปแบบที่กำหนดขอบเขต" ที่สร้างโดย JavaScript Keith Grant เสนอว่าความแตกต่างอยู่ที่การสร้างสมดุลระหว่างน้ำตก* และการสืบทอด กล่าวคือ ให้ความสำคัญกับความใกล้ชิดมากกว่าความจำเพาะ ลองมาดูกัน

น้ำตก

CSS cascade ขึ้นอยู่กับปัจจัยสามประการ:

  1. ความสำคัญที่ กำหนดโดยแฟล็ก !important และรูปแบบต้นทาง (ผู้ใช้ > ผู้แต่ง > เบราว์เซอร์)
  2. ความ จำเพาะ ของตัวเลือกที่ใช้ (inline > ID > class > element)
  3. ลำดับต้นทาง ของรหัสเอง (ล่าสุดมีความสำคัญกว่า)

ไม่มีการกล่าวถึง ความใกล้เคียง — ความสัมพันธ์ DOM-tree ระหว่างส่วนต่างๆ ของตัวเลือก ย่อหน้าด้านล่างจะเป็นสีแดงทั้งคู่ แม้ว่า #inner p จะอธิบายความสัมพันธ์ที่ใกล้ชิดกว่า #outer p สำหรับย่อหน้าที่สอง:

See the Pen [Cascade: Specificity vs Proximity](https://codepen.io/smashingmag/pen/OexweJ/) โดย Miriam Suzanne

ดู Pen Cascade: Specificity vs Proximity โดย Miriam Suzanne
 <section> <p>This text is red</p> <div> <p>This text is also red!</p> </div> </section>
 #inner p { color: green; } #outer p { color: red; }

ตัวเลือกทั้งสองมีความเฉพาะเจาะจงเหมือนกัน พวกเขาทั้งสองอธิบายองค์ประกอบ p เดียวกัน และไม่ถูกตั้งค่าสถานะเป็น !important ดังนั้นผลลัพธ์จะขึ้นอยู่กับลำดับแหล่งที่มาเพียงอย่างเดียว

เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

BEM และรูปแบบที่กำหนดขอบเขต

หลักการตั้งชื่ออย่าง BEM ("Block__Element—Modifier") ถูกใช้เพื่อให้แน่ใจว่าแต่ละย่อหน้า "กำหนดขอบเขต" ไว้สำหรับผู้ปกครองเพียงคนเดียวเท่านั้น เพื่อหลีกเลี่ยงน้ำตกทั้งหมด ย่อหน้า "องค์ประกอบ" ได้รับคลาสเฉพาะสำหรับบริบท "บล็อก" ของพวกเขา:

ดูปากกา [BEM Selectors & Proximity](https://codepen.io/smashingmag/pen/qzPyeM/) โดย Miriam Suzanne

ดู Pen BEM Selectors & Proximity โดย Miriam Suzanne
 <section class="outer"> <p class="outer__p">This text is red</p> <div class="inner"> <p class="inner__p">This text is green!</p> </div> </section>
 .inner__p { color: green; } .outer__p { color: red; }

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

ดูปากกา [Scoped Style Proximity](https://codepen.io/smashingmag/pen/NZaLWN/) โดย Miriam Suzanne

ดู Pen Scoped Style Proximity โดย Miriam Suzanne
 <section outer-scope> <p outer-scope>This text is red</p> <div outer-scope inner-scope> <p inner-scope>This text is green!</p> </div> </section>
 p[inner-scope] { color: green } p[outer-scope] { color: red; }

มรดก

ความใกล้ชิดไม่ได้เป็นส่วนหนึ่งของน้ำตก แต่เป็นส่วนหนึ่งของ CSS นั่นคือสิ่งที่ มรดก กลายเป็นสิ่งสำคัญ ถ้าเราปล่อย p ออกจากตัวเลือกของเรา แต่ละย่อหน้าจะได้รับสีจากบรรพบุรุษที่ใกล้ที่สุด:

See the Pen [มรดก: ความจำเพาะ vs ความใกล้ชิด](https://codepen.io/smashingmag/pen/mZBGyN/) โดย Miriam Suzanne

ดูมรดกปากกา: ความจำเพาะเทียบกับความใกล้ชิด โดย Miriam Suzanne
 #inner { color: green; } #outer { color: red; }

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

แต่มีปัญหาอยู่: ในการใช้การสืบทอด เรากำลังกำหนดสไตล์ ทุกอย่าง ภายใน section และ div ของเรา เราต้องการกำหนดเป้าหมายสีของย่อหน้าโดยเฉพาะ

(Re-) แนะนำคุณสมบัติที่กำหนดเอง

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

ดูปากกา [อุปกรณ์ประกอบฉากที่กำหนดเอง: ความจำเพาะเทียบกับความใกล้เคียง](https://codepen.io/smashingmag/pen/gNGdaO/) โดย Miriam Suzanne

ดูอุปกรณ์ประกอบฉากที่กำหนดเองของปากกา: ความจำเพาะเทียบกับความใกล้เคียง โดย Miriam Suzanne
 p { color: var(--paragraph); } #inner { --paragraph: green; } #outer { --paragraph: red; }

คุณสมบัติ --paragraph ที่กำหนดเองนั้นสืบทอดมาเหมือนกับคุณสมบัติ color แต่ตอนนี้เราควบคุมได้แล้วว่าค่านั้นจะถูกนำไปใช้อย่างไรและที่ไหน คุณสมบัติ --paragraph ทำหน้าที่คล้ายกับพารามิเตอร์ที่สามารถส่งผ่านไปยังองค์ประกอบ p ไม่ว่าจะผ่านการเลือกโดยตรง (specificity-rules) หรือบริบท (proximity-rules)

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

กำหนดเอง "ฟังก์ชัน" และพารามิเตอร์

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

 html { --stripes: linear-gradient( to right, powderblue 20%, pink 20% 40%, white 40% 60%, pink 60% 80%, powderblue 80% ); }

ตัวแปรนั้นถูกกำหนดไว้ในองค์ประกอบ root html (สามารถใช้ :root ได้ แต่จะเพิ่มความจำเพาะที่ไม่จำเป็น) ดังนั้นตัวแปรแบบสไทรพ์ของเราจะสามารถใช้ได้ทุกที่ในเอกสาร เราสามารถใช้ได้ทุกที่ที่รองรับการไล่ระดับสี:

ดูปากกา [อุปกรณ์ประกอบฉากที่กำหนดเอง: ตัวแปร](https://codepen.io/smashingmag/pen/NZwrrm/) โดย Miriam Suzanne

ดูอุปกรณ์ประกอบฉากที่กำหนดเองของปากกา: ตัวแปรโดย Miriam Suzanne
 body { background-image: var(--stripes); }

การเพิ่มพารามิเตอร์

มีการใช้ฟังก์ชันเหมือนตัวแปร แต่กำหนดพารามิเตอร์สำหรับเปลี่ยนเอาต์พุต เราสามารถอัปเดตตัวแปร --stripes ให้มีลักษณะเหมือนฟังก์ชันมากขึ้นโดยกำหนดตัวแปรที่เหมือนพารามิเตอร์บางตัวอยู่ข้างใน ฉันจะเริ่มต้นด้วยการแทนที่ to right ด้วย var(--stripes-angle) เพื่อสร้างพารามิเตอร์เปลี่ยนมุม:

 html { --stripes: linear-gradient( var(--stripes-angle), powderblue 20%, pink 20% 40%, white 40% 60%, pink 60% 80%, powderblue 80% ); }

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

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

ในการใช้ฟังก์ชันข้างต้น เราจำเป็นต้องส่งค่าสำหรับพารามิเตอร์ --stripes-angle และใช้เอาต์พุตกับคุณสมบัติเอาต์พุต CSS เช่น background-image :

 /* in addition to the code above… */ html { --stripes-angle: 75deg; background-image: var(--stripes); } 

ดูปากกา [อุปกรณ์ประกอบฉากที่กำหนดเอง: ฟังก์ชั่น](https://codepen.io/smashingmag/pen/BgwOjj/) โดย Miriam Suzanne

ดู Pen Custom Props: Function โดย Miriam Suzanne

สืบทอดกับสากล

ฉันกำหนด --stripes ฟังก์ชั่นบนองค์ประกอบ html ออกจากนิสัย คุณสมบัติที่กำหนดเองสืบทอดมา และฉันต้องการให้ฟังก์ชันของฉันพร้อมใช้งานทุกที่ ดังนั้นจึงควรใส่ไว้ในองค์ประกอบรูท ทำงานได้ดีสำหรับการสืบทอดตัวแปรเช่น --brand-color: blue ดังนั้นเราอาจคาดหวังให้ตัวแปรนี้ทำงานได้ดีสำหรับ "ฟังก์ชัน" ของเราเช่นกัน แต่ถ้าเราพยายามใช้ฟังก์ชันนี้อีกครั้งในตัวเลือกที่ซ้อนกัน มันจะไม่ทำงาน:

ดูปากกา [อุปกรณ์ประกอบฉากที่กำหนดเอง: การสืบทอดฟังก์ชันล้มเหลว](https://codepen.io/smashingmag/pen/RzjRrM/) โดย Miriam Suzanne

ดู Pen Custom Props: Function Inheritance Fail โดย Miriam Suzanne
 div { --stripes-angle: 90deg; background-image: var(--stripes); }

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

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

ดูปากกา [อุปกรณ์ประกอบฉากที่กำหนดเอง: ฟังก์ชันสากล](https://codepen.io/smashingmag/pen/agLaNj/) โดย Miriam Suzanne

ดู Pen Custom Props: Universal Function โดย Miriam Suzanne
 * { --stripes: linear-gradient( var(--stripes-angle), powderblue 20%, pink 20% 40%, white 40% 60%, pink 60% 80%, powderblue 80% ); }

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

 /* make the function available to elements with a given selector */ .stripes { --stripes: /* etc… */; } /* make the function available to elements nested inside a given selector */ .stripes * { --stripes: /* etc… */; } /* make the function available to siblings following a given selector */ .stripes ~ * { --stripes: /* etc… */; } 

ดูปากกา [อุปกรณ์ประกอบฉากที่กำหนดเอง: ฟังก์ชั่นกำหนดขอบเขต](https://codepen.io/smashingmag/pen/JQMvGM/) โดย Miriam Suzanne

ดู Pen Custom Props: Scoped Function โดย Miriam Suzanne

สามารถขยายได้ด้วยตรรกะของตัวเลือกใดๆ ที่ไม่ต้องอาศัยการสืบทอด

พารามิเตอร์ฟรีและค่าทางเลือก

ในตัวอย่างด้านบน var(--stripes-angle) ไม่มีค่าและไม่มีทางเลือก แตกต่างจากตัวแปร Sass หรือ JS ที่ต้องกำหนดหรือสร้างอินสแตนซ์ก่อนที่จะเรียก คุณสมบัติที่กำหนดเองของ CSS สามารถเรียกได้โดยไม่ต้องกำหนด สิ่งนี้จะสร้างตัวแปร "อิสระ" คล้ายกับพารามิเตอร์ของฟังก์ชันที่สืบทอดมาจากบริบทได้

ในที่สุด เราสามารถกำหนดตัวแปรบน html หรือ :root (หรือบรรพบุรุษอื่น ๆ ) เพื่อตั้งค่าที่สืบทอดมา แต่ก่อนอื่น เราต้องพิจารณาทางเลือกอื่นหากไม่มีการกำหนดค่า มีหลายทางเลือก ขึ้นอยู่กับพฤติกรรมที่เราต้องการ

  1. สำหรับพารามิเตอร์ "จำเป็น" เราไม่ต้องการทางเลือกอื่น ตามที่เป็นอยู่ ฟังก์ชันนี้จะไม่ทำอะไรเลยจนกว่าจะกำหนด --stripes-angle
  2. สำหรับพารามิเตอร์ "ทางเลือก" เราสามารถระบุค่าทางเลือกในฟังก์ชัน var() หลังจากชื่อตัวแปร เราเพิ่มเครื่องหมายจุลภาค ตามด้วยค่าเริ่มต้น:
 var(--stripes-angle, 90deg)

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

 html { /* Computed: Hevetica, Ariel, sans-serif */ font-family: var(--sans-family, Hevetica, Ariel, sans-serif); /* Computed: 0 -1px 0 white, 0 1px 0 black */ test-shadow: var(--shadow, 0 -1px 0 white, 0 1px 0 black); }

นอกจากนี้เรายังสามารถใช้ตัวแปรที่ซ้อนกันเพื่อสร้างกฎคาสเคดของเราเอง โดยให้ลำดับความสำคัญที่แตกต่างกันกับค่าต่างๆ:

 var(--stripes-angle, var(--global-default-angle, 90deg))
  1. ขั้นแรก ลองใช้พารามิเตอร์ที่ชัดเจนของเรา ( --stripes-angle );
  2. ย้อนกลับไปสู่ ​​"ค่าเริ่มต้นของผู้ใช้" ทั่วโลก ( --user-default-angle ) หากมีให้บริการ
  3. สุดท้าย ใช้ทางเลือกแทน "ค่าเริ่มต้นจากโรงงาน" (90deg )

See the Pen [อุปกรณ์ประกอบฉากที่กำหนดเอง: ค่าทางเลือก](https://codepen.io/smashingmag/pen/jjGvVm/) โดย Miriam Suzanne

ดูอุปกรณ์ประกอบฉากที่กำหนดเองของปากกา: ค่าทางเลือกโดย Miriam Suzanne

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

ทางเลือกของเบราว์เซอร์เทียบกับทางเลือกที่เปลี่ยนแปลงได้

เมื่อเราใช้ตัวแปร มีสองเส้นทางสำรองที่เราต้องจำไว้:

  1. เบราว์เซอร์ที่ไม่มีการรองรับตัวแปรควรใช้ค่าใด
  2. เบราว์เซอร์ที่รองรับตัวแปรควรใช้ค่าใด เมื่อตัวแปรบางตัวขาดหายไปหรือไม่ถูกต้อง
 p { color: blue; color: var(--paragraph); }

แม้ว่าเบราว์เซอร์รุ่นเก่าจะไม่สนใจคุณสมบัติการประกาศตัวแปร และตัวเลือกสำรองเป็น blue — เบราว์เซอร์รุ่นใหม่จะอ่านทั้งสองอย่างและใช้อันหลัง var(--paragraph) ของเราอาจไม่ได้กำหนดไว้ แต่ถูกต้องและจะแทนที่คุณสมบัติก่อนหน้า ดังนั้นเบราว์เซอร์ที่มีการสนับสนุนตัวแปรจะเป็นทางเลือกแทนค่าที่สืบทอดหรือค่าเริ่มต้น ราวกับว่าใช้คีย์เวิร์ดที่ไม่ได้ unset

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

  1. ในเวลาแยกวิเคราะห์ การประกาศที่มีไวยากรณ์ที่ไม่ถูกต้องจะถูกละเว้นโดยสมบูรณ์ — กลับไปใช้การประกาศก่อนหน้านี้ นี่คือเส้นทางที่เบราว์เซอร์เก่าจะปฏิบัติตาม เบราว์เซอร์สมัยใหม่รองรับไวยากรณ์ของตัวแปร ดังนั้นการประกาศครั้งก่อนจะถูกยกเลิกแทน
  2. ณ เวลาที่คำนวณค่า ตัวแปรจะถูกคอมไพล์ว่าไม่ถูกต้อง แต่สายเกินไป — การประกาศครั้งก่อนถูกยกเลิกไปแล้ว ตามข้อมูลจำเพาะ ค่า ตัวแปรที่ไม่ถูกต้องจะถือว่าเหมือนกับ unset :

ดูปากกา [อุปกรณ์ประกอบฉากที่กำหนดเอง: ไม่ถูกต้อง/ไม่ได้รับการสนับสนุน vs ไม่ได้กำหนด](https://codepen.io/smashingmag/pen/VJMGbJ/) โดย Miriam Suzanne

ดูอุปกรณ์ประกอบฉากที่กำหนดเองของปากกา: ไม่ถูกต้อง/ไม่ได้รับการสนับสนุน vs ไม่ได้กำหนดโดย Miriam Suzanne
 html { color: red; /* ignored as *invalid syntax* by all browsers */ /* - old browsers: red */ /* - new browsers: red */ color: not a valid color; color: var(not a valid variable name); /* ignored as *invalid syntax* by browsers without var support */ /* valid syntax, but invalid *values* in modern browsers */ /* - old browsers: red */ /* - new browsers: unset (black) */ --invalid-value: not a valid color value; color: var(--undefined-variable); color: var(--invalid-value); }

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

คุณสมบัติที่กำหนดเอง "มิกซ์"

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

 * { --stripes: linear-gradient( var(--stripes-angle), powderblue 20%, pink 20% 40%, white 40% 60%, pink 60% 80%, powderblue 80% ); background-image: var(--stripes); }

ตราบใดที่ --stripes-angle ยังคงไม่ถูกต้องหรือไม่ถูกกำหนด มิกซ์อินจะไม่สามารถคอมไพล์ได้ และจะไม่มี background-image หากเราตั้งค่ามุมที่ถูกต้องบนองค์ประกอบใดๆ ฟังก์ชันจะคำนวณและให้พื้นหลังแก่เรา:

 div { --stripes-angle: 30deg; /* generates the background */ }

ขออภัย ค่าพารามิเตอร์ นั้นจะ สืบทอดมา ดังนั้นคำจำกัดความปัจจุบันจะสร้างพื้นหลังบน div และรายการย่อยทั้งหมด ในการแก้ไขปัญหานั้น เราต้องตรวจสอบให้แน่ใจว่าค่า --stripes-angle ไม่ได้รับการสืบทอด โดยวางให้เป็น initial (หรือค่าที่ไม่ถูกต้อง) ในทุกองค์ประกอบ เราสามารถทำได้โดยใช้ตัวเลือกสากลเดียวกัน:

See the Pen [อุปกรณ์ประกอบฉากที่กำหนดเอง: Mixin](https://codepen.io/smashingmag/pen/ZdXMJx/) โดย Miriam Suzanne

ดูอุปกรณ์ประกอบฉากที่กำหนดเองของปากกา: Mixin โดย Miriam Suzanne
 * { --stripes-angle: initial; --stripes: /* etc… */; background-image: var(--stripes); }

รูปแบบอินไลน์ที่ปลอดภัย

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

See the Pen [อุปกรณ์ประกอบฉากที่กำหนดเอง: Mixin + Inline Style](https://codepen.io/smashingmag/pen/qzPMPv/) โดย Miriam Suzanne

ดูอุปกรณ์ประกอบฉากที่กำหนดเองของปากกา: Mixin + Inline Style โดย Miriam Suzanne
 <div>...</div>

สไตล์อินไลน์มีความเฉพาะเจาะจงสูงและยากต่อการแทนที่ — แต่ด้วยคุณสมบัติแบบกำหนดเอง เรามีตัวเลือกอื่น: ไม่ต้องสนใจ หากเราตั้งค่า div เป็น background-image: none (ตัวอย่าง) ตัวแปรอินไลน์นั้นจะไม่ได้รับผลกระทบ เพื่อให้ห่างไกลออกไปอีก เราสามารถสร้างตัวแปรระดับกลางได้:

 * { --stripes-angle: var(--stripes-angle-dynamic, initial); }

ตอนนี้เรามีตัวเลือกในการกำหนด --stripes-angle-dynamic ใน HTML หรือละเว้น และตั้งค่า --stripes-angle โดยตรงในสไตล์ชีตของเรา

See the Pen [อุปกรณ์ประกอบฉากที่กำหนดเอง: Mixin + Inline / Override](https://codepen.io/smashingmag/pen/ZdXMao/) โดย Miriam Suzanne

ดูอุปกรณ์ประกอบฉากที่กำหนดเองของปากกา: Mixin + Inline / Override โดย Miriam Suzanne

ค่าที่ตั้งไว้ล่วงหน้า

สำหรับค่าที่ซับซ้อนมากขึ้น หรือรูปแบบทั่วไปที่เราต้องการใช้ซ้ำ เราสามารถจัดเตรียมตัวแปรที่กำหนดไว้ล่วงหน้าสองสามตัวให้เลือก:

 * { --tilt-down: 6deg; --tilt-up: -6deg; }

และใช้ค่าที่ตั้งไว้ล่วงหน้าเหล่านั้น แทนที่จะตั้งค่าโดยตรง:

 <div>...</div> 

See the Pen [อุปกรณ์ประกอบฉากที่กำหนดเอง: Mixin + Presets](https://codepen.io/smashingmag/pen/LKemZm/) โดย Miriam Suzanne

ดู Pen Custom Props: Mixin + Presets โดย Miriam Suzanne

เหมาะอย่างยิ่งสำหรับการสร้างแผนภูมิและกราฟตามข้อมูลไดนามิก หรือแม้แต่จัดวางโปรแกรมวางแผนวัน

ดู Pen [แผนภูมิแท่งในตาราง CSS + ตัวแปร](https://codepen.io/smashingmag/pen/wLrEyg/) โดย Miriam Suzanne

ดูแผนภูมิ Pen Bar ในตาราง CSS + ตัวแปรโดย Miriam Suzanne

องค์ประกอบตามบริบท

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

ดูปากกา [อุปกรณ์ประกอบฉากที่กำหนดเอง: ส่วนประกอบ](https://codepen.io/smashingmag/pen/QXqVmM/) โดย Miriam Suzanne

ดู Pen Custom Props: Component โดย Miriam Suzanne
 [data-stripes] { --stripes: linear-gradient( var(--stripes-angle, to right), powderblue 20%, pink 20% 40%, white 40% 60%, pink 60% 80%, powderblue 80% ); background-image: var(--stripes); }

โดยการวางทางเลือกในฟังก์ชัน var() เราสามารถปล่อยให้ --stripes-angle ไม่ได้กำหนดและ "ว่าง" เพื่อสืบทอดค่าจากภายนอกส่วนประกอบ นี่เป็นวิธีที่ยอดเยี่ยมในการเปิดเผยลักษณะบางอย่างของรูปแบบองค์ประกอบต่อการป้อนข้อมูลตามบริบท แม้แต่รูปแบบ "กำหนดขอบเขต" ที่สร้างโดยเฟรมเวิร์ก JS (หรือกำหนดขอบเขตภายใน shadow-DOM เช่นไอคอน SVG) ก็สามารถใช้วิธีนี้เพื่อแสดงพารามิเตอร์เฉพาะสำหรับอิทธิพลภายนอก

ส่วนประกอบที่แยกออกมา

หากเราไม่ต้องการเปิดเผยพารามิเตอร์สำหรับการสืบทอด เราสามารถกำหนดตัวแปรด้วยค่าเริ่มต้นได้:

 [data-stripes] { --stripes-angle: to right; --stripes: linear-gradient( var(--stripes-angle, to right), powderblue 20%, pink 20% 40%, white 40% 60%, pink 60% 80%, powderblue 80% ); background-image: var(--stripes); }

ส่วนประกอบเหล่านี้จะทำงานกับคลาสหรือตัวเลือกที่ถูกต้องอื่นๆ ได้เช่นกัน แต่ฉันเลือกแอตทริบิวต์ data- เพื่อสร้างเนมสเปซสำหรับตัวดัดแปลงที่เราต้องการ:

 [data-stripes='vertical'] { --stripes-angle: to bottom; } [data-stripes='horizontal'] { --stripes-angle: to right; } [data-stripes='corners'] { --stripes-angle: to bottom right; } 

ดูปากกา [อุปกรณ์ประกอบฉากที่กำหนดเอง: ส่วนประกอบที่แยกได้](https://codepen.io/smashingmag/pen/agLaGX/) โดย Miriam Suzanne

ดู Pen Custom Props: Isolated Components โดย Miriam Suzanne

ตัวเลือกและพารามิเตอร์

ฉันมักจะหวังว่าฉันจะใช้ data-attributes เพื่อตั้งค่าตัวแปร — คุณลักษณะที่รองรับโดยข้อกำหนด CSS3 attr() แต่ยังไม่ได้ใช้งานในเบราว์เซอร์ใด ๆ (ดูแท็บทรัพยากรสำหรับปัญหาที่เชื่อมโยงในแต่ละเบราว์เซอร์) ซึ่งจะทำให้เราสามารถเชื่อมโยงตัวเลือกกับพารามิเตอร์เฉพาะได้อย่างใกล้ชิดยิ่งขึ้น:

 <div data-stripes="30deg">...</div> /* Part of the CSS3 spec, but not yet supported */ /* attr( , ) */ [data-stripes] { --stripes-angle: attr(data-stripes angle, to right); } <div data-stripes="30deg">...</div> /* Part of the CSS3 spec, but not yet supported */ /* attr( , ) */ [data-stripes] { --stripes-angle: attr(data-stripes angle, to right); } <div data-stripes="30deg">...</div> /* Part of the CSS3 spec, but not yet supported */ /* attr( , ) */ [data-stripes] { --stripes-angle: attr(data-stripes angle, to right); }

ในระหว่างนี้ เราสามารถบรรลุสิ่งที่คล้ายกันได้โดยใช้แอตทริบิวต์ style :

ดูปากกา [อุปกรณ์ประกอบฉากที่กำหนดเอง: ตัวเลือกรูปแบบ](https://codepen.io/smashingmag/pen/PrJdBG/) โดย Miriam Suzanne

ดูอุปกรณ์ประกอบฉากที่กำหนดเองของปากกา: ตัวเลือกสไตล์โดย Miriam Suzanne
 <div>...</div> /* The `*=` atttribute selector will match a string anywhere in the attribute */ [style*='--stripes-angle'] { /* Only define the function where we want to call it */ --stripes: linear-gradient(…); }

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

 [style*='--grid-area'] { background-color: white; grid-area: var(--grid-area, auto / 1 / auto / -1); padding: 1em; }

บทสรุป

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

สิ่งนี้เรียกร้องให้เราคิดใหม่เกี่ยวกับเครื่องมือต่างๆ ที่เราเคยใช้ในอดีต — ตั้งแต่การตั้งชื่อแบบแผนการตั้งชื่ออย่าง SMACSS และ BEM ไปจนถึงรูปแบบ "กำหนดขอบเขต" และ CSS-in-JS เครื่องมือเหล่านี้จำนวนมากช่วยหลีกเลี่ยงความเฉพาะเจาะจง หรือจัดการรูปแบบไดนามิกในภาษาอื่น ซึ่งเป็นกรณีใช้งานที่เราสามารถแก้ไขได้โดยตรงด้วยคุณสมบัติที่กำหนดเอง รูปแบบไดนามิกที่เราคำนวณบ่อยครั้งใน JS ตอนนี้สามารถจัดการได้โดยส่งข้อมูลดิบไปยัง CSS

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

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

อ่านเพิ่มเติม

  • “ได้เวลาเริ่มใช้ CSS Custom Properties แล้ว” Serg Hospodarets
  • “คู่มือกลยุทธ์สำหรับคุณสมบัติที่กำหนดเองของ CSS” Michael Riethmuller