วิธีที่ Nordhealth ใช้พร็อพเพอร์ตี้ที่กำหนดเองในคอมโพเนนต์เว็บ

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

David Darnes
David Darnes

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

วิธีที่เราสร้างคอมโพเนนต์ของเว็บ

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


import {html, css, LitElement} from 'lit';

export class SimpleGreeting extends LitElement {
  static styles = css`:host { color: blue; font-family: sans-serif; }`;

  static properties = {
    name: {type: String},
  };

  constructor() {
    super();
    this.name = 'there';
  }

  render() {
    return html`

Hey ${this.name}, welcome to Web Components!

`; } } customElements.define('simple-greeting', SimpleGreeting);
คอมโพเนนต์เว็บที่เขียนด้วย Lit.

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


// TODO: DevSite - Code sample removed as it used inline event handlers
การใช้คอมโพเนนต์ของเว็บที่สร้างขึ้นด้านบนในหน้าเว็บ

การห่อหุ้มรูปแบบ Shadow DOM

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

Shadow DOM ตรวจสอบในเครื่องมือสำหรับนักพัฒนาเว็บแล้ว
ตัวอย่างของ Shadow DOM ในองค์ประกอบการป้อนข้อความปกติและในคอมโพเนนต์เว็บอินพุต Nord

หนึ่งในข้อดี (หรือข้อเสีย ขึ้นอยู่กับแผนงานของคุณ) สำหรับ Shadow DOM คือการห่อหุ้มรูปแบบ หากคุณเขียน CSS ภายในคอมโพเนนต์เว็บ รูปแบบเหล่านั้นต้องไม่รั่วไหลและส่งผลกระทบต่อหน้าหลักหรือองค์ประกอบอื่นๆ โดยสไตล์เหล่านี้ทั้งหมดอยู่ภายในคอมโพเนนต์นั้น นอกจากนี้ CSS ที่เขียนขึ้นสำหรับหน้าหลักหรือคอมโพเนนต์เว็บระดับบนสุดต้องไม่รั่วไหลเข้าไปในคอมโพเนนต์เว็บของคุณ

การสรุปรูปแบบเช่นนี้เป็นประโยชน์ในไลบรารีคอมโพเนนต์ของเรา และยังให้การรับประกันได้มากขึ้นว่าเมื่อมีคนใช้คอมโพเนนต์อย่างใดอย่างหนึ่งของเรา ผลิตภัณฑ์นั้นจะมีลักษณะตามที่เราตั้งใจไว้ ไม่ว่ารูปแบบที่ใช้กับหน้าหลักจะเป็นรูปแบบใดก็ตาม และเพื่อให้แน่ใจยิ่งขึ้น เราจะเพิ่ม all: unset; ลงในรูท หรือ "โฮสต์" ของคอมโพเนนต์เว็บทั้งหมดของเรา


:host {
  all: unset;
  display: block;
  box-sizing: border-box;
  text-align: start;
  /* ... */
}
โค้ดต้นแบบของคอมโพเนนต์บางส่วนที่นำไปใช้กับรูทเงาหรือตัวเลือกโฮสต์

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

โดยคุณสมบัติที่กำหนดเองของ CSS สามารถช่วยได้

พร็อพเพอร์ตี้ที่กำหนดเองของ CSS

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


:root {
  --n-color-accent: rgb(53, 89, 199);
  /* ... */
}

.n-color-accent-text {
  color: var(--n-color-accent);
}
ตัวอย่างจากเฟรมเวิร์ก CSS ของโทเค็นการออกแบบในฐานะพร็อพเพอร์ตี้ที่กำหนดเองและนำไปใช้ในคลาสตัวช่วย

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

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

พร็อพเพอร์ตี้ที่กำหนดเองในคอมโพเนนต์เว็บ Nord

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


:root {
  --n-space-m: 16px;
  --n-space-l: 24px;
  /* ... */
  --n-color-background: rgb(255, 255, 255);
  --n-color-border: rgb(216, 222, 228);
  /* ... */
}
กำลังกำหนดพร็อพเพอร์ตี้ที่กำหนดเองของ CSS ในตัวเลือกรูท

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


:host {
  --n-tab-group-padding: 0;
  --n-tab-list-background: var(--n-color-background);
  --n-tab-list-border: inset 0 -1px 0 0 var(--n-color-border);
  /* ... */
}

.n-tab-group-list {
  box-shadow: var(--n-tab-list-border);
  background-color: var(--n-tab-list-background);
  gap: var(--n-space-s);
  /* ... */
}
คุณสมบัติที่กำหนดเองจะถูกกำหนดในรูทเงาของคอมโพเนนต์ แล้วนำไปใช้ในสไตล์คอมโพเนนต์ มีการใช้พร็อพเพอร์ตี้ที่กำหนดเองจากรายการโทเค็นการออกแบบด้วย

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


.n-tab-group-list::before {
  /* ... */
  padding-inline-start: var(--n-tab-group-padding);
}

.n-tab-group-list::after {
  /* ... */
  padding-inline-end: var(--n-tab-group-padding);
}
การใช้พร็อพเพอร์ตี้ที่กำหนดเองตามบริบทของกลุ่มแท็บซึ่งมีการใช้ในหลายๆ ตำแหน่งภายในโค้ดคอมโพเนนต์

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


