Verkstedet

Dette nettstedet tilhører E-tjenesten SA – et lite programvareverksted som lager verktøy for samarbeid, diskusjon og dugnad på Internett. Her skriver vi om webutvikling, interaksjonsdesign og forskjellige ting vi liker.

Gjør ditt nettsted raskere med færre HTTP-forespørsler

Forrige uke studerte vi hvordan mange HTTP-forespørsler etter hverandre kan gjøre ditt nettsted tregere enn det behøver å være, og hva du kan gjøre for å unngå dette.

Minst like viktig er det å holde det totale antallet bilder, skript og stilsett på nettstedet nede.

HTTP

Du skjønner, både nettleseren din og tjenermaskinen i den andre enden har en begrensning på hvor mange forskjellige filer de vil overføre samtidig. Vanligvis ligger denne grensen på 16 filer.

Dette betyr at om du har en side med 17 like store bilder, kan denne siden på en dårlig dag ta dobbelt så lang tid å laste som dersom den bare hadde 16. For om alle de 16 sporene på stasjonen allerede er i bruk, må det syttende toget finne seg i å vente til ett av de andre togene har reist videre.

Ikke bare stiller forespørslene seg i kø når det blir for mange av dem. Både nettleseren og tjeneren er glade i å fortelle alt mulig om seg selv hver gang de utveksler en fil. Uansett hvor liten filen er går de samme høflighetsfrasene igjen. Nettleseren pleier å begynne med noe sånt som dette:

GET /eksperimenter/data-uri/img/application_add.png HTTP/1.1
User-Agent: Opera/9.80 (X11; Linux i686; U; nb) Presto/2.5.24 Version/10.52
Host: e-tjenesten.org
Accept: text/html, application/xml;q=0.9, application/xhtml+xml, image/png,
image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1
Accept-Language: nb-NO,nb;q=0.9,en;q=0.8
Accept-Charset: iso-8859-1, utf-8, utf-16, *;q=0.1
Accept-Encoding: deflate, gzip, x-gzip, identity, *;q=0
Referer: http://e-tjenesten.org/eksperimenter/data-uri/img/
Cache-Control: no-cache
Connection: Keep-Alive, TE
TE: deflate, gzip, chunked, identity, trailers

Tjeneren kan ikke være noe dårligere, så den svarer omtrent som følger:

HTTP/1.1 200 OK
Date: Mon, 10 May 2010 22:36:07 GMT
Server: Apache/2.2.9 (Debian) mod_auth_kerb/5.3 DAV/2 PHP/5.2.6-1+lenny8 with Suhosin-Patch mod_python/3.3.1 Python/2.5.2 mod_ssl/2.2.9 OpenSSL/0.9.8g mod_perl/2.0.4 Perl/v5.10.0
Last-Modified: Wed, 31 Mar 2010 17:10:48 GMT
ETag: "688cca9-26b-4831bd3d2b600"
Accept-Ranges: bytes
Content-Length: 619
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
Content-Type: image/png

Deretter kommer selve filen. Underveis har altså det lille ikonet på bare 619 byte i praksis blitt tre ganger så stort, alle høflighetsfrasene inkludert. Det samme skjer for hver eneste lille fil.

Løsninger for å redusere antall forespørsler

Det er som du kanskje ser flere gode grunner til å holde antallet stilsett, skript og bilder så lavt som mulig. For skript og stilsett er ikke dette så komplisert. Å slå mange små skript eller stilsett sammen til ett stort er en enkel oppgave.

Men hva så med bilder?

Vi er stolte over at webapplikasjonene vi utvikler og drifter har et meget enkelt design, uten unødvendig dill som tar stor plass. Men vi har mange små ikoner på menyelementer, knapper og lister, og flere forskjellige bakgrunnsbilder. Det kan være flere titalls slike små elementer på en enkelt side, og selv om hver enkelt fil er veldig liten går sidelastingen tregere enn vi liker.

Er det mulig å redusere antallet forespørsler på noe vis, uten å måtte fjerne ikonene?

Mange jobber seg rundt problemet ved å legge bildene sine på ett subdomene, stilsettene på et andre og skriptene på et tredje. Da vil nettleseren åpne 16 tilkoblinger til hver av disse subdomenene, og laste ned langt flere filer samtidig. Dette er vel og bra når det gjør nettstedet raskere, men er ofte ikke nok.

Google setter sammen alle de grafiske elementene på sin søkeside til ett eneste bilde:

Riktig del av bildet vises så på riktig element ved hjelp av nøyaktig posisjonering av bakgrunnsbildet, slik at kun den relevante delen av bildet blir synlig for brukeren:

