En enkel introduktion till History API i JavaScript
History API är en av de JavaScript-tekniker som varje webbutvecklare behöver veta kallt. Utan det kommer ett enda klick på bakåtknappen att hoppa rakt ut ur din ansökan. Du kommer att förlora något pågående arbete, och användaren kommer att tvingas börja om igen.
lyckligtvis finns det en enkel lösning. Ända sedan formaliseringen av HTML5 (och lanseringen av Internet Explorer 10), har vi haft en historia API som låter dig lägga till historiklistan och reagera på bakåt och framåt navigering. I den här artikeln lär du dig hur det fungerar — och du kommer att prova det med ett levande exempel.
men innan vi går vidare, Låt oss ta en närmare titt på problemet.
i gamla dagar på webben gjorde en webbsida en sak-visa saker. När du ville titta på något annat klickade du på en länk och gick till en ny URL.
När JavaScript blev kraftfullare insåg utvecklare att varje webbsida kunde vara en komplett applikation i sig. En webbsida kan konkurrera med en stationär applikation! Problemet var att knapparna bakåt och framåt i webbläsaren inte passade den nya applikationsmodellen. Till exempel kan en användare utföra en sekvens av uppgifter i en webbapplikation på en sida och sedan förvänta sig att använda bakåtknappen för att gå tillbaka ett enda steg. Istället skulle bakåtknappen återgå till föregående webbsida och effektivt stänga av JavaScript-appen mitt i vad den gjorde.
ofta var detta beteende inte intuitivt. Tänk till exempel på ett program som använder objektet XMLHttpRequest
för att hämta ny data och uppdatera sidan. För användaren kan det se ut som om de reser till en ny sida. Men om de gör misstaget att använda bakåtknappen, är illusionen krossad och allt går i sidled.
låt oss ta en närmare titt. Här är ett exempel som låter dig gå igenom Dictionnaire Infernal, en berömd (och nu offentlig domän) bok från 1818 som beskriver konstiga djur från overtro och mytologi. Tricket är att föregående och nästa länkar inte tar dig till en annan sida. Istället utlöser de ett asynkront samtal via XMLHttpRequest
som får informationen för nästa bild. Sedan uppdaterar JavaScript-koden bildrutan utan att störa innehållet på resten av sidan.
Här är en live-version av exemplet, komplett med Historik API-stöd. Även om allt sker på en enda webbsida kan du använda knapparna framåt och bakåt för att sömlöst gå från en bild till nästa. Du kan till och med använda Reload — knappen (laddar om den aktuella bilden istället för att starta om hela appen) och du kan placera bokmärken som håller det aktuella läget-med andra ord leder de dig direkt tillbaka till bilden som du tittar på.
och här är en gammal skolversion av exemplet som inte använder History API. Det spelar ingen roll hur långt du är i sidoshowen — när du använder bakåtknappen hoppar du tillbaka till föregående webbsida. Knapparna framåt och ladda om är lika trasiga. Bokmärken startar om appen från början.
Du kan ladda ner all källkod här för att spela med. (Det fungerar dock inte lokalt, eftersom det använder objektet XMLHttpRequest
för att göra webbförfrågningar.) I resten av den här artikeln kommer jag att bryta ner hur allt fungerar.
förbereda exemplet
innan vi kommer in i History API är det viktigt att ha en grundläggande förståelse för hur detta exempel fungerar. Det är allt ganska enkelt.
sidan använder ett nummer för att hålla reda på bilden som den för närvarande visar. När du klickar på Nästa eller föregående ändrar koden det aktuella bildnumret. Sedan använder den sin egen goToSlide()
– funktion för att uppdatera sidan.
funktionengoToSlide()
är där det verkliga arbetet äger rum. Det startar en asykron begäran med den ökändaXMLHttpRequest
objekt.
det är upp till dig att bestämma hur bildinnehållet genereras och skickas tillbaka till anropskoden på sidan. Vanligtvis har du någon form av routingramverk som utlöser rätt kod på serversidan. Den serversidan kan sedan ta tag i bildinnehållet från en cache, en databas, en fil eller till och med ett hårdkodat block av markup.
i det här exemplet har jag hållit saker så enkla som möjligt. Begäran tar tag i HTML-markeringen från en statisk fil. Så om du begär den tredje bilden (med en relativ URL som Slides/3) får du ett utdrag av HTML-markering för just den bilden. Lugn!
När begäran är klar utlöser webbläsarennewSlideReceived()
händelsehanterare. Då handlar det helt enkelt om att ta den mottagna markeringen och infoga den på sidan med en platshållare <div>
.
det är allt du behöver veta för att bygga den naiva versionen av den här sidan, som inte stöder webbläsarhistorik.
historikobjektet
history
objektet har funnits nästan sedan början av JavaScript. Men för mycket av sitt liv var det mycket mindre kraftfullt (och mycket mindre användbart) än det är idag.
det ursprungligahistory
objektet har bara en egenskap och tre grundläggande metoder. Egenskapen är length
, och den berättar hur många poster som finns i webbläsarens historiklista:
alert("You have " + history.length + " pages in the history.");
denna detalj är mestadels värdelös.
de tre ursprungliga history
metoderna är back()
(skickar en besökare ett steg tillbaka i webbhistoriken), forward()
(skickar en besökare ett steg framåt) och go()
(tar en numerisk förskjutning som talar om för webbläsaren hur många steg som ska gå framåt eller bakåt). Allt detta lägger till relativt lite, om du inte vill designa dina egna anpassade bakåt-och framåtknappar på en webbsida.
HistoryAPI lägger till två mycket mer användbara ingredienser:pushState()
– metoden ochonPopState
– händelsen.
lägga till en post i historiklistan
pushState()
metoden är mittpunkten i History API. Det låter dig lägga till ett nytt objekt i historiklistan för den aktuella sidan. Så här ser det ut:
vid denna tidpunkt kanske du undrar — vad är meningen med att ha mer än en post för samma sida? Tricket är att varje post har något tillstånd kopplat till det — en länkad information eller ett objekt som du ställer in. När användaren går tillbaka genom historiklistan får du motsvarande tillståndsinformation så att du kan återställa sidan till dess tidigare version.
pushState()
metoden tar tre argument:
- Data. Det första argumentet är alla data som du vill lagra för att representera det aktuella läget på den här sidan. Du använder den här informationen för att återställa sidan när användaren går tillbaka genom historiklistan.
- Titel. Det andra argumentet är sidtiteln du vill att webbläsaren ska visa. För närvarande är alla webbläsare förenade med att ignorera denna detalj. (Så du kan bara skicka
null
.) - URL. Det tredje argumentet är den nya versionen av webbadressen som du vill visa för sidan i webbläsarens adressfält. Detta låter dig lägga till bättre stöd för Reload-knappen och webbläsarens bokmärken.
i Dictionnaire Infernal slideshow-exemplet är det enkelt att lägga till historikstöd allt du behöver för att hålla reda på är bildnumret. Du lägger till i historiklistan varje gång bilden ändras.
här är koden du behöver lägga till:
history.pushState(slideNumber, null, null);
Du kommer att märka att den här koden inte ändrar sidtiteln med det andra argumentet (eftersom det inte fungerar ändå) och ändrar inte webbadressen (du ser hur du gör det på ett ögonblick). Allt det gör är att lagra bildnumret.
för hela bilden, här är de två samtalen till history.pushState()
på rätt plats-händelsehanterare för nästa och tidigare länkar:
återgå till en tidigare sida
När du använder pushState()
– metoden måste du också tänka på dess naturliga motsvarighet, onPopState
– händelsen. MedanpushState()
metoden sätter en ny post i webbläsarens historiklista,onPopState
händelse ger dig chansen att hantera det när användaren återvänder.
för att förstå hur detta fungerar, överväga vad som händer om en besökare går igenom alla bilder i Dictionnaire Infernal example. När varje ny bild laddas upp lägger sidan till en historikpost. Men om användaren går tillbaka med bakåtknappen händer ingenting.
för att åtgärda denna brist måste du hanteraonPopState
händelse, som utlöses efter varje historiknavigering. (Detta inkluderar om du använder en av historikmetoderna, som history.back()
, samt när användaren klickar på webbläsarens navigeringsknappar.)
onPopState
– händelsen ger din kod den tillståndsinformation du lagrade tidigare medpushState()
. I det här fallet är det bara bildnumret, som du kan ta tag i från egenskapen state
för händelseargumentet, så här:
var slideNumber = e.State;
ditt jobb är att använda tillståndsinformationen för att återställa rätt version av sidan. I det aktuella exemplet betyder det att du laddar motsvarande bild genom att anropa funktionen handy goToSlide()
som hanterar all bildnavigering. Den fullständiga koden är kort och enkel:
Observera att du måste kontrollera om e.State
är null
, vilket händer i vissa webbläsare när sidan laddas för första gången och det inte finns något tillstånd sparat.
ändra URL
den aktuella versionen av detta exempel stöder historiklistan men spelar inte bra med Reload-knappen. Om du till exempel klickar igenom till den tredje bilden och sedan uppdaterar sidan kommer du att hamna tillbaka i början. Du befinner dig i samma situation om du försöker bokmärka en av bilderna — återgå till bokmärket och du börjar tillbaka på den första bilden.
lösningen på dessa problem är att använda History API för att ändra webbadressen. Men det finns en viktig anledning till att jag har lämnat detta steg till sist. Ofta kan exempel som använder historia API ändra webbadressen men inte använda den nya webbadressen. Detta kan leda till en webbapp som går sönder när användaren uppdaterar den (med ett fult 404-fel) eller går till en liknande men något annorlunda version av sidan. Om du inte är säker på hur du hanterar olika webbadresser är det ingen skam att använda det förenklade tillvägagångssättet från föregående exempel.
så låt oss säga att du är redo att dyka in, göra bättre webbadresser, och spela fint med bokmärken och webbläsare uppdateras. Det finns två grundläggande tillvägagångssätt du kan ta:
- ändra URL-sidans namn helt och hållet. I Dictionnaire Infernal-exemplet kan du ändra webbadressen till Slide1.html, Slide2.html, och så vidare som användaren rör sig genom bilderna. Fångsten är att du antingen måste ha faktiska sidor med dessa namn (gör inte det, det är besvärligt), eller du måste ställa in routing så att när din webbserver får en begäran om Slide2.html den kör kod som skapar rätt version av sidan.
- behåll samma sidnamn men lägg till information i frågesträngen. Detta är en enklare, mindre skala strategi. När användaren reser genom bildspelet kommer du att sluta med webbadresser som bildspel.html?slide=1, Sedan bildspel.html?slide = 2, bildspel.html?slide=3, och så vidare. Fördelen med detta tillvägagångssätt är att det är trivialt lätt att skriva en liten bit JavaScript som kontrollerar frågesträngen och ser till att du är på rätt bild när sidan laddas om. Du behöver ingen serverkod för att göra detta arbete — en sida kan göra allt.
här är en modifierad version av pushState()
ring du såg tidigare som använder den andra metoden. Den här koden fyller bildnumret i slutet av webbadressen:
history.pushState(slide, null, "Dictionnaire.html?slide=" + slide);
och här är koden som körs när sidan laddas om. Den söker efter bildinformationen i webbadressen och — om den hittar ett korrekt nummer — anropar funktionen goToSlide()
för att ladda upp bilden.
nu förlorar du aldrig din plats i bildspelet. Du kan prova den fullständiga versionen av det här exemplet eller ladda ner allt här.
slutliga detaljer
historia API är lätt att använda, men det gör det också enkelt att skapa större problem som du löser. Nu när du vet hur allt passar ihop, här är några sista råd:
- Om du trycker, måste du pop.
pushState()
metod ochonPopState
händelse är partners. Vad du lägger in med en, du kommer tillbaka med den andra. - ändra inte webbadressen om du inte är redo att hantera den. Det är trevligt att ändra din URL för att återspegla din föränderliga sida, men du måste kunna hantera förfrågningar för varje URL du använder. Om din webbadress pekar på en sida som inte finns, kommer din ansökan att bryta på uppdateringar och bokmärken.
- Använd ett anpassat tillståndsobjekt för att lagra mer information. Du behöver inte hålla fast vid ett enkelt nummer. Du kan sätta ett helt anpassat objekt i tillstånd, som kan samla all information du behöver för en komplex uppgift.