CSS box-sizing / flexbox: Fotogalerie

CitroenDsVier

Lt. Commander
Registriert
Dez. 2013
Beiträge
1.896
Hallo zusammen,

ich sitze an einer Website zum Ausstellen von Fotos. Hier möchte ich wie im 1. Anhang-Bild gezeigt Bilder unterschiedlicher Größe elegant nebeneinander anordnen. Dabei soll die Reihenfolge der Bilder nicht komplett durcheinandergewürfelt werden, ein paar Sprünge sind aber iO.
Zuerst habe ich das Ganze mit 3 Spalten Serverseitig gelöst: Der Server packt die Bilder nacheinander in eine der drei Spalten, dadurch entstehen keine Lücken und die Reihenfolge bleibt größtenteils erhalten. Das hätte ich so lassen können, allerdings ist das schlecht mit Responsiveness vereinbar. Auf dem Smartphone möchte ich nur noch eine Spalte haben, das wäre dann nur möglich, wenn ich auf die Reihenfolge verzichte.

Jetzt bin ich auf flexbox gestoßen, bin mit dem Ergebnis aber noch nicht 100%ig zufrieden (siehe Screenshot).

Hat Jemand vielleicht Erfahrung mit so etwas und kann mir einen Tipp geben, wie ich das Ganze optisch ansprechend umsetzen kann?

CSS:
#content {
    align-content: flex-start;
}
#content .image {
    display: inline-block;
    margin: 10px;
    width: calc((100% / 3) - 21px);
}
#content .image img {
    width: 100%;
    height: 100%;
    position: relative;
    vertical-align: middle;
}

HTML:
<div id="content">
    <div class="image"><img src="..."></div>
    <div class="image"><img src="..."></div>
    <div class="image"><img src="..."></div>
    <div class="image"><img src="..."></div>
</div>

MfG
 

Anhänge

  • photosite2.jpg
    photosite2.jpg
    66,3 KB · Aufrufe: 372
  • photosite.jpg
    photosite.jpg
    157,6 KB · Aufrufe: 343
Das ist quasi die Version, die ich vorher Serverseitig realisiert habe (siehe meine Paint-Künste im Anhang 1. Post). Problem hierbei ist, dass das erste Bild in der zweiten Spalte (vom PC-Layout) dann auf dem smartphone bei nur einer Spalte zum 10. oder 15. Bild wird.
Das möchte ich möglichst vermeiden, um die Reihenfolge nicht komplett über den Haufen zu werfen.
 
Ah! nun hab ich's verstanden :) Mit Flex Box dürfte das nicht anders möglich sein, weil du deine Boxen entweder in Spalten oder Reihen anordnen musst. (Dein Paint Bild benutzt Spalten, dein Screenshot benutzt Reihen)

Also würde ich folgende 2 Lösungen vorschlagen:
1. Der Server liefert je nach Client die Bilder in einer anderen Reihenfolge aus, oder (wahrscheinlich sinnvoller):
2. Der Client sortiert die Bilder um, bzw. entfernt die Flex Box, wenn es nur noch eine Spalte gibt. Erfordert dann natürlich etwas JavaScript.

Responsive Animationen beim Resize sind damit natürlich ziemlich kompliziert. Aber wenn du keine Animationen brauchst, dann sollte das einfach umgesetzt werden können.
 
  • Gefällt mir
Reaktionen: CitroenDsVier
Es gibt fertige Libraries dafür wie z.B. https://masonry.desandro.com/ oder http://callmecavs.com/bricks.js/

Ich bin mir nicht mehr ganz sicher zu den Details, aber soweit ich mich erinnere sind nicht alle Layouts dieser Art nur mit CSS Flexbox zu machen, das ist nicht ganz mächtig genug dafür. Mit CSS Grid geht da mehr, aber das setzt recht neue Browser voraus.

Generell kann man da mehr Informationen unter dem Begriff "Masonry layout" finden.
 
  • Gefällt mir
Reaktionen: CitroenDsVier und savuti
Super, vielen Dank!

Nachdem ich gerade eine halbe Stunde erfolglos versucht habe, eine fertige Masonry-Lösung zu implementieren (trotz minimalem Versuchsaufbau kam nur Kappes dabei raus), werde ich mich jetzt hinsetzen und selbst versuchen, das Ganze möglichst schlank und schlicht in js zu implementieren. Der Server schickt an den Client dann nur eine Liste von Bildern (in der richtigen Reihenfolge) und der Client entscheidet dann anhand des Viewports, wieviele Spalten daraus werden und wie dementsprechend umsortiert werden muss.
Also quasi Version Nr. 2 von @benneque .

So die Idee :)
 
So ein Spalten-Layout bekommst du mehr oder weniger gut auch einfach als Liste hin, per column-count: X ist es möglich Spalten zu definieren. Per Media-Query kannst du dann für die unterschiedlichen Auflösungen die Spalten überschreiben.
Es müssen dafür auch nicht extra Spalten definiert werden, ganz einfache Liste mit Elementen:

776017


Dummerweise werden die Elemente aber von "oben links" nach "unten rechts" sortiert.
Kommt dann drauf an was für dich
Dabei soll die Reihenfolge der Bilder nicht komplett durcheinandergewürfelt werden, ein paar Sprünge sind aber iO.
bedeutet.
776006
776007



Alternativen ala Grid & Masonry wurden ja schon genannt.
Wobei ich Masonry nur begrenzt empfehlen kann, wenn es nur für ein paar Bilder gebraucht wird ist es ganz okay. Aber wenn es komplexer wird und die Anzahl der Kacheln merkbar steigt merkt man auf so machen Geräten das dort einiges per Javascript gerechnet und positioniert wird.

Wenn dir der Server ein Javascript-Array mit den Links ausspuckt sind es nur ein paar Zeilen Javascript um ein
<div class="row"><div class="column">...</div><div class="column">...</div></div>-Gerüst in der richtigen Reihenfolge zu befüllen.
In .row N .columns neu erstellen, und dann in einer Schleife über die Array-Elemente per Index % N in die entsprechende Spalte ein neues Bild. "N" steht für Anzahl der Spalten. Und das ganze führst du beim .load und .resize vom Browser auf.

Edit: too slow :rolleyes:
 
  • Gefällt mir
