המאמר הזה מבוסס על ההרצאה שהעברתי במיטאפ האחרון בסדרת Tech Talk Teach שהתקיימה במשרדי OATH ישראל (שם אני עובד). במיטאפ הוקדש לשחזור טכנולוגיות משנות התשעים באמצעות טכנולוגיה חדשה. ההרצאה היתה היתולית בתוכנה (אף אחד באמת לא רוצה את blink) אבל הטכנולוגיה היא אכן אמיתית ולפי דעתי היא הכיוון שאליו התחום הולך. הטכנולוגיה כרגע נתמכת במלואה עם כרום בלבד ובעתיד גם בפיירפוקס. כרגע ניתן להשתמש בפוליפילים כדי לאפשר תמיכה.
המצגת:
מי מכיר את תגית <blink>? זו תגית שהיתה פופולרית בשנות ה-90 והיה לה שימוש מדהים במיוחד – כל מה שהיא עטפה הבהב! ממש כך!
אבל למרבה הצער, ארגון ה-W3C הבינלאומי החליט להרוג את התגית הזו! למה? ובכן, הם טענו שהתגית הזו בעייתית כי היא לא נגישה: לקויי ראייה ועם בעיות אפילפסיה יתקשו להתמודד איתה. התגית הזו מערבבת בין תצוגה לשימושיות כיוון שכל ענייני התצוגה והאנימציה צריכים להיות ב-CSS. וכך התגית הזו נזרקה לפח האשפה של ההיסטוריה. כמה מצער, כיוון שהתגית הזו התנוססה בשעתו על גבי מאות אלפי אתרי בית של אנשים בגיאוסיטיז, שירות אחסון האתרים מתקופות אנו-באנו.
אבל אנחנו יכולים ליצור אותה! איך? עם web component. היכולת של ג'אווהסקריפט ליצור קומפוננטות ללא כל ספרייה או פריימוורק. במקום להשתמש באנגולר, ריאקט, ויו או כל פריימוורק אחר שנוצר על ידי מפתחי ג'אווהסקריפט מזוקנים והיפסטרים. באמצעות הטכנולוגיה הזו אנחנו יכולים לייצר קומפוננטות עצמאיות שלא תלויות בסביבה שלהן ולא מושפעות ממנה. וזה כל כך פשוט! באמת!
הצעד הראשון הוא הכרזה על web component באמצעות window.customElements.define שמקבלת שני ארגומנטים: הראשון הוא שם הקומפוננטה כפי שנכניס אותו ב-HTML (תמיד מופרד במקף ולא יכול להיות בלי מקף). והשני הוא השם של הקלאס הג'אווהסקריפטי.
window.customElements.define('blink-reborn', blink);
איך הקלאס נראה? פשוט שבפשוטים! ראשית הוא יורש מ-HTMLElement (קבלו את זה כעובדה, כך האימפלמנטציה דורשת) ובקונסטרקטור שלו יש לנו את super. המתודה השניה היא connectedCallback שרצה מייד לאחר שהקומפוננטה מתרנדרת ובה אנחנו יכולים לעשות מה שאנחנו רוצים. כאן יש לי הפעלה של ה-shadow dom.
class blink extends HTMLElement {
constructor() {
super();
}
connectedCallback() { // Running in the begenning
this.insideText = this.innerHTML;
this.shadow = this.attachShadow({mode: 'open'});
this.shadow.innerHTML = this.insideText;
}
}
window.customElements.define('blink-reborn', blink);
כדי לקרוא לקומפוננטה, כל מה שאני צריך לעשות זה את זה:
<blink-reborn>BLINK IS NOT DEAD!</blink-reborn>
ShadowDOM
זה שם מאוד מאיים למשהו מאוד פשוט. אני רוצה שה-DOM של הקומפוננטה יהיה נפרד מה-DOM של שאר המסמך. אם יש CSS שמשפיע על שאר ה-HTML, אני לא רוצה שהוא ישפיע על הקומפוננטה שלי. אם יש CSS בתוך הקומפוננטה שלי, אני לא רוצה שהוא יזלוג החוצה. shadow DOM פשוט מאפשר לי לשמור על הפרדה מלאה בין ה-HTML של הקומפוננטה לבין כל מה שקורה שבחוץ. כיף כיף כיף :). איך אני יוצא shadow DOM בקומפוננטה שלי? פשוט!
ראשית, לוקח את כל הטקסט ש-<blink-reborn> מקיף ומכניס אותו לתוך this.insideText כי אני רוצה להשתמש בו יותר מאוחר. איך? ככה:
this.insideText = this.innerHTML;
עכשיו אני יוצר את ה-shadow DOM ומכניס אותו לתוך משתנה שנקרא this.shadow. זה חשוב כי הוא הולך להחליף את כל ה-documnet שאני הולך לקרוא לו בקומפוננטה שלי. אני רוצה להכניס טקסט כלשהו לקומפוננטה? אני חייב להשתמש ב-this.shadow.innerText או innerHTML. אני רוצה לבחור אלמנט מתוך הקומפוננטה? this.shadow.querySelector. יש לו את כל המתודות ואת כל התכונות של document, אבל הוא פנימי ופרטי רק לי.
this.shadow = this.attachShadow({mode: 'open'});
בשורה השלישית אני פשוט מכניס את ה-insideText שיצרתי בשורה הראשונה ל-shadowDOM. זה מה שמאפשר לי לשמור את מה שהמשתמש הכניס בין התגיות של הקומפוננטה שלי.
this.shadow.innerHTML = this.insideText;
אם תפתחו inspect ותסתכלו על הרכיב, תוכלו לראות שבמקום document יש לי shadow root:
הנה דוגמה חיה!
See the Pen marquee and blink phase 1 by Ran Bar-Zik (@barzik) on CodePen.
אבל רגע! זו לא ממש דוגמה טובה במיוחד, לא? איפה ההבהוב? איפה הכפיים?
על מנת להתקדם, אני רוצה לעטוף את מה שהמשתמש מספק לי – כלומר הטקסט (או האלמנטים שבין התגיות של blink-reborn באלמנט אחר כדי שאוכל לעצב את זה. איך אני עושה את זה? זה ג'אווהסקריפט!זה לא פריימוורק. כל מה שאנחנו יודעים מג'אווהסקריפט רגיל יעבוד גם פה. אז מה שאני אעשה זה:
class blink extends HTMLElement {
constructor() {
super();
}
connectedCallback() { // Running in the begenning
this.insideText = this.innerHTML;
this.shadow = this.attachShadow({mode: 'open'});
this.shadow.innerHTML = `${this.insideText}
`;
}
}
window.customElements.define('blink-reborn', blink);
במקום להכניס ל-shadow.innerHTML את הטקסט שהמשתמש הכניס, אני אעטוף אותו ב-p עם id. אם הסינטקס של הטמפלייט לא מוכר לכם (זה עם הגרש העקום), זה הזמן לעבור על הפיצ'ר הממש פשוט הזה ב-ES6.
איך זה נראה, לא שונה במיוחד, אבל אם אני אעשה inspect, אני אראה שאת התוכן בתוך ה-shadow DOM עוטף p קטן וחמוד.
See the Pen marquee and blink phase 2 by Ran Bar-Zik (@barzik) on CodePen.
טוב, עדיין לא מה שאנחנו רוצים. מה עם קצת CSS? טוב, גם זה די קל. כאמור יש לי כאן ג'אווהסקריפט פשוט שבפשוטים. כל מה שאני צריך לעשות זה ליצור CSS ולהכניס אותו. אני אשתמש ביצירת ה-CSS באופן מאוד נאיבי. אבל זה די ברור.
class blink extends HTMLElement {
constructor() {
super();
}
connectedCallback() { // Running in the begenning
this.insideText = this.innerHTML;
this.shadow = this.attachShadow({mode: 'open'});
this.shadow.innerHTML = `${this.insideText}
`;
this.addStyle();
}
addStyle() {
const style = document.createElement('style');
style.textContent = `
p { color: red; }
`;
this.shadow.appendChild(style);
}
}
window.customElements.define('blink-reborn', blink);
פונקצית addStyle פשוט יוצרת CSS ומוסיפה אותו. ההבדל היחידי בין זה לבין ג'אווהסקריפט רגיל הוא שאני עושה appendChild ל- shadow dom. זה הכל. ברגע שאני משתמש ב-shadow, אני פשוט פונה אליו במקום ל-document. זה הכל. ככה זה נראה:
See the Pen marquee and blink phase 3 by Ran Bar-Zik (@barzik) on CodePen.
טוב, אנחנו לא רוצים טקסט אדום, אנחנו רוצים אנימציה! כאן אני פשוט אחליף את ה-CSS שצובע את ה-p באדום, ל-CSS שעושה אנימציות. פשוט ביותר. כאמור, אנחנו בג'אווהסקריפט הכי פשוט שיש. לא פריימוורק, לא בלגן. CSS, ג'אווהסקריפט. c'est tout.
class blink extends HTMLElement {
constructor() {
super();
}
connectedCallback() { // Running in the begenning
this.insideText = this.innerHTML;
this.shadow = this.attachShadow({mode: 'open'});
this.shadow.innerHTML = `${this.insideText}
`;
this.addStyle();
}
addStyle() {
const style = document.createElement('style');
style.textContent = `
#blink { animation: blink-animation 1s steps(5, start) infinite;}
@keyframes blink-animation {
to {
visibility: hidden;
}
`;
this.shadow.appendChild(style);
}
}
window.customElements.define('blink-reborn', blink);
וככה זה נראה! איזו חגיגה! סוף סוף יצרנו את התגית blink ונוכל להשתמש בה באתר שלנו בגיאוסיטיז!
See the Pen marquee and blink phase 4 by Ran Bar-Zik (@barzik) on CodePen.
אבל רגע, למה שלא נשפר? נניח ואני רוצה לקבוע את קצב ההבהוב על ידי תכונה שמועברת באלמנט ב-HTML. משהו בסגנון הזה:
<blink-reborn bit="1">BLINK IS NOT DEAD!</blink-reborn>
ה-bit מעביר לי את קצב האנימציה. הדבר היחידי שאני צריך לעשות במקרה הזה, זה להגדיר get לתכונת ה-bit ואת זה אני עושה ב-class שלי. זה כל כך פשוט שזה מגוחך. עושים את זה כך:
class blink extends HTMLElement {
constructor() {
super();
}
connectedCallback() { // Running in the begenning
this.insideText = this.innerHTML;
this.shadow = this.attachShadow({mode: 'open'});
this.shadow.innerHTML = `${this.insideText}
`;
this.addStyle();
}
get bit() {
const bit = this.getAttribute('bit');
return bit;
}
addStyle() {
const style = document.createElement('style');
style.textContent = `
#blink { animation: blink-animation ${this.bit}s steps(5, start) infinite;}
@keyframes blink-animation {
to {
visibility: hidden;
}
`;
this.shadow.appendChild(style);
}
}
window.customElements.define('blink-reborn', blink);
באמצעות getAttribute שמקבלת ארגומנט של שם הערך ומוצבת בפונקצית get, אני יכול לקבוע שכל מה שמשתמש מכניס ל-bit ב-HTML, יהיה לי זמין ב-this.bit וכמובן להשתמש בו ב-CSS. עכשיו יש לי תגית משופרת! ואני יכול לקבוע את קצב ההבהוב! שימו לב לחגיגה הבאה:
See the Pen all together now! by Ran Bar-Zik (@barzik) on CodePen.
אכן, אושר גדול! שימו לב שהשתמשתי פה בתגיות נוספות שה-W3C מאיים עליהן בכליה: center ו-marquee. אבל אל דאגה, ברגע שהן ייתמו מן העולם, נוכל להמשיך את החגיגה וליצור אותן מחדש באמצעות web component שנותנת לנו כלים חדשים לעשות דברים ישנים בדרך טובה הרבה יותר 🙂
12 תגובות
אחלה מדריך
תודה
אתה יכול בבקשה לתת דוגמה ליישום ריאלי?
תכנון מבוסס קומפוננטות נפוץ בהרבה מקומות (כמו במקום שבו אני עובד) ונשען על הקונספטים שיש באנגולר 1.6 ומעלה, ריאקט ו-ויאו. במסגרת של בנייה מבוססת קומפוננטות, כל רכיב הוא קומפוננטה העומדת בפני עצמה. אתה יכול להביט ב-angular material כדי לראות איך דבר כזה נראה למשל.
אכן חזרה לשנות ה90
לא שאני חלילה מתנגד לקצת JS, אבל לא היה קל יותר לסגור את זה ב-CSS פשוט?
https://jsfiddle.net/txudjrw5/1/
ברור שאפשר – כי בסופו של דבר הפתרון עצמו נשען על CSS. אבל אם אני רוצה לשנות את הקצב של ההבהוב באופן פשוט – כמו שהראיתי לקראת החלק האחרון של המאמר, זה יהיה הרבה יותר קשה למימוש ב-CSS.
חוץ מזה, וזה יותר חשוב: מדובר בסופו של דבר בהדגמה (ועוד היתולית! מי לעזאזל ירצה את ה-blink??) שנועדה להראות כמה זה קל לעבוד עם native web component 🙂
אז הסינטקס הישן כמו:
var tmpl = document.querySelector('template');
var host = document.querySelector('.img-slider');
var root = host.createShadowRoot();
root.appendChild(document.importNode(tmpl.content, true));
כבר לא יעבוד?
למה לא יעבוד? בטח שיעבוד! זה ג'אווהסקריפט נייטיב. כל מה שעובד לך בסקריפט רגיל יעבוד לך גם פה 🙂 מבטיח. למה שלא תנסה? תערוך את ה-codepen ותראה.
עוד שאלה – כבר לא משתמשים בתגית template ? עבור קומפוננטות?
אין שום בעיה להשתמש בשום דבר בקומפוננטה. זה מה שיפה בכל הסיפור הזה.
מצאתי את זה:
https://hayato.io/2016/shadowdomv1/
נתקלתי בזה https://stenciljs.com
נראה מעניין