Lazy Loading ist nun im Blog komplett implementiert
Lazy Load ist eine Technik, Content erst zu laden, wenn er auch tatsächlich vom User betrachtet wird und Content der außerhalb des angezeigten Bereiches erst einmal nicht vom Browser laden zu lassen um Traffic und CPU/GPU Ressourcen zu sparen. Es ist besonders bei Bilderblogs sehr wirksam da dort besonders viel Traffic entsteht und es das Seitenladen sehr beschleunigt.
Lazy load für Bilder
Für Bilder habe ich vanilla-lazyload eingebaut, um auf der Index Seite bei vielen Bildern einen Geminderten Load zu erreichen. Das hat sehr gut funktioniert und das Bilder Blog in der Ladezeit beschleunigt.
Die Implementierung war sehr einfach man muss eigentlich nur die JS Lib laden und den IMG Tag etwas anders schreiben.
Beispiel
Im Header habe ich aus den Beispielen ein paar Sachen übernommen, mit ein wenig Logging Feedback in der Konsole. Ich habe dann noch ein myLazyLoad.update() hinzugefügt welsches nach dem fertigen laden der Seite nochmal alle Bilder indexiert, weil das normale laden der Lib wohl ein Limit hat und nicht alle Bilder gefunden hatte.
<style>
img:not([src]) {
visibility: hidden;
}
/* Fixes Firefox anomaly during image load */
@-moz-document url-prefix() {
img:-moz-loading {
visibility: hidden;
}
}
</style>
<script>
function logElementEvent(eventName, element) {
console.log(Date.now(), eventName, element.getAttribute("data-src"));
}
var callback_enter = function (element) {
logElementEvent("🔑 ENTERED", element);
};
var callback_exit = function (element) {
logElementEvent("🚪 EXITED", element);
};
var callback_loading = function (element) {
logElementEvent("⌚ LOADING", element);
};
var callback_loaded = function (element) {
logElementEvent("👍 LOADED", element);
};
var callback_error = function (element) {
logElementEvent("💀 ERROR", element);
element.src =
"https://via.placeholder.com/440x560/?text=Error+Placeholder";
};
var callback_finish = function () {
logElementEvent("✔️ FINISHED", document.documentElement);
};
var callback_cancel = function (element) {
logElementEvent("🔥 CANCEL", element);
};
window.lazyLoadOptions = {
threshold: 0,
// Assign the callbacks defined above
callback_enter: callback_enter,
callback_exit: callback_exit,
callback_cancel: callback_cancel,
callback_loading: callback_loading,
callback_loaded: callback_loaded,
callback_error: callback_error,
callback_finish: callback_finish,
elements_selector: ".img-zoomable"
};
window.addEventListener(
"LazyLoad::Initialized",
function (e) {
console.log(e.detail.instance);
},
false
);
document.addEventListener('DOMContentLoaded', function () {
var myLazyLoad = new LazyLoad(window.lazyLoadOptions);
myLazyLoad.update();
});
</script>
<script async type="text/javascript" src="{{ rootUrl }}/js/lazyload.min.js"></script>
Die IMG Tags haben nun kein src Attribut mehr sondern ein data-src Attribut welsches die Bild URL beinhaltet was bei sichtbar werden des Bildes geladen werden soll.
<a href="ORGIMGURL" target="_blank">
<img data-src="LAZYLOARDIMGURL" alt="image" />
</a>
Das funktioniert bei meinem Bilder Blog, auf dem teils mehrere 100 Bilder auf der Startseite zu sehen sind sehr gut und mit allen getesteten Browsern und Mobilgeräten.
Lazy load für krpano
Bei krpano hatte ich das Problem das Handys teils Probleme hatten, mehr als 4 Panoramen auf einer Seite darzustellen, was dann dazu geführt hatte das kein Panorama mehr funktionierte. Das Problem habe ich mit einen Lazyloader mit Hilfe von IntersectionObserver() lösen können und die Ladezeit des Blogs noch mehr Verbessert.
Beispiel
Im Header habe ich einen krpanoobserver() erzeugt der die Sichtbarkeit der registrierten Elemente Überwach und bei Sichtbarkeit das Panorama Lädt und beim weiter scrollen das Panorama wieder Löscht um WebGl Ressourcen Frei zu machen.
<script type="text/javascript" src="{{ rootUrl }}/js/krpano/krpano.js"></script>
<script>
var krpanolist = ["dummy"];
var krpanoobserver = new IntersectionObserver(function(entries) {
if(entries[0]['isIntersecting'] === true) {
//console.log('Target is visible in the screen');
for (let i = 0; i < krpanolist.length; i++) {
if (krpanolist[i] == entries[0].target.id) {
return;
}
}
console.log('add krpano ' + entries[0].target.id );
krpanolist.push( entries[0].target.id )
embedpano({xml:entries[0].target.attributes.xml.nodeValue, target:entries[0].target.id, id:'krpanoSWFObject'+entries[0].target.id , html5:"only", mobilescale:1.0, passQueryParameters:"startscene,startlookat"});
}
else {
console.log('remove krpano ' + entries[0].target.id);
removepano( 'krpanoSWFObject'+entries[0].target.id );
krpanolist = krpanolist.filter(item => item !== entries[0].target.id)
}
}, { threshold: [0, 0.01, 1] });
</script>
Im Artikel lade ich jetzt nicht mehr das Panorama sondern konfiguriere mit dem krpanoobserver() das Laden des Panoramas und gebe ihm die #ID mit, in dem das Panorama Geladen werden soll bzw. welsches HTML Element observiert werden soll und mit dem xml Attribut gebe ich ihn die url zum pano.xml File welsches geladen werden soll.
<div id="pano_BFUbPBDC" class="pano-image" xml="PANORAMAURL/pano.xml">
<script>
krpanoobserver.observe(document.querySelector("#pano_BFUbPBDC"));
</script>
</div>
Lazy load für openlayer
OpenLayers Benutzte ich um GPX Tracks oder Kartenpunkte im Blog Darzustellen. OpenLayers scheint aber lade Probleme zu bekommen wenn zu viele Karten auf der selben Seite geladen werden, weswegen ich auch für OpenLayers ein Lazy Loading in Blog eingebaut habe um die Karten erst zu laden wenn sie Angezeigt werden soll.
Beispiel
Im Header habe ich einen olmapobserver() Gebaut der bei der ersten Sichtbarkeit der Karte diese lädt und sie dann ignoriert.
<script src="{{ rootUrl }}/js/ol/ol.js"></script>
<script src="{{ rootUrl }}/js/openlayer_init.js"></script>
<link rel="stylesheet" media="screen" href="{{ rootUrl }}/js/ol/ol.css">
<script>
// LazyLoad for openlayer map
var olmaps = {};
var olmapobserver = new IntersectionObserver(function(entries) {
mymapID = entries[0].target.id.split("_")[1];
if(entries[0]['isIntersecting'] === true) {
if(olmaps[mymapID].getTarget() == null) {
console.log('add ol map ' + entries[0].target.id);
olmaps[mymapID].setTarget( entries[0].target.id );
}
}
}, { threshold: [0, 0.01, 1] });
</script>
Im Artikel habe ich die Map soweit vorbereitet und alle Maps in olmaps[] fertig konfiguriert gespeichert. Danach werden sie im Observer mit olmaps[mymapID].setTarget( entries[0].target.id ); ihrem Target zugewiesen, womit sie dann endgültig geladen wird.
<div id="olmap_mapSFDXPwaB" class="olmap">
<select id="tilesource_mapSFDXPwaB" class="tilesource">
<option value="oSTREETm" selected="selected">OpenStreetMap.org</option>
<option value="OSMDE" >OpenStreetMap.de</option>
<option value="4UMAPS" >4UMaps.eu</option>
<option value="CYCLOSM" >CyclOSM</option>
<option value="OPENTOPOMAP" >OpenTopoMap</option>
<option value="TOPPLUS" >TOP PLUS</option>
<option value="ArcGIS">Satellite</option>
</select>
</div>
<script>
olmaps.mapSFDXPwaB = new ol.Map({
controls: ol.control.defaults.defaults().extend([
new ol.control.FullScreen(),
new ol.control.OverviewMap({
layers: [
new ol.layer.Tile({
source: tileSource_oSTREETm,
}),
],
})
]),
target: null, // 'olmap_mapSFDXPwaB'
layers: [],
view: new ol.View({
center: [0, 0],
zoom: 0
})
});
olmaps.mapSFDXPwaB.addLayer(new ol.layer.Tile({
source: tileSource_OSMDE
}));
var vectorSource_mapSFDXPwaB = new ol.source.Vector({
url: 'URLZUMGPXTRACK.gpx',
format: new ol.format.GPX()
});
var vectorStyle = new ol.style.Style({
stroke: new ol.style.Stroke({
color: 'red',
width: 3,
opacity: 0.5
})
});
olmaps.mapSFDXPwaB.addLayer(new ol.layer.Vector({
source: vectorSource_mapSFDXPwaB,
style: vectorStyle
}));
var layers_mapSFDXPwaB = olmaps.mapSFDXPwaB.getLayers().getArray();
vectorSource_mapSFDXPwaB.once('change',function(e){
if(vectorSource_mapSFDXPwaB.getState() === 'ready') {
if(layers_mapSFDXPwaB[1].getSource().getFeatures().length > 0) {
olmaps.mapSFDXPwaB.getView().fit(vectorSource_mapSFDXPwaB.getExtent());
}
}
});
var select_mapSFDXPwaB = document.getElementById('tilesource_mapSFDXPwaB');
function onChange_mapSFDXPwaB() {
var newMap = select_mapSFDXPwaB.value;
if(newMap) {
var tileSource ='tileSource_' + newMap;
var mapSource = eval(tileSource);
var mapLayer0 = layers_mapSFDXPwaB[0];
mapLayer0.setSource(mapSource);
}
}
select_mapSFDXPwaB.addEventListener('change', onChange_mapSFDXPwaB);
olmapobserver.observe(document.querySelector("#olmap_mapSFDXPwaB"));
</script>
Viel Spaß beim Nachbauen