<span class="csb ch" style="background-position:-76px 0; margin-right:34px;width:66px">

Dette reduserer antallet HTTP-forespørsler betraktelig, men et slikt puslespill er kronglete å vedlikeholde og oppdatere. Kildekoden blir heller ikke spesielt leselig.

Finnes det flere måter å løse dette problemet på?

data URI

Ja, heldigvis. Si hei til data URI.

En data URI er en adresse som inneholder en fil. Det høres unektelig litt snodig ut, men er egentlig ganske enkelt. Se for eksempel på denne her:

data:text/plain;charset=utf-8,Jeg%20er%20en%20liten%20tekstfil.%20Kopier%20meg%20til%20adressefeltet%20ditt!

Slike adresser med filer i kan du bruke alle steder på en nettside der du kan henvise til eksterne ressurser – i lenker, stilsett eller bilder. Det betyr at du for eksempel kan bytte ut denne koden:

.ikon { background-image: url(ikon.png); }

… med denne:

.ikon { background-image: url(data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/ INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwA
AAFiSURBVBgZpcEhbpRRGIXh99x7IU0asGBJWEIdCLaAqcFiCArFCkjA0KRJF0EF
26kkFbVVdEj6/985zJ0wBjfp8ygJD6G3n358fP3m5NvtJscJYBObchEHx6QKJ6SK
snn6eLm7urr5/PP76cU4eXVy/ujouD074hDHd5s6By7GZknb3P7mU+WNLZGKn
x595JDvf96zTQSM92vRYA4lMEEO5RNraHWUDH3FV48f0K5mAYJk5pQQpqIgixa
E1JDKtRDd2OsYfJaTKNcTA2IBIIesMAOPdDUGYJSqGYml5lGHHYkSGhAJBBIkAo
WREAT3Z3JLqZhF3uS2EloQCQ8xLBxoAEWO7aZxros7EgISIIkwlZCY6s1OlAJT
WFal5VppMzUgbAlQcIkiT0DXSI2U2ymYZs9AWJL4n+df3pncsI0bn5dX344W
05dhctUFbapZcE2ToiLVHBMbGymS7aUhIdoPNBf7Jjw/gQ77u4AAAAASUVORK
5CYII=); }

Det ser kanskje ikke så pent ut, men for hver eksterne referanse du bytter ut på denne måten sparer du en HTTP-forespørsel. Om stilsettet refererer mange bilder kan dette utgjøre en stor forskjell.

For å omgjøre en fil til en data URI kan du bruke data URI kitchen eller en base64_encode()-funksjon i skriptspråket du benytter.

Blir det noe raskere, da?

For å teste om denne fremgangsmåten faktisk er noe raskere enn alternativet, har jeg laget en håndfull forskjellige tester. Disse finner du her.

I testene laster jeg henholdsvis 10, 20 og 100 forskjellige ikoner på to forskjellige måter. I det ene tilfellet på gamlemåten, der alle ikonene er eksterne filer. I det andre tilfellet er samtlige inkludert i stilsettet ved hjelp av data URI.

Hver av testene er kjørt ti ganger, og gjennomsnittlig lastetid fremgår i tabellen under. Testene er kjørt både på min noenlunde raske nettlinje hjemme, og via en tregere 3G-tilkobling.

17Mbit kabel 3G
Antall Flere filer Én fil Flere filer Én fil
10 153ms 105ms 1278ms 555ms
20 217ms 107ms 2342ms 1105ms
100 575ms 296ms 9514ms 2206ms

På den raskere linjen gir dette trikset en hastighetsøkning på mellom 46% og 102%. På den tregere linjen er forskjellen enda større: mellom 130% og 330%!

Jeg må innrømme at dette var langt over hva jeg hadde forventet.

Vil dette fungere på mitt nettsted?

Testene over er syntetiske og tester kun et veldig smalt bruksområde, så det finnes ingen garantier for at du kan få en like stor hastighetsøkning på ditt nettsted. Kanskje er det helt andre flaskehalser  som gjør seg gjeldende der. Jeg vil anbefale deg å kjøre dine egne tester og se hvor stor forskjell dette utgjør hos deg.

I vårt tilfelle har vi et digert stilsett som refererer over hundre forskjellige ikoner, men kun en brøkdel av disse er i bruk på hver enkelt side. For å teste dette scenariet laget jeg en test som refererer til 150 forskjellige ikoner i stilsettet, men kun bruker henholdsvis 10, 20 og 30 av dem.