Reaktionen: CitroenDsVier
Joshinator schrieb:
Wenn dir der Server ein Javascript-Array mit den Links ausspuckt

Das war der Knackpunkt, auf die simple Idee bin ich alleine nicht gekommen :D

Danke für Deinen ausführlichen Post @Joshinator, inzwischen hab ich's selbst geschrieben.

Die Version läuft jetzt zuverlässig, benötigt nur noch etwas css-Anpassung. Vielleicht hilft es ja Jemandem:
PHP:
function listPhotoURLs($dir) {
        if($dir == "" || $dir == -1) { return "no gal: ".$dir; }
        $photoList = "";
        $i = 1;
        $files = scandir($dir."/small");
       
        if($files != null) {
            foreach($files as $file) {
                if($i > 2) {    // skip ".." and "."
                    $photoList .= "\"".$dir."/small/".$file."\",";
                }
                $i++;
            }
        }
        $photoList = substr($photoList, 0, -1);        // remove last comma
        return $photoList;
    }

Javascript:
var images = [<?php echo listPhotoURLs(getGalleryDir($_GET["hash"]));?>];

function sortImages() {
    // calc column-count and -width
    conWidth = $("#content").width();
    if(conWidth < 500) {
        // Smartphone, 1 Column
        $("#content").prepend('<div class="column" id="column0"></div>');
        for (i = 0; i < images.length; i++) {
            $("#column0").append('<div class="image"><img src="' + images[i] + '" title="image-' + (i+1) + '" /></div>');
        }
    } else if(conWidth >= 500 && conWidth < 800) {
        // Tablet, 2 Columns
        $("#content").prepend('<div class="column" id="column1"></div>');
        $("#content").prepend('<div class="column" id="column0"></div>');
       
        cc = 0;    // column counter
        for(i = 0; i < images.length; i++) {
            $("#column" + cc).append('<div class="image"><img src="' + images[i] + '" title="image-' + (i+1) + '" /></div>');
            cc++;
            if(cc > 1) { cc = 0; }
        }
       
        // set column width
        for(i = 0; i < 2; i++) {
            $("#column" + i).width(Math.round((conWidth / 2) - 1));
        }
    } else if(conWidth >= 800 && conWidth < 1150) {
        // Computer, 3 Columns
        $("#content").prepend('<div class="column" id="column2"></div>');
        $("#content").prepend('<div class="column" id="column1"></div>');
        $("#content").prepend('<div class="column" id="column0"></div>');
       
        // add images to columns
        cc = 0;    // column counter
        for(i = 0; i < images.length; i++) {
            $("#column" + cc).append('<div class="image"><img src="' + images[i] + '" title="image-' + (i+1) + '" /></div>');
            cc++;
            if(cc > 2) { cc = 0; }
        }
       
        // set column width
        for(i = 0; i < 3; i++) {
            $("#column" + i).width(Math.round((conWidth / 3) - 1));
        }
    } else if(conWidth >= 1150) {
        // Computer, 4 Columns
        $("#content").prepend('<div class="column" id="column3"></div>');
        $("#content").prepend('<div class="column" id="column2"></div>');
        $("#content").prepend('<div class="column" id="column1"></div>');
        $("#content").prepend('<div class="column" id="column0"></div>');
       
        // add images to columns
        cc = 0;    // column counter
        for(i = 0; i < images.length; i++) {
            $("#column" + cc).append('<div class="image"><img src="' + images[i] + '" title="image-' + (i+1) + '" /></div>');
            cc++;
            if(cc > 3) { cc = 0; }
        }
       
        // set column width
        for(i = 0; i < 4; i++) {
            $("#column" + i).width(Math.round((conWidth / 4) - 1));
        }
    }  
}
HTML:
...
<div id="content">
    <!-- hier entstehen die columns -->
    <div style="clear:both;"></div>
</div>
...
 
  • Gefällt mir
Reaktionen: Joshinator
Yikes, der Code sieht... funktional aus :D
Auch auf die Gefahr hin das ich jetzt wie ein Ar*** klinge, das lässt sich schöner erledigen.

So wie ich mich kenne werden da noch 1-2 Fehler drin sein aber das sollte dir eine Idee geben wie es anders gehen würde.
Javascript:
function sortImages() {
    var conWidth = $("#content").width();
    var columns = 1; // Anzahl Spalten nach Breakpoint
    if (conWidth >= 1150) {
        columns = 4;
    } else if (conWidth >= 800) {
        columns = 3;      
    } else if (conWidth >= 500) {
        columns = 2;
    }
   
    $('#content').html(""); // Gallerie leeren / alte Spalten entfernen
    for(var i = 0; i < columns; i++) { // Spalten erstellen
        $("#content").append('<div class="column" id="column' + i + '"></div>'); // Spalten erzeugen
    }
   
    images.forEach(function(element, index) { // geht nicht in <IE11!! wenn wichtig -> for-Schleife benutzen
        var column = index % columns; // Modulo für Restwert-Division (http://manderc.com/operators/modoperator/index.php)
        $('#column' + column).append('<div class="image"><img src="' + element + '" title="image-' + (index + 1) + '" /></div>')
    });
}

Modulo gibt dir den Restwert einer Division aus, 5 % 3 wäre 2, 6 % 3 jedoch 0. Für sowas ist der recht praktisch weil du dann nicht mehr in einer Variable mitzählen musst.


Das festlegen der Breite einer Spalte ist auch unnötig. Das lässt sich per CSS sehr einfach erledigen.
CSS:
#content {
    display: flex;
}

#content > .column {
    flex: 1;
}

Dadurch wird jede .column in #content gleich breit. Bei einer Spalte wären es 100%, bei zwei 50%, bei 3 33%, usw...

Im Endeffekt tun beide Versionen das gleiche, aber es ist generell nicht verkehrt die Styles so weit es geht über CSS zu manipulieren.
Das Verändern vom DOM ist in Javascript relativ gesehen recht aufwendig, grade sowas wie .animate() in jQuery wo mehrmals per Sekunde der DOM verändert wird. Da kommt man besser damit weg Klassen zu setzten und per CSS zu animieren, da kann die Browserengine entscheiden wie es am flüssigstem geht.
Zwar etwas weit ausgeholt :rolleyes:, aber der Grund warum ich sowas lieber per CSS erledige, und alles was Style angeht ist im CSS sowieso gut aufgehoben.

