Prendendo spunto da questo thread del Forum GT (Sito veloce nonostante social plugin e javascript, come fare?) ho colto l’occasione per scrivere questo articolo, analizzando l’impatto negativo che hanno i social widgets sulle performance del nostro sito e sulle tecniche da adottare per evitarlo.
Oramai, quasi tutti i siti utilizzano social widgets che causano un rallentamento del caricamento della pagina.
Inoltre, tutti i widgets, essendo praticamente javascript possono essere causa di SPOF (illustrati da Andrea Pernici in Cosa sono i Single Point Of Failure).
Per di più, a loro volta, questi javascript richiedono a cascata altre risorse (altri javascript, css, immagini) che causano un aumento considerevole delle richieste (anche una cinquantina).
Velocizziamo i social widgets
Esistono diversi metodi applicabili affinché l’incidenza di questi widgets sia ridotta al minimo.
Ho creato delle pagine di esempio per analizzare le diverse tecniche ed ho utilizzato Speedoo per misurarne i tempi (il tool online, made in italy, per testare la velocità di un sito web targato GT).
Consideriamo i seguenti widgets:
- Facebook Like button
- Facebook comment box
- Twitter button
- Google+ button
Di seguito illustrerò tre tecniche diverse.
Senza Social widgets
Iniziamo analizzando una pagina web che non fa uso di widgets.
Pagina del test: Caricamento social widgets – Senza social widgets
Waterfall generato da Speedoo
Test Speedoo completo: https://www.giorgiotave.it/speedoo/result/130615_MS_4/
Caricamento Asincrono
Il passo più semplice per integrare i social widgets e per evitare gli SPOF è adottare il caricamento asincrono.
Di seguito i codici ufficiali per ogni widget:
Pulsante Mi Piace Facebook
<div id="fb-root"></div> <script> (function(d, s, id) { var js, fjs = d.getElementsByTagName(s)[0]; if (d.getElementById(id)) return; js = d.createElement(s); js.id = id; js.src = "//connect.facebook.net/it_IT/all.js#xfbml=1"; fjs.parentNode.insertBefore(js, fjs); }(document, 'script', 'facebook-jssdk'));</script> <div class="fb-like" data-href="https://www.andrea-cardinale.it/" data-send="false" data-width="450" data-show-faces="true" data-font="arial"></div>
Modulo commenti Facebook
<div id="fb-root"></div> <script>(function(d, s, id) { var js, fjs = d.getElementsByTagName(s)[0]; if (d.getElementById(id)) return; js = d.createElement(s); js.id = id; js.src = "//connect.facebook.net/it_IT/all.js#xfbml=1"; fjs.parentNode.insertBefore(js, fjs); }(document, 'script', 'facebook-jssdk'));</script> <div class="fb-comments" data-href="https://example.com" data-width="470" data-num-posts="10"></div>
Pulsante tweet di Twitter
<a href="https://twitter.com/share" class="twitter-share-button" data-via="CardinaleAndrea" data-lang="it" data-related="CardinaleAndrea" data-hashtags="perfmatters">Tweet</a> <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
Pulsante +1 di Google+
<script type="text/javascript" src="https://apis.google.com/js/plusone.js"></script> <g:plusone></g:plusone> <script type="text/javascript"> (function() { var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true; po.src = 'https://apis.google.com/js/plusone.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s); })(); </script> <g:plusone></g:plusone>
Pagina del test: Caricamento social widgets – Asincrono
Logicamente, nel caso di più widgets Facebook, occorre inserire il relativo codice javascript soltanto una volta.
Analizziamo il Waterfall generato da Speedoo
Test Speedoo completo: https://www.giorgiotave.it/speedoo/result/130614_FD_W/
Documento Completato | Completamente Caricato | ||||||||
---|---|---|---|---|---|---|---|---|---|
Tempo Caricamento | Primo Byte | Inizio Render | Tempo | Richieste | Byte | Tempo | Richieste | Byte | |
Senza social widgets | 1.250s | 0.228s | 0.557s | 1.250s | 11 | 150KB | 1.445s | 13 | 152KB |
Asincrono | 3.150s | 0.226s | 0.547s | 3.150s | 18 | 565KB | 8.123s | 51 | 1,366KB |
Innanzitutto notiamo che vi sono 38 richieste in più che rappresentano il 75% delle richieste totali.
Il tempo del completamento del documento è passato da 1,250 secondi a 3,150 secondi.
Il tempo del caricamento completo è balzato da 1,445 secondi a 8,123 secondi.
Il peso della pagine è aumentato da 152 KB a 1366 KB.
Un altro aspetto non immediato da constatare è che il tempo di caricamento delle due immagini (tigro.jpg e tigro-2.jpg) è aumentato passando dai circa 800 millisecondi ai circa 1700 millisecondi, poiché le risorse racchiuse nel primo riquadro (evidenziato in rosso) vengono scaricate in contemporanea alle due immagini.
Su un totale di 51 richieste, notiamo che 18 sono effettuate prima del completamento del documento e le restanti 33 dopo.
Nel primo riquadro ho racchiuso le richieste delle risorse, relative ai social widgets, che richiamiamo direttamente dalla nostra pagina.
Il secondo, invece, fa riferimento a tutte le risorse che vengono richiamate a loro volta dai widgets stessi.
Deferer
Questo metodo consiste nel ritardare il caricamento dei social widgets. Nel mio esempio effettuerò la chiamata degli script dopo che la pagina è completamente caricata e con “completamente” intendo dopo aver caricato immagini, css e javascript inclusi nel codice html.
Così facendo si evitano concorrenze di download tra le risorse “fondamentali” della nostra pagina e le risorse necessarie ai social widgets.
Per ottenere questo risultato è sufficiente affidarsi all’evento onload.
quest’ultimo si verifica quando il solo documento HTML è caricato, mentre l’onload si verifica quando tutte le risorse richiamate (css, js, immagini, etc…) dal documento sono state completate.
// Add a script element as a child of the body function socialWidgetsLoad() { //alert("La pagina è caricata. Adesso tocca ai socials!"); //Facebook (function(d, s, id) { var js, fjs = d.getElementsByTagName(s)[0]; if (d.getElementById(id)) return; js = d.createElement(s); js.id = id; js.src = "//connect.facebook.net/it_IT/all.js#xfbml=1"; fjs.parentNode.insertBefore(js, fjs); }(document, 'script', 'facebook-jssdk')); //Twitter !function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs'); //Google+ (function() { var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true; po.src = 'https://apis.google.com/js/plusone.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s); })(); } if (window.addEventListener) window.addEventListener("load", socialWidgetsLoad, false); else if (window.attachEvent) window.attachEvent("onload", socialWidgetsLoad); else window.onload = socialWidgetsLoad;
Pagina del test: Caricamento social widgets – Defer
Vediamo come cambia il waterfall generato da Speedoo
Test Speedoo completo: https://www.giorgiotave.it/speedoo/result/130614_W5_T/
Innanzitutto notiamo che il tempo di caricamento delle due immagini è tornato “alla normalità” (riquadro 1).
Dal secondo riquadro si evince come il caricamento del widgets avviene dopo il completamento del documento (rappresentato dalla linea blu verticale)
Documento Completato | Completamente Caricato | ||||||||
---|---|---|---|---|---|---|---|---|---|
Tempo Caricamento | Primo Byte | Inizio Render | Tempo | Richieste | Byte | Tempo | Richieste | Byte | |
Senza social widgets | 1.250s | 0.228s | 0.557s | 1.237s | 11 | 150KB | 1.445s | 13 | 152KB |
Asincrono | 3.150s | 0.226s | 0.547s | 3.150s | 18 | 565KB | 8.123s | 51 | 1,366KB |
Defer | 1.237s | 0.221s | 0.535s | 1.237s | 11 | 151BK | 6.569s | 51 | 1,367KB |
Come si evince dalla tabella, il tempo, le richieste ed i bytes relativi al completamento del documento, sono ritornate ai valori della pagina che non faceva uso di social widgets. Rimangono quasi invariati invece i valori che si riferiscono al completamento del documento. In pratica abbiamo aumentato la velocità percepita dall’utente.
Lazy loading
Il lazy loading (traducendo letteralmente: caricamento pigro) è una tecnica molto importante per aumentare le performance dei siti web.
Consiste nel caricare le risorse solamente quando sono necessarie e si può applicare a qualunque risorsa: javascript, css, immagini, etc…
Non esiste una metodologia standard, possiamo decidere a nostro gusto ed in base alle nostre esigenze come effettuare il lazy loading.
Di seguito illustro due esempi:
Social widgets subito visibili (p.e.: posizionati in testa ad un articolo)
Se i social widgets sono subito visibili nella nostra pagina possiamo scegliere di mostrare inizialmente delle icone statiche e di richiamare le funzioni per il caricamento dei social widgets al verificarsi dell’evento onmouseover dell’elemento che li contiene
document.getElementById("socialwidgets").onmouseover=function(){ socialWidgetsLoad(); document.getElementById("socialwidgets").onmouseover=null; };
Pagina del test: Caricamento social widgets – Lazy load (onmouseover)
Se un utente passa con il mouse sopra l’elemento con id=socialwidgets verrà richiamata la funzione socialWidgetsLoad (la medesima del punto precedente).
Social widgets non visibili inizialmente (p.e.: posizionati in fondo ad un articolo)
In questo caso possiamo decidere di caricare i social widgets solo e soltanto se l’utente effettua lo scrolling della pagina fino a quando il div che li contiene entra nella zona visibile della finestra.
function inViewport(el) { var top = el.offsetTop; var left = el.offsetLeft; var width = el.offsetWidth; var height = el.offsetHeight; while(el.offsetParent) { el = el.offsetParent; top += el.offsetTop; left += el.offsetLeft; } return ( top < (window.pageYOffset + window.innerHeight) && left < (window.pageXOffset + window.innerWidth) && (top + height) > window.pageYOffset && (left + width) > window.pageXOffset ); } window.onscroll = function (e) { if(inViewport(document.getElementById("socialwidgets"))) { socialWidgetsLoad(); window.onscroll = null; } }
Pagina del test: Caricamento social widgets – Lazy load (onscroll)
L’impatto sul waterfall generato da Speedoo è sorprendente
Test Speedoo completo: https://www.giorgiotave.it/speedoo/result/130614_4X_X/
Documento Completato | Completamente Caricato | ||||||||
---|---|---|---|---|---|---|---|---|---|
Tempo Caricamento | Primo Byte | Inizio Render | Tempo | Richieste | Byte | Tempo | Richieste | Byte | |
Senza social widgets | 1.250s | 0.228s | 0.557s | 1.237s | 11 | 150KB | 1.445s | 13 | 152KB |
Asincrono | 3.150s | 0.226s | 0.547s | 3.150s | 18 | 565KB | 8.123s | 51 | 1,366KB |
Defer | 1.237s | 0.221s | 0.535s | 1.237s | 11 | 151KB | 6.569s | 51 | 1,367KB |
Lazy load | 1.262s | 0.226s | 0.559s | 1.262s | 11 | 151KB | 1.521s | 13 | 153KB |
Praticamente tutti i valori risultano uguali a quelli iniziali: avremo un sito veloce come se non facesse uso di social widgets.
Logicamente al verificarsi dell’evento da noi scelto verranno scaricate le 38 risorse necessarie al funzionamento dei widgets, ma solo quando sono realmente indispensabili al corretto funzionamento, evitando così all’utente inutili download di files.
Le tecniche adottate sono soltanto esempi didattici, migliorabili e adattabili alle proprie esigenze.
Esistono diversi script javascript, plugins jQuery, plugins WordPress per attuare il lazy loading del social widgets.
Risorse
- Loading Scripts Without Blocking
- Loading JavaScript without blocking
- jQuery.getScript()
- Socialite.js
- jQuery Lazy Plugin
- Viewport Selectors for jQuery
Gran bell’articolo Andrea, complimenti!
È sempre un piacere leggere un po’ di dati reali, davvero molto utile… quasi quasi implemento il lazy load 🙂
Grazie Roberto.
Ritengo il lazy loading importante quasi come il caching 🙂
Piccola curiosità, premetto che non so esperto in js.
Perchè il onmouseover= null ?
document.getElementById(“socialwidgets”).onmouseover=null;
grazie ciao
Ciao Massimo,
viene impostato a null poiché, altrimenti, ad ogni mouseover verrebbe richiamata la funzione socialWidgetsLoad.
In questo modo invece verrà richiamata soltanto la prima volta.
ok, grazie.
Ciao Andrea, ho un fix per te
var socialwidgets = document.getElementById(“socialwidgets”);
window.onscroll = function (e) {
if( socialwidgets && inViewport( socialwidgets ) ) {
socialWidgetsLoad();
window.onscroll = null;
}
}
Il problema era che se non veniva trovato l’ID socialwidget il debugger continuava a segnalarmi un errore nella funzione inViewport (variabile nulla).
(Se c’è una soluzione migliore fammelo sapere :-))
Ciao
Enea