:host([padding="l"]) {
  --n-tab-group-padding: var(--n-space-l);
}
องค์ประกอบแท็บแบบหนึ่งซึ่งมีการเปลี่ยนแปลงระยะห่างจากขอบโดยใช้การอัปเดตพร็อพเพอร์ตี้ที่กำหนดเองรายการเดียวแทนการอัปเดตหลายรายการ

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


<nord-tab-group label="Title">
  <!-- ... -->
</nord-tab-group>

<style>
  nord-tab-group {
    --n-tab-group-padding: var(--n-space-xl);
  }
</style>
การใช้คอมโพเนนต์กลุ่มแท็บในหน้าเว็บและการอัปเดตพร็อพเพอร์ตี้ที่กำหนดเองของระยะห่างจากขอบให้มีขนาดใหญ่ขึ้น

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

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

พัฒนาคุณสมบัติที่กำหนดเองให้ก้าวไปอีกขั้น

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

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


<nord-divider></nord-divider>

<section>
  <nord-divider></nord-divider>
   <!-- ... -->
</section>

<style>
  nord-divider {
    --n-divider-color: var(--n-color-status-danger);
  }

  section {
    padding: var(--n-space-s);
    background: var(--n-color-surface-raised);
  }
  
  section nord-divider {
    --n-divider-color: var(--n-color-status-success);
  }
</style>
คอมโพเนนต์ตัวแบ่งของเรา 2 อินสแตนซ์ซึ่งจำเป็นต้องใช้ตัวเลือกสีที่แตกต่างกัน 2 แบบ แท็กหนึ่งจะฝังอยู่ในส่วนที่เราจะใช้สำหรับตัวเลือกที่เจาะจงมากขึ้นได้ แต่เราต้องกำหนดเป้าหมายตัวแบ่งโดยเฉพาะ

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

พร็อพเพอร์ตี้ที่กำหนดเองแบบสาธารณะและแบบส่วนตัว

พร็อพเพอร์ตี้ที่กำหนดเองที่กำหนดเองคือพร็อพเพอร์ตี้ที่ Lea Verou ได้รวบรวมไว้ ซึ่งเป็นพร็อพเพอร์ตี้ที่กำหนดเองแบบ "ส่วนตัว" ตามบริบทในคอมโพเนนต์เอง แต่มีการตั้งค่าเป็นพร็อพเพอร์ตี้ที่กำหนดเอง "สาธารณะ" ที่มีพร็อพเพอร์ตี้สำรอง



:host {
  --_n-divider-color: var(--n-divider-color, var(--n-color-border));
  --_n-divider-size: var(--n-divider-size, 1px);
}

.n-divider {
  border-block-start: solid var(--_n-divider-size) var(--_n-divider-color);
  /* ... */
}
CSS คอมโพเนนต์เว็บของตัวแบ่งที่มีการปรับพร็อพเพอร์ตี้ที่กำหนดเองตามบริบทเพื่อให้ CSS ภายในใช้พร็อพเพอร์ตี้ที่กำหนดเองแบบส่วนตัว ซึ่งตั้งค่าเป็นพร็อพเพอร์ตี้ที่กำหนดเองแบบสาธารณะโดยมีทางเลือกสำรอง

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


<nord-divider></nord-divider>

<section>
  <nord-divider></nord-divider>
   <!-- ... -->
</section>

<style>
  nord-divider {
    --n-divider-color: var(--n-color-status-danger);
  }

  section {
    padding: var(--n-space-s);
    background: var(--n-color-surface-raised);
    --n-divider-color: var(--n-color-status-success);
  }
</style>
ตัวแบ่ง 2 อันอีกครั้ง แต่คราวนี้คุณสามารถเปลี่ยนสีของตัวแบ่งได้โดยเพิ่มพร็อพเพอร์ตี้ที่กำหนดเองตามบริบทของตัวแบ่งลงในตัวเลือกส่วน ตัวแบ่งจะรับค่านั้น ซึ่งจะทำให้ได้โค้ดที่ดูสะอาดและยืดหยุ่นมากกว่า

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

เราหวังว่าคุณจะได้รับข้อมูลเชิงลึกเกี่ยวกับวิธีที่เราใช้คอมโพเนนต์ของเว็บที่มีพร็อพเพอร์ตี้ที่กำหนดเองของ CSS ที่เป็นประโยชน์ อย่าลืมบอกให้เราทราบว่าคุณคิดอย่างไร และหากคุณตัดสินใจจะใช้วิธีการเหล่านี้ในการทำงานของคุณเอง ไปพบฉันได้ที่ Twitter @DavidDarnes นอกจากนี้ ยังสามารถค้นหา Nordhealth @NordhealthHQ ใน Twitter รวมถึงทีมงานคนอื่นๆ ที่ทำงานอย่างเต็มที่เพื่อนำระบบการออกแบบนี้มาทำงานร่วมกันและใช้งานฟีเจอร์ต่างๆ ที่กล่าวถึงในบทความนี้ ได้แก่ @Viljamis, @WickyNilliams และ @eric_habich

รูปภาพหลักโดย Dan Cristian Pădureusercontent