Moin,
ich habe mal
ChatGPT gebeten das Script mit
Sprunganker auf die Block
id="galerie" zu erstellen. Das hat mehrere Anläufe gebraucht, bis es geklappt hat. Jetzt geht das, denn sonst sieht das nicht gut aus bei größeren Galerien. Sehen kannst Du es wenn Du die Anzahl der Bilder oben auf 6 stellst (wie bei diesem Link bereits eingearbeitet), damit zwei Seiten entstehen:
https://www.mobirise-tutorials.com/Tuto ... =1#galerie
Code: Alles auswählen
https://www.mobirise-tutorials.com/Tutorials-3/Galerie-Upload.php?gal_per_page=6&gal_page=1#galerie
Hier Dein
umgeschriebenes Skript "galerie.php", so wie ich es jetzt verwende.
Keine Ahnung, ob Du das so in Deinen Download packen willst, denn es geht ja darum das
korrekt in Mobirise einzubauen. So wie es bisher ist, fehlt der Sprunganker. Voraussetzung ist den Mobirise-Block mit dem PHP-Include oben links auch so zu nennen, denn da ist dann die
Sprunganker-ID:
galerie
Code: Alles auswählen
<?php
// ========== KONFIG ==========
define('UPLOAD_DIR', 'uploads/');
define('THUMB_DIR', 'uploads/thumbs/');
// Optional: Wenn du die Links auf einen *bestimmten* Seitenpfad zwingen willst,
// kannst du hier z.B. '/Tutorials-3/Galerie-Upload.php' setzen.
// Leer lassen => es wird automatisch die aktuelle Seite (PHP_SELF) verwendet.
const GALLERY_SELF_PATH = ''; // z.B. '/deine-seite.php'
// ========== HILFSFUNKTIONEN ==========
function gallery_self_path(): string {
if (GALLERY_SELF_PATH !== '') return GALLERY_SELF_PATH;
// Sicherer, basisrelativer Pfad zur aktuell ausgeführten Seite
$path = parse_url($_SERVER['PHP_SELF'] ?? '', PHP_URL_PATH);
return $path ?: '/';
}
// Baut eine Galerie-URL: NUR die Galerie-Parameter, sonst nichts (robust gegen Konflikte)
function gallery_url_clean(int $page, int $perPage): string {
$path = gallery_self_path();
$query = http_build_query([
'gal_page' => max(1, $page),
'gal_per_page' => max(6, min(50, $perPage)),
]);
$url = $path . '?' . $query . '#galerie';
return htmlspecialchars($url, ENT_QUOTES, 'UTF-8');
}
// Bilder holen
function getGalleryImages(): array {
$images = [];
if (!is_dir(UPLOAD_DIR)) return $images;
$files = glob(UPLOAD_DIR . '*.{jpg,jpeg,png,gif,webp}', GLOB_BRACE) ?: [];
foreach ($files as $file) {
$filename = basename($file);
$images[] = [
'filename' => $filename,
'full_path' => $file,
'thumb_path' => $file, // Original als Thumb
'size' => @filesize($file) ?: 0,
'modified' => @filemtime($file) ?: time(),
];
}
usort($images, fn($a,$b) => strnatcmp($a['filename'], $b['filename']));
return $images;
}
// ========== LOGIK ==========
$galleryImages = getGalleryImages();
$imagesPerPage = isset($_GET['gal_per_page']) ? max(6, min(50, (int)$_GET['gal_per_page'])) : 12;
$currentPage = isset($_GET['gal_page']) ? max(1, (int)$_GET['gal_page']) : 1;
$totalImages = count($galleryImages);
$totalPages = max(1, (int)ceil($totalImages / $imagesPerPage));
$currentPage = min($currentPage, $totalPages);
$startIndex = ($currentPage - 1) * $imagesPerPage;
$currentImages = array_slice($galleryImages, $startIndex, $imagesPerPage);
?>
<style>
/* ... (dein bestehendes CSS, unverändert) ... */
.gallery-container img { image-rendering:-webkit-optimize-contrast!important; image-rendering:optimize-contrast!important; image-rendering:crisp-edges!important; -ms-interpolation-mode:nearest-neighbor!important; transform:none!important; filter:none!important; }
.gallery-container img { max-width:100%!important; height:auto!important; display:block!important; }
body{background-color:white;}
.gallery-container{max-width:1400px;margin:0 auto;padding:20px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;scroll-margin-top:90px;}
.gallery-header{text-align:center;margin-bottom:40px;}
.gallery-title{font-size:2.5rem;color:#333;margin-bottom:10px;background:linear-gradient(45deg,#667eea,#764ba2);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;}
.gallery-stats{color:#666;font-size:1.1rem;margin-bottom:20px;}
.gallery-controls{display:flex;justify-content:center;align-items:center;gap:20px;margin-bottom:30px;flex-wrap:wrap;}
.per-page-selector{display:flex;align-items:center;gap:10px;}
.per-page-selector select{padding:8px 12px;border:2px solid #e1e5e9;border-radius:8px;font-size:14px;background:white;cursor:pointer;}
.gallery-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:25px;margin-bottom:40px;}
.gallery-item{background:white;border-radius:15px;overflow:hidden;box-shadow:0 4px 20px rgba(0,0,0,.08);transition:all .3s ease;cursor:pointer;}
.gallery-item:hover{transform:translateY(-8px);box-shadow:0 12px 40px rgba(0,0,0,.15);}
.gallery-item img{width:100%;height:250px;object-fit:cover;transition:transform .3s ease;}
.gallery-item:hover img{transform:scale(1.1)!important;}
.gallery-item-info{padding:15px;text-align:center;}
.image-title{font-weight:600;color:#333;font-size:14px;margin-bottom:5px;word-break:break-word;}
.image-meta{font-size:12px;color:#888;}
.pagination{display:flex;justify-content:center;align-items:center;gap:10px;margin-top:40px;flex-wrap:wrap;}
.pagination a,.pagination span{padding:10px 15px;text-decoration:none;border:2px solid #e1e5e9;border-radius:8px;color:#333;font-weight:500;transition:all .3s ease;min-width:45px;text-align:center;}
.pagination a:hover{background:linear-gradient(45deg,#667eea,#764ba2);color:white;border-color:transparent;}
.pagination .current{background:linear-gradient(45deg,#667eea,#764ba2);color:white;border-color:transparent;}
.pagination .disabled{opacity:.5;cursor:not-allowed;}
.pagination .disabled:hover{background:white;color:#333;border-color:#e1e5e9;}
.lightbox{display:none;position:fixed;inset:0;background:rgba(0,0,0,.95);z-index:10000;cursor:pointer;justify-content:center;align-items:center;}
.lightbox-content{display:flex;flex-direction:column;align-items:center;max-width:95vw;max-height:95vh;text-align:center;}
.lightbox img{max-width:100%;max-height:100%;object-fit:contain;border-radius:10px;box-shadow:0 0 50px rgba(0,0,0,.5);}
.lightbox-info{color:white;margin-top:20px;font-size:16px;}
.lightbox-close{position:absolute;top:20px;right:30px;color:white;font-size:40px;cursor:pointer;z-index:10001;transition:opacity .3s ease;}
.lightbox-close:hover{opacity:.7;}
.lightbox-nav{position:absolute;top:50%;transform:translateY(-50%);color:white;font-size:30px;cursor:pointer;background:rgba(0,0,0,.5);padding:20px;border-radius:50%;transition:all .3s ease;user-select:none;}
.lightbox-nav:hover{background:rgba(0,0,0,.8);}
.lightbox-prev{left:30px;}
.lightbox-next{right:30px;}
.empty-gallery{text-align:center;padding:80px 20px;color:#666;}
.empty-gallery h3{font-size:1.5rem;margin-bottom:10px;}
@media(max-width:768px){.gallery-container{padding:15px}.gallery-title{font-size:2rem}.gallery-grid{grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:20px}.gallery-controls{flex-direction:column;gap:15px}.pagination{gap:5px}.pagination a,.pagination span{padding:8px 12px;font-size:14px}.lightbox-nav{font-size:24px;padding:15px}.lightbox-close{font-size:30px;top:15px;right:20px}}
@media(max-width:480px){.gallery-grid{grid-template-columns:1fr;gap:15px}.lightbox-nav{display:none}}
@media(min-width:769px){.lightbox img{max-width:80vw;max-height:80vh}}
</style>
<div id="galerie" class="gallery-container">
<div class="gallery-header">
<h1 class="gallery-title">Bilder</h1>
<div class="gallery-stats">
<?php echo $totalImages; ?> Bilder
<?php if ($totalPages > 1): ?> • Seite <?php echo $currentPage; ?> von <?php echo $totalPages; ?> <?php endif; ?>
</div>
</div>
<?php if ($totalImages > 0): ?>
<div class="gallery-controls">
<div class="per-page-selector">
<label for="per-page">Bilder pro Seite:</label>
<select id="per-page" onchange="changePerPage(this.value)">
<option value="6" <?php echo $imagesPerPage == 6 ? 'selected' : ''; ?>>6</option>
<option value="12" <?php echo $imagesPerPage == 12 ? 'selected' : ''; ?>>12</option>
<option value="24" <?php echo $imagesPerPage == 24 ? 'selected' : ''; ?>>24</option>
<option value="48" <?php echo $imagesPerPage == 48 ? 'selected' : ''; ?>>48</option>
</select>
</div>
</div>
<div class="gallery-grid">
<?php foreach ($currentImages as $index => $image): ?>
<div class="gallery-item" onclick="openLightbox(event, <?php echo $startIndex + $index; ?>)">
<img src="<?php echo htmlspecialchars($image['thumb_path']); ?>" alt="<?php echo htmlspecialchars($image['filename']); ?>" loading="lazy">
<div class="gallery-item-info">
<div class="image-title"><?php echo htmlspecialchars($image['filename']); ?></div>
<div class="image-meta"><?php echo date('d.m.Y', $image['modified']); ?> • <?php echo number_format($image['size'] / 1024, 1); ?> KB</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php if ($totalPages > 1): ?>
<div class="pagination" id="gal-pagination">
<?php if ($currentPage > 1): ?>
<a href="<?php echo gallery_url_clean(1, $imagesPerPage); ?>" data-gal-page="1">«</a>
<a href="<?php echo gallery_url_clean($currentPage-1, $imagesPerPage); ?>" data-gal-page="<?php echo $currentPage-1; ?>">‹</a>
<?php else: ?>
<span class="disabled">«</span>
<span class="disabled">‹</span>
<?php endif; ?>
<?php
$start = max(1, $currentPage - 2);
$end = min($totalPages, $currentPage + 2);
if ($start > 1) {
echo '<a href="'.gallery_url_clean(1, $imagesPerPage).'" data-gal-page="1">1</a>';
if ($start > 2) echo '<span>...</span>';
}
for ($i = $start; $i <= $end; $i++) {
if ($i == $currentPage) {
echo '<span class="current">'.$i.'</span>';
} else {
echo '<a href="'.gallery_url_clean($i, $imagesPerPage).'" data-gal-page="'.$i.'">'.$i.'</a>';
}
}
if ($end < $totalPages) {
if ($end < $totalPages - 1) echo '<span>...</span>';
echo '<a href="'.gallery_url_clean($totalPages, $imagesPerPage).'" data-gal-page="'.$totalPages.'">'.$totalPages.'</a>';
}
?>
<?php if ($currentPage < $totalPages): ?>
<a href="<?php echo gallery_url_clean($currentPage+1, $imagesPerPage); ?>" data-gal-page="<?php echo $currentPage+1; ?>">›</a>
<a href="<?php echo gallery_url_clean($totalPages, $imagesPerPage); ?>" data-gal-page="<?php echo $totalPages; ?>">»</a>
<?php else: ?>
<span class="disabled">›</span>
<span class="disabled">»</span>
<?php endif; ?>
</div>
<?php endif; ?>
<?php else: ?>
<div class="empty-gallery">
<h3>Noch keine Bilder verfügbar</h3>
<p>Die Galerie ist momentan leer.</p>
</div>
<?php endif; ?>
</div>
<div id="lightbox" class="lightbox" onclick="closeLightbox()">
<span class="lightbox-close" onclick="closeLightbox()">×</span>
<div class="lightbox-prev lightbox-nav" onclick="event.stopPropagation(); prevImage()">‹</div>
<div class="lightbox-next lightbox-nav" onclick="event.stopPropagation(); nextImage()">›</div>
<div class="lightbox-content">
<img id="lightbox-img" src="" alt="">
<div id="lightbox-info" class="lightbox-info"></div>
</div>
</div>
<script>
// Daten
const allImages = <?php echo json_encode($galleryImages); ?>;
let currentLightboxIndex = 0;
// Lightbox
function openLightbox(e, idx){ e.stopPropagation(); currentLightboxIndex = idx; updateLightbox(); document.getElementById('lightbox').style.display='flex'; document.body.style.overflow='hidden'; }
function closeLightbox(){ document.getElementById('lightbox').style.display='none'; document.body.style.overflow='auto'; }
function updateLightbox(){
const img = allImages[currentLightboxIndex]; if(!img) return;
document.getElementById('lightbox-img').src = img.full_path;
document.getElementById('lightbox-info').innerHTML =
'<strong>'+img.filename+'</strong><br>'+
new Date(img.modified*1000).toLocaleDateString('de-DE')+' • '+(img.size/1024).toFixed(1)+' KB';
}
function nextImage(){ if(currentLightboxIndex < allImages.length-1){ currentLightboxIndex++; updateLightbox(); } }
function prevImage(){ if(currentLightboxIndex > 0){ currentLightboxIndex--; updateLightbox(); } }
// Per-Page
function changePerPage(value){
const url = new URL(window.location.href);
url.searchParams.set('gal_per_page', value);
url.searchParams.set('gal_page', '1');
url.hash = 'galerie';
window.location = url.toString();
}
// Onload: Hash beachten
window.onload = function(){
document.getElementById('lightbox').style.display='none';
if (location.hash === '#galerie') {
const el = document.getElementById('galerie');
if (el) el.scrollIntoView({ behavior: 'instant', block: 'start' });
}
};
// Tastatur
document.addEventListener('keydown', e=>{
const lb = document.getElementById('lightbox');
if (lb.style.display === 'flex') {
if (e.key === 'Escape') closeLightbox();
if (e.key === 'ArrowLeft') prevImage();
if (e.key === 'ArrowRight') nextImage();
}
});
// Touch/Swipe
let touchStartX=0,touchEndX=0;
document.getElementById('lightbox').addEventListener('touchstart', e=>{ touchStartX = e.changedTouches[0].screenX; });
document.getElementById('lightbox').addEventListener('touchend', e=>{
touchEndX = e.changedTouches[0].screenX;
const d = touchEndX - touchStartX; if (Math.abs(d) > 50) { d > 0 ? prevImage() : nextImage(); }
});
// Zusätzliche Absicherung: Pagination-Klicks per JS setzen (falls ein <base> o.ä. stört)
document.getElementById('gal-pagination')?.addEventListener('click', function(e){
const a = e.target.closest('a[data-gal-page]');
if (!a) return;
// Wenn ein Theme/Script das Link-Ziel verbiegt, erzwingen wir unseren Weg:
e.preventDefault();
const url = new URL(window.location.href);
url.searchParams.set('gal_page', a.getAttribute('data-gal-page'));
url.searchParams.set('gal_per_page', '<?php echo $imagesPerPage; ?>');
url.hash = 'galerie';
window.location = url.toString();
});
// Optionales Lazy Loading (nur wenn data-src vorhanden)
if ('IntersectionObserver' in window) {
const obs = new IntersectionObserver((entries,o)=>entries.forEach(en=>{
if (en.isIntersecting) {
const img = en.target;
if (img.dataset && img.dataset.src) { img.src = img.dataset.src; img.classList.remove('lazy'); o.unobserve(img); }
}
}));
document.querySelectorAll('img[data-src]').forEach(img=>obs.observe(img));
}
</script>
Hier die
Erklärung von ChatGPT dazu:
ChatGPT hat geschrieben:
Was das Problem war
Parameter-Konflikt
Dein ursprüngliches Skript verwendete ?page=... und ?per_page=....
Mobirise verwendet aber intern auch die Parameter page und per_page in manchen Blöcken oder beim internen Routing.
Wenn dein Include auf einer Mobirise-Seite eingebettet war, kollidierten diese Parameter.
Ergebnis:
Dein Link änderte zwar ?page=2
Aber Mobirise interpretierte diesen Wert selbst – und lieferte weiter Seite 1 der Galerie zurück.
Doppelte IDs & Hash-Verhalten
Dein Mobirise-Block und das Galerie-Skript hatten beide id="galerie".
Das war kein direkter Fehler, aber Mobirise hat auf Klicks mit JavaScript gescrollt und das Neuladen der Galerie teilweise blockiert.
Basis-URL & <base href>
Mobirise setzt in vielen Projekten <base href="..."> in den <head>.
Dadurch haben relative Links wie ?page=2 manchmal nicht auf die gleiche Seite gezeigt, sondern wurden durch die Base-URL umgeleitet.
Teilweise blieb man deshalb visuell auf Seite 1 hängen, obwohl sich die URL änderte.
Was wir geändert haben
Eigene Parameter verwendet
Statt page / per_page nehmen wir jetzt gal_page und gal_per_page.
Diese sind eindeutig und können nicht mit Mobirise kollidieren.
Links explizit auf dieselbe Seite gesetzt
Alle Paginierungs-Links werden jetzt dynamisch mit $_SERVER['PHP_SELF'] gebaut.
Dadurch zeigen sie immer auf das richtige PHP-Skript – egal, wie Mobirise-URLs, Includes oder Unterverzeichnisse strukturiert sind.
Hash #galerie am Ende
Der Hash sorgt dafür, dass der Browser nach dem Reload automatisch zum Galerie-Bereich springt.
Das machen wir sauber und konsistent in allen Links.
JavaScript-Fallback eingebaut
Falls Mobirise per JavaScript einen Link-Click überschreibt, fängt unser Skript den Klick ab und setzt die URL aktiv neu.
Damit erzwingen wir, dass wirklich die nächste Galerie-Seite geladen wird.
In einfachen Worten
Vorher:
→ Klick auf „Seite 2“ → Mobirise fing ?page=2 ab → Galerie blieb auf Seite 1 → Sprunganker funktionierte trotzdem, weil der Hash ja da war.
Jetzt:
→ Klick auf „Seite 2“ → unser Link geht mit eigenen Parametern → keine Kollision mehr → PHP lädt Seite 2 → Browser springt zur Galerie.