Nettlesere er såpass intelligente at de ikke laster ned eksterne ressurser de ikke behøver, så her har eksterne bilder et fortrinn. Når du legger alt sammen til én pakke med data URI må du laste ned ned alt eller ingenting.

17Mbit kabel 3G
Antall Flere filer Én fil Flere filer Én fil
10 99ms 167ms 1221ms 2510ms
20 155ms 186ms 1910ms 2322ms
30 210ms 191ms 2881ms 2142ms

Som du ser av tabellen over lønner ikke data URI-trikset seg om du bruker en for liten andel av bildene du refererer i stilsettet. Bruker du kun 20 av 150 refererte ikoner går den optimaliserte versjonen tregere ved første sidelasting, mens ved 30 av 150 ikoner er resultatet positivt. Ved andre sidelasting, når stilsett og ikoner ligger mellomlagret i nettleseren, vil dog den optimaliserte versjonen alltid ha et fortrinn. Uansett bør vi nok rydde godt i stilsettet vårt.

Hvilke nettlesere fungerer dette i?

Data URI fungerer fint i alle moderne nettlesere, men ikke i Internet Explorer 6 og 7. Så lenge merkbar andel av våre potensielle kunder bruker disse eldre nettleserne kan vi selvsagt ikke ignorere dette.

Heldigvis kan vi gi et ekstra stilsett eldre versjoner av Internet Explorer ved hjelp av såkalte «conditional comments»:

<link rel='stylesheet' href='style.css'>
<!--[if lte IE 7]>
<link rel=’stylesheet’ href=’iesucks.css’>
<![endif]–>

I style.css har vi vårt ordinære stilsett med data URI. Dette brukes av alle nettlesere. De eldre nettleserne vil ikke forstå noe av data URI-referansene og vil ignorere disse. I iesucks.css, som kun vil lastes av Internet Explorer 6 og 7, overstyrer vi de opprinnelige deklarasjonene. Slik:

.ikon { background-image: url(ikon.png); }

Sidelasting vil gå noe tregere for brukere av disse antikke nettleserne, men det kan vi leve med. Det viktigste er at alt fungerer som normalt også for dem.

Fotografiet øverst er tatt av vitelone. Ikonene er fra famfamfam og Gnome-prosjektet.

Lagt ut 16. May, 23:58. 2 kommentarer »

Responstid og asynkrone JavaScript

De siste årene har bruk av asynkrone JavaScript fått stor utbredelse. I stedet for å laste en hel side på nytt hver gang du klikker på noe, kan et lite skript ta seg av kommunikasjonen med tjeneren og sømløst dytte inn nye data riktig sted på siden.

Dette er vel og bra når det gjør nettstedet raskere og enklere å bruke. Men det er dessverre ikke alltid tilfelle. Blendet av fordelene med dette verktøyet er det mange utviklere som benytter det også i tilfeller der en statisk side hadde fungert like fint. Ofte fører dette til at nettsider og webapplikasjoner blir tregere enn de behøver å være.

Test #1: Uten asynkrone JavaScript

La oss bruke følgende enkle side som et eksempel:

Denne siden består av en statisk HTML-side og to statiske bilder – totalt 1.5kB med data. Å laste denne siden på min datamaskin tar i gjennomsnitt 75 millisekunder, fordelt omtrent som følger:

HTML 50ms
Bilde 25ms
Bilde 25ms

Tid →

Nettleseren laster først ned HTML-dokumentet og tolker dette. Underveis oppdager nettleseren at det refereres til to forskjellige bilder, og laster ned disse også. Svoosj. Lynraskt.

Test #2: Med asynkrone JavaScript

La oss prøve én gang til, men la et JavaScript hente inn kommentarene på egenhånd når siden først er lastet. Hvor lang tid vil det ta?

HTML 50ms
JS 20ms
Data 36ms
Bilde 25ms
Bilde 25ms

Tid →

På min datamaskin tar dette i gjennomsnitt 131 millisekunder. Det er fremdeles ganske raskt, men er allikevel hele 75% lengre tid enn i det første eksempelet!

Hva er det egentlig som foregår?

Men hvordan kan forskjellen bli så stor av et så lite skript? Burde ikke den raske og fine nettlinjen min klare å laste ned denne lille siden like raskt som den andre?

Nei, det er dessverre ikke slik det fungerer. Selv om du kan overføre store mengder data per sekund over en rask nettlinje, behøver signalene fremdeles en gitt mengde tid for å fysisk komme seg fra A til B. Å sende et signal via Internett fra Oslo til Bergen og tilbake tar minst 15 millisekunder, uansett hvor lite data som følger med. Signalet skal jo fysisk forflytte seg gjennom tusen kilometer med kobber- og fiberkabel, og enn så lenge vi er begrenset av lysets hastighet må vi belage oss på litt venting.