Wenn du jetzt deine Website morgen auf einem iMac Pro mit drölfzen-K Auflösung öffnest und fest stellst das du doch gerne Breakpoints für 7, 8, 9 und 10 Spalten willst musst du nur das if-else anpassen damit die columns-Variable den richtigen Wert hat.

PS: es gibt in PHP json_encode(). Damit kann man super easy PHP-Variablen ausgeben um die in JS zu benutzen. In deiner Version würde das Anführungszeichen in $file dafür sorgen dass das JS auf die Nase fällt. json_encode() escaped alle Zeichen die Probleme machen solange das Encoding passt.
In deinem Fall unwahrscheinlich das " im Text drin steht, aber als Anstoß für optionale Optimierungen am Code :p
 
  • Gefällt mir
Reaktionen: CitroenDsVier
Ich habe mich bei keinem Schönheitswettbewerb beworben :D

Getreu nach dem Motto first do it, then to it right, then do it fast ;)

Die CSS-Tricks sind mir tatsächlich neu, danke!

Deine Methode sieht in der Tat schöner aus, ich hab mir beim Schreiben schon gedacht, dass man das schöner (und weniger wiederholend) hinkriegen müsste. Das Erstellen der columns z.B. war aber Faulheit, das musste schnell gehen :D
Für mich hat meine Variante gerade den Vorteil, dass ich eine Lightbox (steht noch auf der TODO Liste) bspw. beim Smartphone-Design entspannt weglassen kann.
Wenn alles läuft und alle Funktionen gegeben sind, wird optimiert :)
 
  • Gefällt mir
Reaktionen: Joshinator
Muss es unbedingt Flexbox sein? Für das was di erreichen willst eignet sich CSS Grid hervorragend - ohne eine Zeile JS-Code...
 
@kim88 : Die Sache, die du ansprichst, hat sich ja inzwischen geklärt :)

Eine Sache hab ich noch @Joshinator :

Mir ist aufgefallen, dass bei 2, 3 oder 4 Spalten häufig eine Spalte sehr viel länger als die anderen sind. Daher wollte ich meine js-Methode anpassen, sodass die letzten x Bilder (in meinem Test x = 8) immer der jeweils kürzesten Spalte zugewiesen werden.
Hierfür hole ich mir mit $("#column3").innerHeight() die Höhe jeder Spalte und nehme die mit der geringsten Höhe.

Das klappt allerdings nur in meinem Kopf, denn jQuery bzw. js geben mir unplausible Höhen für die Spalten, meistens sind sogar alle 4 gleich hoch. Kleines Beispiel: Ich habe eine Galerie mit 97 Bildern. Schaue ich mir die Spalten im Browser an, haben die eine Höhe von ca. 5000px laut Objekt-Inspektor. Javascript gibt mir aber Höhen um die 900 aus.

Hier mal mein code: (immernoch nicht schön gemacht ;))
Javascript:
(...)

    else if(conWidth >= 1150) {
        // Computer, 4 Columns
        // Include Lightbox
        $("#content").prepend('<div class="column" id="column3"></div>');
        $("#content").prepend('<div class="column" id="column2"></div>');
        $("#content").prepend('<div class="column" id="column1"></div>');
        $("#content").prepend('<div class="column" id="column0"></div>');
   
        // add images to columns
        cc = 0;    // column counter
        for(i = 0; i < images.length; i++) {
            if(i > (images.length - 8)) {        // sort last images to shortest column
                cc = getShortestColumnIndex();        // $("#column0"), $("#column1"), $("#column2"), $("#column3")
                console.log("i is " + i + ", shortest column is c no " + cc);
            }
            $("#column" + cc).append('<div class="image"><a class="example-image-link" href="' + images[i].replace("small", "big") + '" data-lightbox="example-1" data-title=""><img class="example-image" src="' + images[i] + '" title="image-' + (i+1) + '" /></a></div>');
            cc++;
            if(cc > 3) { cc = 0; }
        }
   
        // set column width
        for(i = 0; i < 4; i++) {
            $("#column" + i).width(Math.round((conWidth / 4) - 1));
        }
    }
}

function getShortestColumnIndex() {
    ah = $("#column0").innerHeight();
    bh = $("#column1").innerHeight();
    ch = $("#column2").innerHeight();
    dh = $("#column3").innerHeight();
    min = Math.min(ah, bh, ch, dh);

    console.log("ah: " + ah + " bh: " + bh + " ch: " + ch + " dh: " + dh + " min: " + min);

    if(min == ah) { return 0; }
    if(min == bh) { return 1; }
    if(min == ch) { return 2; }
    if(min == dh) { return 3; }
    return false;
}

und die daraus resultierenden Konsolenausgaben:
Code:
ah: 897 bh: 897 ch: 858 dh: 858 min: 858
i is 90, shortest column is c no 2
ah: 897 bh: 897 ch: 897 dh: 858 min: 858
i is 91, shortest column is c no 3
ah: 897 bh: 897 ch: 897 dh: 897 min: 897
i is 92, shortest column is c no 0
ah: 936 bh: 897 ch: 897 dh: 897 min: 897
i is 93, shortest column is c no 1
ah: 936 bh: 936 ch: 897 dh: 897 min: 897
i is 94, shortest column is c no 2
ah: 936 bh: 936 ch: 936 dh: 897 min: 897
i is 95, shortest column is c no 3
ah: 936 bh: 936 ch: 936 dh: 936 min: 936
i is 96, shortest column is c no 0


Die Bilder werden mit dem Code genauso auf die Spalten aufgeteilt, wie vorher (siehe Anhang, die Nummern in den Bildern sind die Bildnummern).
 

Anhänge

  • photosite3.jpg
    photosite3.jpg
    264 KB · Aufrufe: 277
Zuletzt bearbeitet: (Grund: Code lesbarer gemacht)
Alternativ kannst du auch die Position vom letzten Bild pro Spalte abfragen.

Javascript:
$('#column0 > img:last-of-type').offset().top;

Am besten addierst du da drauf noch die Höhe vom Bild, kann ja gut sein das ein Bild doppelt so hoch wie das andere ist.
 
  • Gefällt mir
