סטנדרטיזציה של ניתוב בצד הלקוח באמצעות ממשק API חדש לגמרי, שמשנה לחלוטין את האופן שבו יוצרים אפליקציות של דף יחיד.
אפליקציות בדף יחיד (SPA) מוגדרות לפי תכונה מרכזית: כתיבת מחדש דינמית של התוכן שלהן בזמן שהמשתמש מבצע אינטראקציה עם האתר, במקום שיטת ברירת המחדל של טעינת דפים חדשים לגמרי מהשרת.
אמנם אפשר להשתמש בתכונה הזו ב-SPA דרך History API (או במקרים מוגבלים, על ידי שינוי החלק #hash באתר), אבל זהו ממשק API לא יעיל שפותח הרבה לפני ש-SPA הפך לנורמה – והאינטרנט זקוק לגישה חדשה לגמרי. Navigation API הוא ממשק API מוצעת שמבצע שדרוג מקיף של האזור הזה, במקום לנסות לתקן את הבעיות של History API. (לדוגמה, התוסף Scroll Restoration תיקן את History API במקום לנסות להמציא אותו מחדש).
בפוסט הזה מתוארת Navigation API ברמה גבוהה. כדי לקרוא את ההצעה הטכנית, אפשר לעיין בטיוטת הדוח במאגר של WICG.
דוגמה לשימוש
כדי להשתמש ב-Navigation API, קודם צריך להוסיף מאזין "navigate"
לאובייקט navigation
הגלובלי.
האירוע הזה הוא בעיקר ריכוזי: הוא יופעל בכל סוגי הניווט, בין שהמשתמש ביצע פעולה (למשל, לחיצה על קישור, שליחת טופס או חזרה אחורה וקדימה) ובין שהניווט הופעל באופן פרוגרמטי (כלומר, דרך הקוד של האתר).
ברוב המקרים, הקוד מאפשר לשנות את התנהגות ברירת המחדל של הדפדפן לפעולה הזו.
באתרי SPA, סביר להניח שהמשמעות היא להשאיר את המשתמש באותו דף ולטעון או לשנות את תוכן האתר.
האירוע NavigateEvent
מועבר למאזין "navigate"
, ומכיל מידע על הניווט, כמו כתובת ה-URL של היעד, ומאפשר לכם להגיב לניווט במקום מרכזי אחד.
מאזין בסיסי של "navigate"
יכול להיראות כך:
navigation.addEventListener('navigate', navigateEvent => {
// Exit early if this navigation shouldn't be intercepted.
// The properties to look at are discussed later in the article.
if (shouldNotIntercept(navigateEvent)) return;
const url = new URL(navigateEvent.destination.url);
if (url.pathname === '/') {
navigateEvent.intercept({handler: loadIndexPage});
} else if (url.pathname === '/cats/') {
navigateEvent.intercept({handler: loadCatsPage});
}
});
יש שתי דרכים להתמודד עם הניווט:
- קריאה ל-
intercept({ handler })
(כפי שמתואר למעלה) כדי לטפל בניווט. - שליחת
preventDefault()
, שיכולה לבטל את הניווט לגמרי.
בדוגמה הזו, קוראים לפונקציה intercept()
באירוע.
הדפדפן קורא ל-callback של handler
, שצריך להגדיר את המצב הבא של האתר.
הפעולה הזו תיצור אובייקט מעבר, navigation.transition
, שקוד אחר יכול להשתמש בו כדי לעקוב אחרי התקדמות הניווט.
בדרך כלל מותר להשתמש ב-intercept()
וב-preventDefault()
, אבל יש מקרים שבהם אי אפשר להפעיל אותם.
אי אפשר לטפל בניווטים דרך intercept()
אם מדובר בניווט בין מקורות.
בנוסף, אי אפשר לבטל ניווט באמצעות preventDefault()
אם המשתמש לוחץ על הלחצנים 'הקודם' או 'הבא' בדפדפן שלו. אסור שתהיה לכם אפשרות לפתות את המשתמשים להישאר באתר.
(הנושא הזה נדון ב-GitHub).
גם אם אי אפשר לעצור או ליירט את הניווט עצמו, האירוע "navigate"
עדיין יופעל.
הוא מידעי, כך שהקוד יכול, למשל, לתעד אירוע ב-Analytics כדי לציין שמשתמש עוזב את האתר.
למה כדאי להוסיף אירוע נוסף לפלטפורמה?
רכיב מעקב אירועים מסוג "navigate"
מרכז את הטיפול בשינויים בכתובות URL בתוך אפליקציית SPA.
קשה לעשות זאת באמצעות ממשקי API ישנים יותר.
אם כתבתם ניתוב ל-SPA משלכם באמצעות History API, יכול להיות שהוספתם קוד כמו זה:
function updatePage(event) {
event.preventDefault(); // we're handling this link
window.history.pushState(null, '', event.target.href);
// TODO: set up page based on new URL
}
const links = [...document.querySelectorAll('a[href]')];
links.forEach(link => link.addEventListener('click', updatePage));
זה בסדר, אבל לא מקיף. קישורים יכולים להופיע ולהיעלם בדף, והם לא הדרך היחידה שבה משתמשים יכולים לנווט בין דפים. לדוגמה, הם יכולים לשלוח טופס או אפילו להשתמש במפת תמונה. יכול להיות שהדף שלכם יטפל בבעיות האלה, אבל יש עוד הרבה אפשרויות שאפשר פשוט לפשט – וזה מה ש-Navigation API החדש עושה.
בנוסף, הקוד שלמעלה לא מטפל בניווט אחורה/קדימה. יש אירוע אחר לצורך כך, "popstate"
.
לדעתי, בדרך כלל נראה ש-History API יכול לעזור באפשרויות האלה.
עם זאת, יש לו רק שתי חזיתות: תגובה אם המשתמש לוחץ על 'הקודם' או 'הבא' בדפדפן, וכן דחיפת כתובות URL והחלפתן.
אין לו אנלוגיה ל-"navigate"
, אלא אם מגדירים באופן ידני מאזינים לאירועי קליק, לדוגמה, כפי שמוצג למעלה.
החלטה איך לטפל באירוע ניווט
השדה navigateEvent
מכיל הרבה מידע על הניווט, שבעזרתו תוכלו להחליט איך לטפל בניווט מסוים.
המאפיינים העיקריים הם:
canIntercept
- אם הערך הוא false, אי אפשר ליירט את הניווט. אי אפשר ליירט ניווטים בין מקורות שונים ותנועה בין מסמכים שונים.
destination.url
- זהו כנראה המידע החשוב ביותר שצריך להביא בחשבון כשמפעילים את הניווט.
hashChange
- ערך TRUE אם הניווט הוא באותו מסמך, וה-hash הוא החלק היחיד בכתובת ה-URL ששונה מכתובת ה-URL הנוכחית.
ב-SPAs מודרניים, ה-hash צריך לשמש לקישור לחלקים שונים של המסמך הנוכחי. לכן, אם הערך של
hashChange
הוא true, סביר להניח שאין צורך ליירט את הניווט הזה. downloadRequest
- אם הערך הזה נכון, הניווט הופעל על ידי קישור עם מאפיין
download
. ברוב המקרים אין צורך ליירט את הבקשה הזו. formData
- אם הערך לא null, הפנייה הזו היא חלק משליחת טופס POST.
חשוב להביא את זה בחשבון כשמפעילים את הניווט.
אם אתם רוצים לטפל רק בפעולות ניווט מסוג GET, הימנעו מאינטראקציה עם פעולות ניווט שבהן הערך של
formData
הוא לא null. בהמשך המאמר מופיעה דוגמה לטיפול בשליחת טפסים. navigationType
- הערך יכול להיות
"reload"
,"push"
,"replace"
או"traverse"
. אם הערך הוא"traverse"
, לא ניתן לבטל את הניווט הזה באמצעותpreventDefault()
.
לדוגמה, הפונקציה shouldNotIntercept
שנעשה בה שימוש בדוגמה הראשונה עשויה להיראות כך:
function shouldNotIntercept(navigationEvent) {
return (
!navigationEvent.canIntercept ||
// If this is just a hashChange,
// just let the browser handle scrolling to the content.
navigationEvent.hashChange ||
// If this is a download,
// let the browser perform the download.
navigationEvent.downloadRequest ||
// If this is a form submission,
// let that go to the server.
navigationEvent.formData
);
}
חסימת נתונים
כשהקוד קורא ל-intercept({ handler })
מתוך המאזין "navigate"
שלו, הוא מודיע לדפדפן שהוא מכין עכשיו את הדף למצב החדש המעודכן, ושיכול להיות שהניווט יימשך זמן מה.
הדפדפן מתחיל בתיעוד מיקום הגלילה של המצב הנוכחי, כדי שניתן יהיה לשחזר אותו מאוחר יותר (אופציונלי). לאחר מכן הוא קורא ל-callback של handler
.
אם הפונקציה handler
מחזירה הבטחה (מה שקורה באופן אוטומטי בפונקציות אסינכררוניות), ההבטחה הזו מספרת לדפדפן כמה זמן נמשכת הניווט ואם הוא הצליח.
navigation.addEventListener('navigate', navigateEvent => {
if (shouldNotIntercept(navigateEvent)) return;
const url = new URL(navigateEvent.destination.url);
if (url.pathname.startsWith('/articles/')) {
navigateEvent.intercept({
async handler() {
const articleContent = await getArticleContent(url.pathname);
renderArticlePage(articleContent);
},
});
}
});
לכן, ה-API הזה מציג מושג סמנטי שהדפדפן מבין: כרגע מתרחשת ניווט ב-SPA, לאורך זמן, שמשנה את המסמך מכתובת URL וממצב קודמים למצב ולכתובת URL חדשים. לכך יש כמה יתרונות פוטנציאליים, כולל נגישות: הדפדפנים יכולים להציג את ההתחלה, הסיום או הכישלון הפוטנציאלי של ניווט. לדוגמה, ב-Chrome מופעל אינדיקטור הטעינה המקורי ומאפשר למשתמש ליצור אינטראקציה עם לחצן ההפסקה. (הבעיה הזו לא מתרחשת כרגע כשהמשתמש מנווט באמצעות הלחצנים 'הקודם'/'הבא', אבל היא תתוקן בקרוב).
ביצוע ניווט
כשמנתבים ניווטים, כתובת ה-URL החדשה תיכנס לתוקף ממש לפני הקריאה החוזרת (callback) של handler
.
אם לא מעדכנים את ה-DOM באופן מיידי, נוצרת תקופה שבה התוכן הישן מוצג לצד כתובת ה-URL החדשה.
השינוי הזה משפיע על דברים כמו פתרון כתובות URL יחסיות בזמן אחזור נתונים או טעינת משאבי משנה חדשים.
ב-GitHub מתנהל דיון על דרך לעכב את שינוי כתובת ה-URL, אבל בדרך כלל מומלץ לעדכן את הדף באופן מיידי באמצעות סוג כלשהו של placeholder לתוכן שיגיע:
navigation.addEventListener('navigate', navigateEvent => {
if (shouldNotIntercept(navigateEvent)) return;
const url = new URL(navigateEvent.destination.url);
if (url.pathname.startsWith('/articles/')) {
navigateEvent.intercept({
async handler() {
// The URL has already changed, so quickly show a placeholder.
renderArticlePagePlaceholder();
// Then fetch the real data.
const articleContent = await getArticleContent(url.pathname);
renderArticlePage(articleContent);
},
});
}
});
כך לא רק שמונעות בעיות בחירת כתובת ה-URL, אלא גם שהתגובה למשתמש נראית מהירה יותר כי היא מתקבלת באופן מיידי.
אותות ביטול
מכיוון שאפשר לבצע עבודה אסינכררונית במטפל intercept()
, יכול להיות שהניווט יהפוך ליותר מיותר.
המצב הזה מתרחש במקרים הבאים:
- המשתמש לוחץ על קישור אחר או שקוד מסוים מבצע ניווט אחר. במקרה כזה, הניווט הישן יוחלף בניווט החדש.
- המשתמש לוחץ על הלחצן 'עצירה' בדפדפן.
כדי לטפל בכל אחת מהאפשרויות האלה, האירוע שמוענק למאזין "navigate"
מכיל מאפיין signal
שהוא AbortSignal
.
מידע נוסף זמין במאמר אחזור שניתן לבטל.
בקצרה, הוא מספק אובייקט שמפעיל אירוע כשצריך להפסיק את העבודה.
חשוב לציין שאפשר להעביר את הערך AbortSignal
לכל קריאה שמבצעים ל-fetch()
, והערך הזה יבטל בקשות רשת בזמן פעילות אם הניווט יופסק.
כך תוכלו לחסוך את רוחב הפס של המשתמש ולדחות את הערך Promise
שהוחזר על ידי fetch()
, וכך למנוע מקוד שיבוא אחריו לבצע פעולות כמו עדכון ה-DOM כדי להציג ניווט לא חוקי בדף.
זוהי הדוגמה הקודמת, אבל עם getArticleContent
מוטמע, שמראה איך אפשר להשתמש ב-AbortSignal
עם fetch()
:
navigation.addEventListener('navigate', navigateEvent => {
if (shouldNotIntercept(navigateEvent)) return;
const url = new URL(navigateEvent.destination.url);
if (url.pathname.startsWith('/articles/')) {
navigateEvent.intercept({
async handler() {
// The URL has already changed, so quickly show a placeholder.
renderArticlePagePlaceholder();
// Then fetch the real data.
const articleContentURL = new URL(
'/get-article-content',
location.href
);
articleContentURL.searchParams.set('path', url.pathname);
const response = await fetch(articleContentURL, {
signal: navigateEvent.signal,
});
const articleContent = await response.json();
renderArticlePage(articleContent);
},
});
}
});
טיפול בגלילה
כשintercept()
אתם ניווט, הדפדפן ינסה לטפל בגלילה באופן אוטומטי.
בתרחישים של ניווט לרשומה חדשה בהיסטוריה (כשהערך של navigationEvent.navigationType
הוא "push"
או "replace"
), המשמעות היא ניסיון לגלול לחלק שמצוין על ידי קטע כתובת ה-URL (החלק אחרי #
), או איפוס הגלילה לחלק העליון של הדף.
בטעינות מחדש ובמעבר בין רשומות, המשמעות היא שחזור מיקום הגלילה למיקום שבו הוא היה בפעם האחרונה שבה הרשומה הזו בהיסטוריה הוצגה.
כברירת מחדל, זה קורה אחרי שההבטחה שחוזרת מה-handler
מתקבלת, אבל אם הגיוני לגלול אחורה, אפשר לבצע קריאה ל-navigateEvent.scroll()
:
navigation.addEventListener('navigate', navigateEvent => {
if (shouldNotIntercept(navigateEvent)) return;
const url = new URL(navigateEvent.destination.url);
if (url.pathname.startsWith('/articles/')) {
navigateEvent.intercept({
async handler() {
const articleContent = await getArticleContent(url.pathname);
renderArticlePage(articleContent);
navigateEvent.scroll();
const secondaryContent = await getSecondaryContent(url.pathname);
addSecondaryContent(secondaryContent);
},
});
}
});
לחלופין, אפשר לבטל את הטיפול האוטומטי בגלילה לגמרי על ידי הגדרת האפשרות scroll
של intercept()
לערך "manual"
:
navigateEvent.intercept({
scroll: 'manual',
async handler() {
// …
},
});
טיפול במיקוד
אחרי שההבטחה שחוזרת מה-handler
תבוצע, הדפדפן יתמקד באלמנט הראשון שבו מוגדר המאפיין autofocus
, או באלמנט <body>
אם לאף אלמנט אין את המאפיין הזה.
כדי לבטל את ההתנהגות הזו, מגדירים את האפשרות focusReset
של intercept()
לערך "manual"
:
navigateEvent.intercept({
focusReset: 'manual',
async handler() {
// …
},
});
אירועי הצלחה וכישלון
כשמתבצעת קריאה למטפל intercept()
, מתרחשת אחת משתי האפשרויות הבאות:
- אם הערך המוחזר של
Promise
עומד בדרישות (או שלא התבצעה קריאה ל-intercept()
), Navigation API יפעיל את"navigatesuccess"
עםEvent
. - אם הערך המוחזר של
Promise
יהיה דחייה, ה-API יפעיל את"navigateerror"
עםErrorEvent
.
האירועים האלה מאפשרים לקוד לטפל בהצלחה או בכישלון באופן מרוכז. לדוגמה, אפשר להסתיר אינדיקטור התקדמות שמוצג בעבר, כך:
navigation.addEventListener('navigatesuccess', event => {
loadingIndicator.hidden = true;
});
לחלופין, אפשר להציג הודעת שגיאה במקרה של כשל:
navigation.addEventListener('navigateerror', event => {
loadingIndicator.hidden = true; // also hide indicator
showMessage(`Failed to load page: ${event.message}`);
});
מאזין האירועים "navigateerror"
, שמקבל ErrorEvent
, שימושי במיוחד כי מובטח שהוא יקבל את כל השגיאות מהקוד שמגדיר דף חדש.
אתם יכולים פשוט await fetch()
, בידיעה שאם הרשת לא זמינה, השגיאה תופנה בסופו של דבר אל "navigateerror"
.
רשומות ניווט
navigation.currentEntry
מאפשרת גישה לרשומה הנוכחית.
זהו אובייקט שמתאר את המיקום של המשתמש כרגע.
הרשומה הזו כוללת את כתובת ה-URL הנוכחית, מטא-נתונים שאפשר להשתמש בהם כדי לזהות את הרשומה הזו לאורך זמן ומצב שסופק על ידי המפתח.
המטא-נתונים כוללים את key
, מאפיין מחרוזת ייחודי של כל רשומה שמייצג את הרשומה הנוכחית ואת החריץ שלה.
המפתח הזה נשאר ללא שינוי גם אם כתובת ה-URL או המצב של הרשומה הנוכחית משתנים.
הוא עדיין נמצא באותו חריץ.
לעומת זאת, אם משתמש לוחץ על 'הקודם' ואז פותח מחדש את אותו הדף, הערך של key
ישתנה כי הרשומה החדשה הזו יוצרת חריץ חדש.
למפתחים, key
שימושי כי Navigation API מאפשר לנווט את המשתמש ישירות לרשומה עם מפתח תואם.
אפשר להחזיק בו, גם במצבים של רשומות אחרות, כדי לעבור בקלות בין דפים.
// On JS startup, get the key of the first loaded page
// so the user can always go back there.
const {key} = navigation.currentEntry;
backToHomeButton.onclick = () => navigation.traverseTo(key);
// Navigate away, but the button will always work.
await navigation.navigate('/another_url').finished;
מדינה
Navigation API מציג את המושג 'מצב', שהוא מידע שמפתחים מספקים ושנשמר באופן קבוע ברשומה הנוכחית בהיסטוריה, אבל לא גלוי ישירות למשתמש.
הפונקציה הזו דומה מאוד לפונקציה history.state
ב-History API, אבל היא משופרת.
ב-Navigation API, אפשר להפעיל את השיטה .getState()
של הרשומה הנוכחית (או של כל רשומה אחרת) כדי להחזיר עותק של המצב שלה:
console.log(navigation.currentEntry.getState());
כברירת מחדל, הערך יהיה undefined
.
מצב ההגדרה
אפשר לשנות אובייקטים של מצב, אבל השינויים האלה לא נשמרים ברשומה בהיסטוריה, ולכן:
const state = navigation.currentEntry.getState();
console.log(state.count); // 1
state.count++;
console.log(state.count); // 2
// But:
console.info(navigation.currentEntry.getState().count); // will still be 1
הדרך הנכונה להגדיר מצב היא במהלך הניווט בסקריפט:
navigation.navigate(url, {state: newState});
// Or:
navigation.reload({state: newState});
כאשר newState
יכול להיות כל אובייקט שניתן לשכפול.
אם רוצים לעדכן את המצב של הרשומה הנוכחית, עדיף לבצע ניווט שמחליף את הרשומה הנוכחית:
navigation.navigate(location.href, {state: newState, history: 'replace'});
לאחר מכן, ה-event listener של "navigate"
יוכל לזהות את השינוי הזה דרך navigateEvent.destination
:
navigation.addEventListener('navigate', navigateEvent => {
console.log(navigateEvent.destination.getState());
});
עדכון מצב באופן סינכרוני
באופן כללי, עדיף לעדכן את המצב באופן אסינכרוני באמצעות navigation.reload({state: newState})
, ואז המאזין "navigate"
יוכל להחיל את המצב הזה. עם זאת, לפעמים שינוי המצב כבר יושם במלואו עד שהקוד יקבל על כך הודעה, למשל כשהמשתמש מפעיל או משבית רכיב <details>
או כשהמשתמש משנה את המצב של קלט בטופס. במקרים כאלה, כדאי לעדכן את המצב כדי שהשינויים האלה יישמרו במהלך טעינות מחדש וטרaversals. אפשר לעשות זאת באמצעות updateCurrentEntry()
:
navigation.updateCurrentEntry({state: newState});
יש גם אירוע שבו אפשר לשמוע על השינוי הזה:
navigation.addEventListener('currententrychange', () => {
console.log(navigation.currentEntry.getState());
});
עם זאת, אם אתם מגיבים לשינויים במצב ב-"currententrychange"
, יכול להיות שאתם מפצלים או אפילו מכפילים את הקוד לטיפול במצב בין האירוע "navigate"
לבין האירוע "currententrychange"
, בעוד ש-navigation.reload({state: newState})
מאפשר לכם לטפל בכך במקום אחד.
מצב לעומת פרמטרים של כתובת URL
מכיוון שמצב יכול להיות אובייקט מובנה, קל להתפתות להשתמש בו לכל מצב האפליקציה. עם זאת, במקרים רבים עדיף לאחסן את המצב הזה בכתובת ה-URL.
אם אתם מצפים שהמצב יישמר כשהמשתמש ישתף את כתובת ה-URL עם משתמש אחר, כדאי לאחסן אותו בכתובת ה-URL. אחרת, אובייקט המצב הוא האפשרות הטובה יותר.
גישה לכל הרשומות
אבל 'הרשומה הנוכחית' היא לא הכול.
ה-API מספק גם דרך לגשת לכל רשימת הרשומות שהמשתמש עבר עליהן במהלך השימוש באתר, באמצעות הקריאה navigation.entries()
, שמחזירה מערך של קובץ snapshot של הרשומות.
אפשר להשתמש בנתונים האלה, למשל, כדי להציג ממשק משתמש שונה על סמך האופן שבו המשתמש ניווט לדף מסוים, או פשוט כדי לחזור לכתובות ה-URL הקודמות או למצבים שלהן.
אי אפשר לעשות זאת באמצעות History API הנוכחי.
אפשר גם להאזין לאירוע "dispose"
ב-NavigationHistoryEntry
מסוימים. האירוע הזה מופעל כשהרשומה כבר לא חלק מהיסטוריית הגלישה. זה יכול לקרות כחלק מניקוי כללי, אבל גם במהלך ניווט. לדוגמה, אם חוזרים 10 מקומות אחורה ואז מנווטים קדימה, 10 רשומות ההיסטוריה האלה יוסרו.
דוגמאות
האירוע "navigate"
מופעל בכל סוגי הניווט, כפי שצוין למעלה.
(יש למעשה נספח ארוך במפרט של כל הסוגים האפשריים).
באתרים רבים, התרחיש הנפוץ ביותר הוא כאשר המשתמש לוחץ על <a href="...">
, אבל יש שני סוגים בולטים ומורכבים יותר של ניווט שחשוב להתייחס אליהם.
ניווט פרוגרמטי
האפשרות הראשונה היא ניווט פרוגרמטי, שבו הניווט מושפע מקריאה לשיטה בקוד בצד הלקוח.
אפשר להפעיל את navigation.navigate('/another_page')
מכל מקום בקוד כדי לגרום לניווט.
האירוע יטפל על ידי מאזין האירועים המרכזי שרשום ב-listener "navigate"
, והמאזין המרכזי יקרא לסנכרון.
האפשרות הזו מיועדת לצבירה משופרת של שיטות ישנות יותר כמו location.assign()
ו-friends, וגם של השיטות pushState()
ו-replaceState()
של History API.
השיטה navigation.navigate()
מחזירה אובייקט שמכיל שני עותקים של Promise
ב-{ committed, finished }
.
כך מבצע ההפעלה יכול להמתין עד שהמעבר 'אושר' (כתובת ה-URL הגלויה השתנתה ויש NavigationHistoryEntry
חדש זמין) או עד שהוא 'הסתיים' (כל ההתחייבויות שהוחזרו על ידי intercept({ handler })
הושלמו – או נדחו, בגלל כשל או עקב ניווט אחר שהקדים אותו).
לשיטה navigate
יש גם אובייקט אפשרויות שבו אפשר להגדיר:
state
: המצב של הרשומה החדשה בהיסטוריה, כפי שהוא זמין באמצעות method.getState()
ב-NavigationHistoryEntry
.history
: אפשר להגדיר את הערך"replace"
כדי להחליף את הרשומה הנוכחית בהיסטוריה.info
: אובייקט להעברה לאירוע הניווט דרךnavigateEvent.info
.
באופן ספציפי, אפשר להשתמש ב-info
כדי לציין, למשל, אנימציה מסוימת שגורמת להצגת הדף הבא.
(החלופה יכולה להיות הגדרת משתנה גלובלי או הכללה שלו כחלק מה-#hash. שתי האפשרויות קצת לא נוחות).
חשוב לציין שהאירוע info
לא יופעל מחדש אם המשתמש יבצע ניווט מאוחר יותר, למשל באמצעות הלחצנים 'הקודם' ו'הבא'.
למעשה, במקרים כאלה הערך תמיד יהיה undefined
.
ל-navigation
יש גם כמה שיטות ניווט אחרות, שכולן מחזירות אובייקט שמכיל את { committed, finished }
.
כבר הזכרתי את traverseTo()
(שיכול לקבל key
שמציין רשומה ספציפית בהיסטוריה של המשתמש) ואת navigate()
.
הוא כולל גם את back()
, forward()
ו-reload()
.
כל השיטות האלה מטופלות – בדיוק כמו navigate()
– על ידי מאזין האירועים המרכזי "navigate"
.
שליחת טפסים
שנית, שליחת <form>
HTML באמצעות POST היא סוג מיוחד של ניווט, ו-Navigation API יכול ליירט אותה.
הבקשה הזו כוללת מטען ייעודי (Payload) נוסף, אבל עדיין המאזין של "navigate"
מטפל בניווט באופן מרכזי.
כדי לזהות שליחת טופס, מחפשים את המאפיין formData
ב-NavigateEvent
.
הנה דוגמה שמראה איך להפוך כל שליחת טופס לשליחה שמשאירה את המשתמש בדף הנוכחי באמצעות fetch()
:
navigation.addEventListener('navigate', navigateEvent => {
if (navigateEvent.formData && navigateEvent.canIntercept) {
// User submitted a POST form to a same-domain URL
// (If canIntercept is false, the event is just informative:
// you can't intercept this request, although you could
// likely still call .preventDefault() to stop it completely).
navigateEvent.intercept({
// Since we don't update the DOM in this navigation,
// don't allow focus or scrolling to reset:
focusReset: 'manual',
scroll: 'manual',
handler() {
await fetch(navigateEvent.destination.url, {
method: 'POST',
body: navigateEvent.formData,
});
// You could navigate again with {history: 'replace'} to change the URL here,
// which might indicate "done"
},
});
}
});
מה חסר?
למרות האופי המרכזי של מאזין האירועים "navigate"
, המפרט הנוכחי של Navigation API לא מפעיל את "navigate"
בטעינה הראשונה של הדף.
באתרים שמשתמשים בעיבוד בצד השרת (SSR) בכל המצבים, יכול להיות שזה בסדר – השרת יכול להחזיר את המצב הראשוני הנכון, וזו הדרך המהירה ביותר להעביר תוכן למשתמשים.
עם זאת, באתרים שמשתמשים בקוד מצד הלקוח כדי ליצור את הדפים שלהם, יכול להיות שיהיה צורך ליצור פונקציה נוספת כדי לאתחל את הדף.
בחירה מודעת נוספת בתכנון של Navigation API היא שהוא פועל רק בתוך מסגרת אחת – כלומר הדף ברמה העליונה, או <iframe>
ספציפי אחד.
לכך יש כמה השלכות מעניינות שמתועדות בהרחבה במפרט, אבל בפועל, הדבר יפחית את הבלבול בקרב המפתחים.
בממשק History API הקודם יש כמה מקרים קיצוניים מבלבלים, כמו תמיכה בפריימים, וממשק Navigation API החדש מטפל במקרים הקיצוניים האלה כבר מההתחלה.
לבסוף, עדיין אין הסכמה לגבי שינוי או סידור מחדש של רשימת הרשומות שהמשתמש עבר עליהן באופן פרוגרמטי. הנושא הזה נמצא כרגע בבדיקה, אבל אפשרות אחת היא לאפשר רק מחיקה: של רשומות היסטוריות או של 'כל הרשומות העתידיות'. האפשרות השנייה תאפשר מצב זמני. לדוגמה, כמפתח/ת, אוכל:
- לשאול את המשתמש שאלה על ידי מעבר לכתובת URL או למצב חדשים
- מאפשרים למשתמש להשלים את העבודה (או לחזור אחורה)
- הסרת רשומה בהיסטוריה לאחר השלמת משימה
האפשרות הזו יכולה להתאים במיוחד למודעות מעברון או למודעות מעברון זמניות: משתמשים יכולים להשתמש בתנועת החזרה אחורה כדי לצאת מכתובת ה-URL החדשה, אבל הם לא יכולים לעבור בטעות ל'הבא' כדי לפתוח אותה שוב (כי הרשומה הוסרה). אי אפשר לעשות זאת באמצעות History API הנוכחי.
התנסות ב-Navigation API
Navigation API זמין ב-Chrome 102 בלי דגלים. אפשר גם לנסות הדגמה של Domenic Denicola.
History API הקלאסי נראה פשוט, אבל הוא לא מוגדר בצורה טובה ויש בו מספר רב של בעיות שקשורות לתרחישי קצה ולאופן שבו הוא מוטמע בצורה שונה בדפדפנים שונים. נשמח לקבל ממך משוב על Navigation API החדש.
קובצי עזר
- WICG/navigation-api
- העמדה של Mozilla בנושא תקנים
- Intent To Prototype
- בדיקת התג
- הרשומה ב-Chromestatus
תודות
תודה ל-Thomas Steiner, ל-Domenic Denicola ול-Nate Chapin על בדיקת הפוסט הזה.