Når nettleseren min må gjøre fire slike rundturer (HTML, JS, data, bilder) for å få tak i dataene den behøver for å kunne vise siden over, tar det naturligvis lengre tid enn når den bare må gjøre to (HTML, bilder).

Effekten blir gradvis større jo lenger unna du er fra datamaskinen du vil kommunisere med. Mens det bare tar 15 millisekunder å komme seg fra Oslo til Bergen og tilbake, tar tur-retur Tromsø 30 millisekunder, og skal du kommunisere med noen i California tar det hele 200 millisekunder! Så det som ser fint og raskt ut for deg kan være smertelig tregt for brukerne dine andre steder i verden.

Konklusjon

Hvor mange rundturer gjør brukerens nettleser når din nettside lastes? Om du gjør alt riktig bør det aldri være mer enn tre når siden først lastes, og to når du gjør asynkrone oppdateringer underveis. Om du kan redusere antall rundturer ytterligere vil det glede både brukerne dine og tjenermaskinen din.

Om en side inneholder data som skal oppdateres dynamisk og ofte, sørg for å laste ned en statisk versjon først. Så kan du oppdatere den dynamisk noen sekunder senere.

Kartet over er basert på dette, og kan fritt brukes av andre under en Creative Commons-lisens.

Lagt ut 9. May, 12:44. Ingen kommentarer »

<button>, border-radius og box-shadow

Knappene vi bruker i skjemaer på Bikube var inntil nylig ganske kjedelige, triste og ikke spesielt pene. Etter litt eksperimentering med border-radius og box-shadow ble de dog seende slik ut:

De blir runde og fine i Opera, Firefox, Chrome og Safari. I Internet Explorer forblir de firkantede, men de går like fint an å trykke på. Skyggen fungerer foreløpig bare i Opera og Firefox, men brukere av andre nettlesere klarer seg sikkert fint uten akkurat den kosmetiske detaljen.

En fungerende demonstrasjon av knappene med og uten ikoner finner du her:

Lån gjerne deler av CSSen vår.

Lagt ut 15. April, 21:50. Ingen kommentarer »

Stilsett for utskrift

Å skrive ut en avisartikkel på papir er ofte en smertefull prosess. Til tross for at artikkelen ser vakker ut på skjerm, fremstår det gjerne som at hele desken ved VG er byttet ut med fulle aper når den kommer ut av skriveren.

Artikkelen er sannsynligvis spredd utover dobbelt så mange sider som nødvendig, fulle av menyer, lenker og reklame som vanskelig kan klikkes på når de henger på veggen.

Det finnes flere måter å løse dette problemet på. Den vanligste er å plassere en lenke til en «utskriftsvennlig versjon» på et tilsynelatende tilfeldig valgt sted på siden. Det er dog ikke en spesielt god løsning. Den er tungvindt for vedkommende som lager nettsiden, forvirrende for søkemotorer som kan ende opp med å indeksere to nesten identiske sider. Og, viktigst av alt, irriterende for brukerne som ikke lenger kan bruke «Utskrift»-knappen i nettleseren sin.

Heldigvis finnes der en løsning som både er enklere og mer elegant. Fem linjer CSS er alt som skal til:

@media print {
   .reklame, .meny {
      display: none;
   }
}

CSS-spesifikasjonen definerer en rekke forskjellige medietyper, hvorav «print» er én. Med deklarasjonen @media print {} angir vi at den aktuelle blokken med kode skal ignoreres når dokumentet vises på skjerm, og kun ta effekt ved utskrift. I eksempelet over skjuler vi alle menyer og reklamebannere fra vårt hypotetiske dokument, slik at alt som gjenstår er selve artikkelteksten.

Dersom du ønsker det kan du selvsagt også angi mer leselige skriftstørrelser, farger med bedre kontrast, hensiktsmessige sidemarger eller gjøre andre justeringer for å gjøre utskriften mer leselig:

@media print {

   ...

   #artikkel {
      font-size: 14pt;
      color: black;
      background-color: white;
      width: auto;
      margin: 1% 2%;
      padding: 0;
      float: none;
   }
}

Du kan også putte utskrifts-reglene i et separat stilsett:

<link href='utskrift.css' rel='stylesheet' media='print'>

En demonstrasjon av hvordan disse tingene fungerer i praksis finner du her:

Mer informasjon om relevant CSS-magi finner du i spesifikasjonen:

Lagt ut 1. April, 16:47. 1 kommentar »