Reaktionen: CitroenDsVier
Die Variante liefert leider auch falsche Ergebnisse, da werden die letzten 8 Bilder jetzt abwechselnd auf column0 und column1 verteilt. Dementsprechend sind die ersten beiden Columns dann eine gute Ecke länger als die anderen beiden.
Ich musste hierbei
Javascript:
$('#column0 > img:last-of-type').offset().top;
umändern in:
Javascript:
$('#column0 > div:last-of-type').offset().top;
(da jedes img noch in ein div gepackt ist) aber das sollte der Sache ja keinen Abbruch tun.

Die höhe des jeweils letzten div.image habe ich dabei noch addiert, das ändert nichts.
Auffällig ist auch, dass die Werte zwischendurch stark schwanken ("dh", also die Höhe der 4. column, ist erst 1815, dann 2673, und anschließend wieder 1854). Das kann ja nicht sein, es werden ja keine Bilder aus einer column herausgenommen.
"ch" wächst zwar nur (und wird nicht kleiner), allerdings landet dort ja kein Bild mehr, von daher sollte die Höhe eigentlich gleichbleiben.
Ebenfalls auffällig ist, dass c0, c1 und c3 angeblich bei der 90. Iteration (erster Konsolenoutput) gleichhoch sein sollen. Wenn man sich die Seite aber anguckt, kann das garnicht sein. Genauso sind am Ende auch nicht c0 und c1 sowie c2 und c3 gleichhoch (wie js es berechnet), sondern haben die Höhen 5763px, 6490px, 4728px, 5223px.

Hier der zugehörige Konsolenoutput:

Code:
ah: 957 bh: 957 ch: 1815 dh: 1815 min: 957
i is 90, shortest column is c no 0

ah: 996 bh: 957 ch: 1815 dh: 2673 min: 957
i is 91, shortest column is c no 1

ah: 996 bh: 996 ch: 1854 dh: 1854 min: 996
i is 92, shortest column is c no 0

ah: 1035 bh: 996 ch: 1854 dh: 2712 min: 996
i is 93, shortest column is c no 1

ah: 1035 bh: 1035 ch: 1893 dh: 1893 min: 1035
i is 94, shortest column is c no 0

ah: 1074 bh: 1035 ch: 1893 dh: 2751 min: 1035
i is 95, shortest column is c no 1

ah: 1074 bh: 1074 ch: 1932 dh: 1932 min: 1074
i is 96, shortest column is c no 0

Kann das Ganze eventuell mit zu dem Zeitpunkt noch nicht fertig geladenen Bildern zu tun haben?
Meine sortImages() Methode wird über jQuery mit
Javascript:
$(window).load(main);
aufgerufen.
Ergänzung ()

Ich rechne jetzt einfach die Höhen der columns beim Hinzufügen jedes einzelnen Bildes mit... (Also addiere bei jedem Hinzufügen entsprechend die Höhe des Bildes in eine Variable)

sicherlich nicht die performanteste Lösung, aber Javascript (oder ich) scheints ja nicht gebacken zu bekommen :)
Das Ergebnis ist zufriedenstellend.
 
Zuletzt bearbeitet: (Grund: Nachtrag)
Ich muss hier nochmal anknüpfen.

Soweit läuft der js-code, allerdings stolpere ich ab und zu über folgendes Problem:
Lade ich die Seite auf einem neuen Gerät das erste Mal, werden nur ein oder manchmal zwei Bilder angezeigt. In der Konsole stehen keine Fehler, die columns werden korrekt erstellt. Nur eben scheint die sortImages() nach ein-zwei Bildern aufzuhören.
Lade ich die Seite dann nochmal, sind alle Bilder da.

Zwischendurch hatte ich es auch ein paar Mal, dass nur etwa 40% der Bilder angezeigt wurden. Mit einem reload der Seite ließ sich das dann aber ebenfalls beheben.

Woran kann das liegen? Ist window.load doch nicht der richtige Zeitpunkt für sortImages() ?

Nachtrag: dabei sei erwähnt, dass im JS-Array alle Bilder vorhanden sind. Der Server bzw. php macht also alles richtig.

Javascript:
function sortImages() {
    // calc column-count and -width
    var conWidth = $("#content").width();
   
    var columns = 1;
    var ch = [0];        // columnHeights
    var includeLightbox = false;
   
    if (conWidth >= 1150) {
        columns = 4;
        ch = [0, 0, 0, 0];
        includeLightbox = true;
    } else if (conWidth >= 800) {
        columns = 3;
        ch = [0, 0, 0];
        includeLightbox = true;
    } else if (conWidth >= 500) {
        columns = 2;
        ch = [0, 0];
    }
   
    // create columns
    for(var i = columns - 1; i >= 0; i--) {
        $("#content").prepend('<div class="column" id="column' + i + '"></div>');
    }
   
    // fill with images
    for(var i = 0; i < images.length; i++) {
        var cc = getShortestColumnIndex(ch);
        if(includeLightbox) {
            var imageHTML = '<div class="image hover"><img src="' + images[i] + '" title="image-' + (i+1) + '" onClick="showLightbox(' + i + ');" /></div>';
        } else {
            var imageHTML = '<div class="image"><img src="' + images[i] + '" title="image-' + (i+1) + '" /></div>';
        }
        $("#column" + cc).append(imageHTML);
        ch[cc] += getImageHeight(images[i], conWidth, columns);
    }
}

function mainFirst() {
    // loginPopup
    $("#loginPopup").fadeOut(0);
    loginPopupState = false;
   
    initLightbox();
}

$(document).ready(mainFirst);
$(window).load(sortImages);
 
Zuletzt bearbeitet:
nächster Nachtrag: Das ganze scheint mit dem Browser-Cache zu tun zu haben: Leere ich den Cache und lade anschließend die Seite neu, bekomme ich nur ein Bild zu sehen. Je nach Internetgeschwindigkeit (habe in den Firefox-Development-Tools mal eine Drosselung auf 3G eingestellt) muss ich die Seite sogar mehrfach neu laden, bis ich alle Bilder sehe.

Ich habe jetzt versucht, mit einem erzwungen Vorladen der Bilder dem Abhilfe zu schaffen, leider ohne Erfolg. Folgenden Code habe ich dafür verwendet:

