פיתוח Web Components (Custom Elements) לאתר
שילוב לוגיקת UI מורכבת בפרויקט ללא פריימוורקים הוא כאב ראש. אנו נתקלים לעתים קרובות במשימה של יצירת אלמנט לשימוש חוזר שעובד היטב ב-WordPress, Laravel Blade, ו-HTML סטטי. הפתרון הוא Web Components — קבוצה של ממשקי API מקוריים של הדפדפן המאפשרים לפתח אלמנטים HTML מותאמים אישית עם לוגיקה וסגנונות מובלעים. ללא תלות ב-React, Vue, או jQuery. הרכיבים חיים בכל הקשר HTML.
שלושה מרכיבים: Custom Elements API (רישום תג חדש), Shadow DOM (בידוד סגנונות), HTML Templates (תבניות). הם משמשים באופן עצמאי או יחד. הניסיון של הצוות שלנו הוא 5+ שנים בפיתוח מקורי, עם למעלה מ-10 פרויקטים המשתמשים ב-Web Components. אנו מבטיחים תאימות בין דפדפנים וביצועים.
בעיות עיקריות בשילוב Custom Elements
הבעיה הראשונה היא התנגשויות סגנון. ללא Shadow DOM, סגנונות מובלעים יכולים להידרס על ידי כללי CSS גלובליים. הבעיה השנייה היא חוסר ראקטיביות: בניגוד לפריימוורקים, אלמנטים מקוריים אינם מתעדכנים אוטומטית כאשר תכונות משתנות. הבעיה השלישית היא מורכבות בדיקות: נדרשת סביבה עם תמיכה ב-Custom Elements. אנו פותרים בעיות אלה באמצעות ארכיטקטורה מחושבת ושימוש ב-TypeScript.
יתרונות של Web Components
היתרון המרכזי הוא עצמאות מפריימוורקים. רכיב שנכתב פעם אחת עובד בכל פרויקט: React, Vue, Angular, Svelte, HTML פשוט. זה מקצר את זמן הפיתוח ב-30% ומפשט את התחזוקה. בנוסף, Web Components אינם דורשים bundler — גודלם אפס, בניגוד לרכיבי React שדורשים כ-40 KB של ספרייה. עבור פרויקט ישן על jQuery, זו דרך אידיאלית להוסיף פונקציונליות מודרנית ללא העברת כל המחסנית.
איך ליצור Custom Element מאפס?
התהליך כולל חמישה שלבים:
- עיצוב ה-API של הרכיב — קביעת תכונות, אירועים ומתודות ציבוריות.
- כתיבת מחלקה היורשת מ-HTMLElement.
- יישום מתודות מחזור חיים (connectedCallback, attributeChangedCallback וכו').
- רישום האלמנט באמצעות customElements.define().
- בדיקה במספר דפדפנים.
הנה דוגמה אופיינית — רכיב התראה:
class ToastNotification extends HTMLElement {
private shadow: ShadowRoot
private messageEl: HTMLElement | null = null
static get observedAttributes() {
return ['type', 'message', 'duration']
}
constructor() {
super()
this.shadow = this.attachShadow({ mode: 'open' })
}
connectedCallback() {
this.render()
const duration = parseInt(this.getAttribute('duration') || '3000')
if (duration > 0) {
setTimeout(() => this.dismiss(), duration)
}
}
disconnectedCallback() {
this.messageEl?.removeEventListener('click', this.dismiss)
}
attributeChangedCallback(name: string, oldVal: string, newVal: string) {
if (oldVal !== newVal && this.isConnected) {
this.render()
}
}
private render() {
const type = this.getAttribute('type') || 'info'
const message = this.getAttribute('message') || ''
this.shadow.innerHTML = `
<style>
:host {
display: block;
font-family: inherit;
}
.toast {
padding: 12px 20px;
border-radius: 8px;
font-size: 14px;
line-height: 1.4;
cursor: pointer;
animation: slide-in 0.3s ease;
}
.toast--info {
background: #1a1a2e;
color: #7eb8f7;
border: 1px solid #2a4a7f;
}
.toast--success {
background: #0d2e1a;
color: #5cb85c;
border: 1px solid #1a5e30;
}
.toast--error {
background: #2e0d0d;
color: #e74c3c;
border: 1px solid #7f1a1a;
}
@keyframes slide-in {
from {
transform: translateY(-10px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
</style>
<div class="toast toast--${type}" part="toast">
${message}
</div>
`
this.messageEl = this.shadow.querySelector('.toast')
this.messageEl?.addEventListener('click', this.dismiss)
}
private dismiss = () => {
this.dispatchEvent(new CustomEvent('toast-dismiss', {
bubbles: true,
composed: true,
}))
this.remove()
}
show(message: string, type = 'info') {
this.setAttribute('message', message)
this.setAttribute('type', type)
if (!this.isConnected) {
document.body.appendChild(this)
}
}
}
customElements.define('toast-notification', ToastNotification)
ניתן להשתמש בו דרך HTML וגם דרך JS.
מחזור חיים ו-TypeScript
Custom Elements מספקים ארבע מתודות מחזור חיים: constructor, connectedCallback, disconnectedCallback, attributeChangedCallback. לשילוב נכון עם TypeScript, יש להצהיר על טיפוסים ב-HTMLElementTagNameMap:
declare global { interface HTMLElementTagNameMap { 'toast-notification': ToastNotification } } ת打字 של אלמנטים מותאמים אישית הוא שלב חשוב ליצירת רכיבים עסקיים עם השלמה אוטומטית ב-IDE.
| מתודה | מתי נקראת | מה לעשות |
|---|---|---|
| constructor | כאשר האלמנט נוצר | אתחול מצב, יצירת Shadow DOM |
| connectedCallback | כאשר נוסף ל-DOM | הגדרת מטפלים, טעינת נתונים |
| disconnectedCallback | כאשר הוסר מה-DOM | ניקוי מטפלים, טיימרים |
| attributeChangedCallback | כאשר תכונה במעקב משתנה | עדכון תצוגה |
מתי Shadow DOM אינו נחוץ?
Shadow DOM מוסיף בידוד אך גם מורכבות. עבור אלמנטים פשוטים (לדוגמה, מונה או רשימה מתקפלת), Light DOM מספיק — סגנונות יורשים מההורה, הקוד פשוט יותר. דוגמה לאקורדיון:
class AccordionItem extends HTMLElement {
private header!: HTMLElement
private content!: HTMLElement
private isOpen = false
connectedCallback() {
this.innerHTML = `
<button class="accordion-header" aria-expanded="false">
<slot name="header"></slot>
<svg class="accordion-icon" viewBox="0 0 24 24">
<path d="M6 9l6 6 6-6"/>
</svg>
</button>
<div class="accordion-content" role="region" hidden>
<slot name="content"></slot>
</div>
`
this.header = this.querySelector('.accordion-header')!
this.content = this.querySelector('.accordion-content')!
this.header.addEventListener('click', this.toggle)
}
private toggle = () => {
this.isOpen = !this.isOpen
this.header.setAttribute('aria-expanded', String(this.isOpen))
this.content.hidden = !this.isOpen
}
disconnectedCallback() {
this.header?.removeEventListener('click', this.toggle)
}
}
customElements.define('accordion-item', AccordionItem) השוואת גישות
| פרמטר | Shadow DOM | Light DOM |
|---|---|---|
| בידוד סגנונות | מלא | אין |
| מורכבות | גבוהה יותר | נמוכה יותר |
| ביצועים | זהים | זהים |
| מתאים ל | ספריות, ווידג'טים מורכבים | אלמנטים אינטראקטיביים פשוטים |
| גישה | זמן לרכיב ראשון | שימוש חוזר | תלות |
|---|---|---|---|
| Web Components | 4-8 שעות | 100% בכל פרויקט | אין |
| רכיב React | 2-4 שעות | רק ב-React | 40KB |
| רכיב Vue | 2-4 שעות | רק ב-Vue | 30KB |
מה כלול בעבודה?
- עיצוב API של הרכיב
- יישום ב-TypeScript עם מחזור חיים מלא
- כתיבת הצהרות טיפוסים
- אינטגרציה עם הפרויקט הקיים
- תיעוד על שימוש והתאמה אישית
- חודש תמיכה לאחר המסירה
לוח זמנים ועלות
רכיב פשוט אחד ללא Shadow DOM — מ-4 עד 8 שעות. רכיב מורכב עם אנימציות, בדיקות ו-TypeScript — 1–3 ימים. ספרייה של 5–10 רכיבים — 1–2 שבועות. העלות מחושבת באופן אישי עבור הפרויקט שלך. צור קשר לייעוץ — נעריך את היישום ונציע פתרון אופטימלי.
בפרויקט אחד עבור בנק גדול, פיתחנו ספרייה של 8 אלמנטים מותאמים אישית (אקורדיון, טאבים, התראות, dropdown, חלון מודאלי, paginator, כרטיס מוצר, סליידר) כדי להחליף תוספי jQuery. כתוצאה מכך, גודל העמוד ירד ב-200 KB, ומהירות הטעינה עלתה ב-15%. יתר על כן, הרכיבים שימשו גם ב-WordPress וגם בפאנל הניהול של React ללא שינויים. חיסכון כזה במשאבים הוא תוצאה ישירה של היעדר תלות וקלות המשקל של אלמנטים מקוריים.
הזמן פיתוח Web Components מותאמים אישית — קבל אלמנטים אמינים ועצמאיים לכל אתר. קבל ייעוץ — נענה על כל השאלות ונעזור לך לבחור את הגישה האופטימלית.