Javascript:
function preloadImages() {
    for(var i = 0; i < images.length; i++) {
        var tmpImg = new Image();
        tmpImg.src = images[i];
    }
}

function mainFirst() {
    // loginPopup
    $("#loginPopup").fadeOut(0);
    loginPopupState = false;
   
    initLightbox();
   
    preloadImages();
}

$(document).ready(mainFirst);
$(window).load(sortImages);

Hast Du @Joshinator vlt eine Idee dazu?
 
Was passiert denn wenn die Bilder nicht aus dem Cache kommen? Im DOM erscheinen sollten die ja trotzdem, dein Script schmeißt die Bilder ja nur in Spalten.
Das ist aber nur Rätselraten mit dem was du postest, im DOM-Explorer mal gucken ob die einzelnen Nodes da sind, oder ob die einfach nur durch CSS/Positionierung nicht zu sehen sind.

Bin mir im erstem Moment gar nicht mal sicher ob deine preloadImages-Funktion so funktioniert, wäre eigentlich recht eigenartig wenn Javascript ein Bild laden würde was gar nicht im DOM auftaucht sondern nur als Variable in JS existieren. Musste so einen Spökes aber auch noch nicht veranstalten, da ist onload von einem Bild eleganter.

Ich würde mal drauf tippen das die Lightbox irgendwie das CSS deiner Elemente anpasst (absolut positioniert), da die Bilder zu dem Moment aber noch keine Höhen haben (weil die grade geladen werden) wird falsch positioniert.
Falls dass das Problem ist: beim onload vom Bild erst die Lightbox für das Bild initialisieren, dann sind auch die Höhen vorhanden.
Die gesamte Seite zu sehen würde das helfen was einfacher machen, so nur Mutmaßungen.
 
Nein, die Bilder sind auch nicht im DOM-Explorer zu finden. CSS ist hier also leider nicht das Problem, sie werden von js einfach nicht hinzugefügt. (Im images[] Array sind die URLs aber drinnen!)

Und die lightbox wird erst durch onClick auf eines der Bilder hinzugefügt sowie in den Vordergrund geholt, vorher existiert die im HTML garnicht, sondern nur das etwas dunklere overlay als Hintergrund der Lightbox.

Das, was ich in der preloadImages() verwendet habe, war das, was ich auf die Schnelle in diversen Google-Ergebnissen gefunden habe. Wenn man die Bandbreite extrem runterschraubt (GPRS), lässt sich auch gut erkennen, dass er ein Bild nach dem Anderen holt (abwechselnd "warten auf " und "laden von "). Ich habe nochmal einen Screenshot hinzugefügt, mangels öffentlichem Webserver kann ich die Seite leider anders nicht teilen.

[Nachtrag: natürlich kann ich aber den gesamten Code posten... :D]
Javascript:
var loginPopupState = false;
            var lightboxState = false;
            var lightboxCurrentImage = -1;
            var images = [<?php if(isset($_GET["hash"])) { echo listPhotoURLs(getGalleryDir($_GET["hash"])); } ?>];
        
            // shows and hides the login-popup
            async function loginPopup() {
                if(loginPopupState) {
                    $("#loginPopup").fadeOut(150);
                    await sleep(150);
                    $("#content").css('z-Index', 0);
                    loginPopupState = false;
                } else {
                    $("#content").css('z-Index', -1);
                    $("#loginPopup").fadeIn(150);
                    loginPopupState = true;
                }
            }
        
            /*    Redirects user to other site
             *    @param loc: location to redirect to, used like enums.
             */
            function redirect(loc) {
                switch(loc) {
                    case "logout":        window.location.href = "logout.php"; break;
                    case "management":    window.location.href = ".?lnk=management"; break;
                    case "download":    window.location.href = "<?php echo getGalleryDownloadPath();?>"; break;
                }
            
            }
        
            /*    has been replaced by redirect("logout")
            function logout() {
                window.location.href = "logout.php";
            }*/
        
        
            /*    calcs column-count, sorts images to columns
             */
            function sortImages() {
                console.log("sortImages() started...");
            
                // calc column-count and -width
                var conWidth = $("#content").width();
            
                var columns = 1;
                var ch = [0];        // columnHeights
                var includeLightbox = false;
            
                if (conWidth >= 1150) {
                    columns = 4;
                    ch = [0, 0, 0, 0];
                    includeLightbox = true;
                } else if (conWidth >= 800) {
                    columns = 3;
                    ch = [0, 0, 0];
                    includeLightbox = true;
                } else if (conWidth >= 500) {
                    columns = 2;
                    ch = [0, 0];
                }
            
                // create columns
                for(var i = columns - 1; i >= 0; i--) {
                    $("#content").prepend('<div class="column" id="column' + i + '"></div>');
                }
            
                // fill with images
                for(var i = 0; i < images.length; i++) {
                    var cc = getShortestColumnIndex(ch);
                    if(includeLightbox) {
                        var imageHTML = '<div class="image hover"><img src="' + images[i] + '" title="image-' + (i+1) + '" onClick="showLightbox(' + i + ');" /></div>';
                    } else {
                        var imageHTML = '<div class="image"><img src="' + images[i] + '" title="image-' + (i+1) + '" /></div>';
                    }
                    $("#column" + cc).append(imageHTML);
                    ch[cc] += getImageHeight(images[i], conWidth, columns);
                }
            
                /* set column width
                for(i = 0; i < columns; i++) {
                    $("#column" + i).width(Math.round((conWidth / columns) - 1));
                }*/
            }
        
            /*    calculates height of image, when fittet into column
             *    @param imagePath: path of the image, relative to index.php root
             *    @param contentWidth: width of #content element
             *    @param columns: count of columns
             *    @returns: height of image
             */
            function getImageHeight(imagePath, contentWidth, columns) {
                var tmpImage = new Image();
                tmpImage.src = imagePath;
                var rawImgWidth = tmpImage.width;
                var rawImgHeight = tmpImage.height;
                var displayImgWidth = Math.round((contentWidth / columns) - 1);
                var displayImgHeight = rawImgHeight * (displayImgWidth / rawImgWidth);
                return Math.round(displayImgHeight);
            }
        
            /*    calculates shortest column
             *    @param arr: Array with lenghts of columns
             *    @returns: Index of shortest column or false when error
             */
            function getShortestColumnIndex(arr) {
                if(arr.length == 1) {
                    return 0;
                } else if(arr.length == 2) {
                    var min = Math.min(arr[0], arr[1]);
                } else if(arr.length == 3) {
                    var min = Math.min(arr[0], arr[1], arr[2]);
                } else if(arr.length == 4) {
                    var min = Math.min(arr[0], arr[1], arr[2], arr[3]);
                }
            
                if(min == arr[0]) { return 0; }
                if(min == arr[1]) { return 1; }
                if(min == arr[2]) { return 2; }
                if(min == arr[3]) { return 3; }
                return false;
            }
        
            /*    shows and hides the lightbox
             *    @param imgNr: index of image to be displayed
             */
            async function showLightbox(imgNr) {
                lightboxCurrentImage = imgNr;        // global variable for current lightbox image
                if(lightboxState) {
                    $("#lightboxBackground").fadeOut(350);
                    //$(".image").css("background-color", "#000");                //TODO: background-color von .image hier auf schwarz setzen, unten css-Regel löschen (Versuch)
                    await sleep(350);
                    $("#content").css('z-Index', 0);
                    lightboxState = false;
                } else {
                    //$(".image").css("background-color", $("body").css('backgroundColor'));
                    lightboxAppendImage(imgNr);
                
                    $("#content").css('z-Index', -1);
                    $("#lightboxBackground").fadeIn(350);
                    lightboxState = true;
                }
            }
        
            /*    adds image into #lightbox div
             *    @param imgNr: index of image to be added
             */
            function lightboxAppendImage(imgNr) {
                $("#lightbox").html("");
                $("#lightbox").append('<div id="lightboxImage"><img src="' + images[imgNr].replace("small", "big") + '" /></div>');
                if(imgNr > 0) {
                    // include left/previous image click-area
                    $("#lightboxImage").append('<div id="lightboxClickLeft" onClick="lightboxNextImage(0)"></div>');
                }
                if(imgNr < images.length-1) {
                    // include right/next image click-area
                    $("#lightboxImage").append('<div id="lightboxClickRight" onClick="lightboxNextImage(1)"></div>');
                }
            
                // cache previous and next image in BIG version
                if(imgNr-1 >= 0 && imgNr-1 < images.length) {
                    var imgPrev = new Image();
                    imgPrev.src = images[imgNr - 1];
                }
                if(imgNr+1 < images.length) {
                    var imgNext = new Image();
                    imgNext.src = images[imgNr + 1];
                }
            }
        
            /*    displays next or previous image in the lightbox
             *    @param direction: either 0 or 1, 0 = previous image, 1 = next image
             */
            function lightboxNextImage(direction) {
                var nextImageNr = -1;
                if(direction == 0 && lightboxCurrentImage > 0) {
                    nextImageNr = lightboxCurrentImage - 1;
                } else if(direction == 1 && lightboxCurrentImage < images.length-1) {
                    nextImageNr = lightboxCurrentImage + 1;
                } else {
                    return;
                }
                lightboxCurrentImage = nextImageNr;
                lightboxAppendImage(nextImageNr);
            }
        
            function sleep(milliseconds) {
                return new Promise(resolve => setTimeout(resolve, milliseconds));
            }
        
            /*    initializes lightbox and keylistener
             */
            function initLightbox() {
                // fade out on page load
                $("#lightboxBackground").fadeOut(0);
                lightboxState = false;
            
                // add listeners
                // listener for proper closing
                $("#lightbox").on("click", function(event){
                    event.stopPropagation();
                });
            
                // listener for lightbox keyboard navigation
                $(document).keydown(function(e) {
                    if(e.which == 37 && lightboxState && lightboxCurrentImage > 0) {        // left arrow
                        lightboxNextImage(0);
                    } else if(e.which == 39 && lightboxState && lightboxCurrentImage < images.length) {        // right arrow
                        lightboxNextImage(1);
                    } else if(e.which == 27 && lightboxState) {        // ESC
                        showLightbox(-1);
                    }
                });
            }
        
            function preloadImages() {
                console.log("preloading images");
                // display loading screen
                $("#content").append('<div id="loadingScreen"><img scr="images/loading.gif" /></div>');
            
                // actually load images
                for(var i = 0; i < images.length; i++) {
                    var tmpImg = new Image();
                    tmpImg.src = images[i];
                }
            
                // hide loading screen
                $("#content").html('<div style="clear:both;"></div>');
            }
        
            /*    main method, gets called before second main method
             */
            function mainFirst() {
                // loginPopup
                $("#loginPopup").fadeOut(0);
                loginPopupState = false;
            
                initLightbox();
            
                // preload images, in Case they are not in the browser cache yet
                //preloadImages();
            }
        
            /*    second main method
             */
            function mainSecond() {
                // when in gallery, start sorting images
                if(<?php if(isset($_GET["hash"])) { echo "true"; } else { echo "false";} ?>) {
                    sortImages();
                }
            }
        
            $(document).ready(mainFirst);
            $(window).load(mainSecond);

HTML:
<!DOCTYPE html>
<html>
    <head>
        <title>Fotos</title>
        <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
        <meta charset="utf-8">
        <link rel="stylesheet" href="style.css" type="text/css" />
        <link rel="stylesheet" href="lightbox.min.css" type="text/css" />
        <script type="text/javascript" src="jquery-v1.11.3.js"></script>
    </head>
    <body>
        <div id="lightboxBackground" onClick="showLightbox(-1);">
            <div id="lightbox">
            
            </div>
        </div>
        <div id="header">
            <div class="headerItem left" id="galleryHeading"><?php if(isset($_GET["hash"])) { echo getGalleryName($_GET["hash"]); } ?> </div>
            <?php
                if(isset($_GET["hash"]) && downloadAllowed($_GET["hash"])) {
            ?>
                    <div class="headerItem left clickable" id="download" onClick="redirect('download');" >Diese Galerie herunterladen<b id="downloadIcon"></b></div>
            <?php
                }
                if(isset($_SESSION["Username"])) {
            ?>
                    <div class="headerItem right clickable" onClick="redirect('logout');" >Logout</div>
                    <div class="headerItem right clickable" onClick="redirect('management');">Verwaltung</div>
            <?php
                } else {
            ?>
                    <div class="headerItem right clickable" onClick="loginPopup();" >Login</div>
            <?php
                }
            ?>
            <div style="clear:both;"></div>
        </div>
        <?php include "loginform.php"; ?>
        <div id="content">
            <?php
                $content = getContent();
                include $content;
            ?>
            <div style="clear:both;"></div>
        </div>
        <div id="footer">
            <span id="footerText">&copy;</span>
        </div>
    </body>
</html>

PHP:
<?php
    function listPhotoURLs($dir) {
        if($dir == "" || $dir == -1) { return "no gal: ".$dir; }
        $photoList = "";
        $i = 1;
        $files = scandir($dir."/small");
        //print_r($files);
        if($files != null) {
            foreach($files as $file) {
                if($i > 2) {                                              // skip "." and ".."
                    $photoList .= "\"".$dir."/small/".$file."\",";
                }
                $i++;
            }
        }
        $photoList = substr($photoList, 0, -1);         // remove last comma
        return $photoList;
    }

    /*    @param $hash: Hashvalue
     *    @returns: Name of the gallery-directory, which the hashvalue links to
     */
    function getGalleryDir($hash) {
        // TODO: get data from db, include correct gallery
        switch($hash) {
            case "slo7":        return "galleries/Slowenien";
            case "rua5":        return "galleries/Ruanda";
        }
        return -1;
    }

    function downloadAllowed($hash) {
        // TODO: get data from db
        switch($hash) {
            case "slo7":        return true;
            case "rua5":        return true;
        }
        return false;
    }

    function getGalleryName($hash) {
        $nameFileFileName = getGalleryDir($hash)."/galName.txt";
        if($nameFileFileName == "-1/galName.txt") {
            return "";
        }
        $nameFile = fopen($nameFileFileName, "r");
        $galleryName = fread($nameFile, filesize($nameFileFileName));
        fclose($nameFile);
        if($galleryName == "") {
            return "";
        }
        return $galleryName;
    }

    function getGalleryDownloadPath() {
        if(isset($_GET["hash"])) {
            return getGalleryDir($_GET["hash"])."/download.zip";
        }
        return false;
    }

    function getContent() {
        // include content
    
        // link set -> try if link is allowed
        if(isset($_GET["lnk"])) {
            // management only allowed when user logged in
            if($_GET["lnk"] == "management" && isset($_SESSION["Username"])) {
                return "management.php";
            }
        }
    
        if(!isset($_GET["hash"])) {
            // nothing set, check if user logged in
        
            if(isset($_SESSION["Username"])) {
                // nothing set, user logged in -> show management site
                return "management.php";
            } else {
                // nothing set, no user logged in -> show login site
                return "loginform.php";
            }
        }
    }



    function mainf() {
        session_start();
    }

    mainf();
?>

CSS:
/* general */
@import url('https://fonts.googleapis.com/css?family=Abel');
@import url('https://fonts.googleapis.com/css?family=Roboto');

* {
    margin: 0;
    padding: 0;
    z-Index: 0;
}

body {
    background-color: #ccc;
    color: #111;
    font-family: 'Roboto', sans-serif;
}

#lightboxBackground {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100vh;
    background-color: rgba(0, 0, 0, 0.8);
    text-align: center;
    z-Index: 5;
}
#lightboxBackground #lightbox {
    height: 90vh;
    max-width: 95vw;
    display: inline-block;
    top: 5vh;
    left: 0;
    margin: 0 auto;
    margin-top: 5vh;
    padding: 5px;
    background-color: #888;
    z-Index: 6;
}
#lightboxBackground #lightbox #lightboxImage {
    height: 100%;
    overflow: hidden;
    z-Index: inherit;
}
#lightboxBackground #lightbox #lightboxImage img {
    width: inherit;
    height: inherit;
    z-Index: inherit;
}
#lightboxBackground #lightbox #lightboxImage #lightboxClickLeft,
  #lightboxBackground #lightbox #lightboxImage #lightboxClickRight {
    display: inline-block;
    width: 50%;
    height: 100%;
    z-Index: 7;
    position: relative;
    top: -90vh;
}
#lightboxBackground #lightbox #lightboxImage #lightboxClickLeft {
    float: left;
}
#lightboxBackground #lightbox #lightboxImage #lightboxClickRight {
    float: right;
}

#header {
    position: fixed;
    top: 0;
    left: 0;
    width: calc(100% - 60px);
    height: 60px;
    background-color: #eee;
    padding-left: 30px;
    padding-right: 30px;
    box-shadow: 0 0 8px rgba(0, 0, 0, 0.5);
    z-Index: 3;
}
#header .headerItem {
    display: inline-block;
    height: calc(100% - 40px);
    margin-left: 10px;
    margin-right: 10px;
    padding: 20px;
    color: #555;
}
#header .headerItem:hover {
    color: #111;
}
#header #galleryHeading {
    font-size: 22px;
    padding-top: 0;
    display: inline-block;
    position: absolute;
    top: 14px;
    color: #111;
}
#header #download {
    padding-right: 46px;
    margin-left: 179px;
}
#header .right {
    float: right;
}
#header .clickable {
    transition: 0.15s ease;
    -o-transition: 0.15s ease;
    -webkit-transition: 0.15s ease;
}
#header .clickable:hover {
    cursor: pointer;
    background-color: #ccc;
}
#header b#downloadIcon {
    display: inline-block;
    width: 26px;
    height: 26px;
    position: absolute;
    background-image: url("icons8-download-26-light.png");
    top: 16px;
    left: 432px;
    transition: 0.15s ease;
    -o-transition: 0.15s ease;
    -webkit-transition: 0.15s ease;
}
#header #download:hover b#downloadIcon {
    background-image: url("icons8-download-26-dark.png");
}

#loginPopup #loginPopupContainer {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100vh;
    background-color: rgba(0, 0, 0, 0.5);
    z-Index: 2;
}
#loginPopup #loginPopupContainer #loginPopupForm {
    width: 400px;
    height: 300px;
    margin: 0 auto;
    margin-top: 150px;
    padding: 40px;
    background-color: #eee;
    box-shadow: 0 0 8px rgba(0, 0, 0, 0.5);
    z-Index: inherit;
}
#loginPopup #loginPopupContainer #loginPopupForm span#loginFormHeading {
    display: block;
    margin: 0 auto;
    margin-bottom: 20px;
    font-size: 2em;
    z-Index: inherit;
}
#loginPopup #loginPopupContainer #loginPopupForm input {
    width: 390px;
    height: 30px;
    margin: 0 auto;
    margin-bottom: 15px;
    padding: 5px;
    border-radius: 2px;
    border: none;
    font-size: 20px;
    text-align: center;
    z-Index: inherit;
    transition: 0.15s ease;
    -o-transition: 0.15s ease;
    -webkit-transition: 0.15s ease;
}

#loginPopup #loginPopupContainer #loginPopupForm input[type=submit] {
    margin-top: 40px;
    height: 50px;
    box-shadow: 0 0 2px rgba(0, 0, 0, 0.7);
}
#loginPopup #loginPopupContainer #loginPopupForm input:focus,
  #loginPopup #loginPopupContainer #loginPopupForm input[type=submit]:hover {
    box-shadow: 0 0 8px rgba(0, 130, 255, 0.7);
}
#loginPopup #loginPopupContainer #loginPopupForm input[type=submit]:hover {
    cursor: pointer;
}


#content {
    display: flex;
    width: 80%;
    max-width: 1200px;
    margin: 0 auto;
    margin-top: 60px;
    padding-top: 10px;
    z-Index: 0;
}

/* VERSION WITH x COLUMNS OF IMAGES*/

#content .column {
    /*width: calc((100% / 3) - 1px);*/
    float: left;
    z-Index: inherit;
}
#content > .column {
    flex: 1;
}
#content .image {
    display: inline-block;
    margin: 10px;
    width: calc(100% - 20px);
    /*background-color: #000;*/
    z-Index: inherit;
}
#content .hover:hover {
    cursor: pointer;
}
#content .hover:hover > img {
    opacity: 0.5;
}
#content .image img {
    width: 100%;
    height: 100%;
    position: relative;
    vertical-align: middle;
    z-Index: inherit;
    transition: 0.15s ease;
    -o-transition: 0.15s ease;
    -webkit-transition: 0.15s ease;
}

#footer {
    width: 100%;
    height: 45px;
    margin-top: 15px;
    background-color: #eee;
    bottom: 0;
    color: #555;
}
#footer span#footerText {
    display: block;
    text-align: center;
    position: relative;
    top: 15px;
}


/* Smartphone */
@media screen and (max-width: 549px) {
    #header {
        width: calc(100% - 30px);
        padding-left: 0;
    }
    #header .headerItem {
        display: none;
    }
    #header #galleryHeading {
        display: block;
    }
 
    #content {
        width: 100%
    }
    #content .image {
        margin-top: 5px;
        margin-bottom: 5px;
    }
}


/* Tablet */
@media screen and (min-width: 550px) and (max-width: 1024px) {
    /*#header {
        width: calc(100% - 30px);
        padding-left: 0;
    }*/
    #header #download {
        display: none;
    }
    #content {
        width: calc(100% - 10px);
        padding: 5px;
    }
}
 

Anhänge

  • photosite5.jpg
    photosite5.jpg
    52,7 KB · Aufrufe: 263
Zuletzt bearbeitet:
Javascript:
for(var i = 0; i < images.length; i++) {
                    var cc = getShortestColumnIndex(ch);
                    if(includeLightbox) {
                        var imageHTML = '<div class="image hover"><img src="' + images[i] + '" title="image-' + (i+1) + '" onClick="showLightbox(' + i + ');" /></div>';
                    } else {
                        var imageHTML = '<div class="image"><img src="' + images[i] + '" title="image-' + (i+1) + '" /></div>';
                    }
                    $("#column" + cc).append(imageHTML);
                    ch[cc] += getImageHeight(images[i], conWidth, columns);
                }

Ich vermute dann mal das der Fehler irgendwo da drin passiert, würde ja auch erklären warum die nicht im DOM erscheinen. Vielleicht hat cc einen Wert der nicht benutzt werden kann, entweder gibt die Funktion dir das false zurück oder eine Zahl die zu hoch ist.
In der Schleife mal per console.log/Debugger gucken was in den Variablen steht bzw. was die Methoden dir zurückgeben.

Kann auch sein das getImageHeight dir den Strich durch die Rechnung macht. Dir sollte klar sein dass das laden von Bildern asynchron passiert und nicht während des Downloads vom Bild der Code still steht. Verhält sich also nicht wie ein PHP Curl wo alles auf Stop gestellt wird.
Javascript:
tmpImage.src = imagePath;
var rawImgWidth = tmpImage.width;
Während das Bild heruntergeladen wird wird die nächste Zeile ausgeführt, egal ob das Bild fertig geladen wurde oder nicht. Erklärt auch warum es beim Cache funktioniert, da sind die Maße vom Bild ja sofort vorhanden.
Dafür müsstest du so etwas in der Art machen:

Javascript:
function getImageHeight(imagePath, contentWidth, columns) {
                var tmpImage = new Image();
                tmpImage.src = imagePath;
                tmpImage.onload = function() {
                    // größen returnen weil jetzt das Bild da ist
                }
            }

Das wird aber so nicht funktionieren, dafür bräuchtest du etwas wissen in Promises / Asynchronen Funktionen, würde aber den Ramen sprengen.


Was wohl auch ein Problem verursachen kann: du hast vor dem aufrufen von sortImages() noch gar keine Bilder in deiner Galerie. Heißt auch das $(window).load nicht aufgerufen wird nachdem alle Galeriebilder da sind, sondern schon vorher. Die Bilder werden ja erst nachher per Javascript hinzugefügt und geladen.
Da wäre es eine Option dir per PHP die <img src="" />-Elemente auszuprinten und per CSS zu verstecken, dann würde das load() zum richtigem Zeitpunkt aufgerufen werden.
Dann würde getImageHeight nicht asyncron sein müssen und deine preload-Funktion wird überflüssig.

Musst du mal durchtesten, hatte eigentlich auf einen Link gehofft wo man das ganze in Aktion sehen kann. Immer noch die schnellste Art Fehler zu finden ;)
 

Ähnliche Themen

Zurück
Oben