首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 圖片目錄分頁顯示:掃描、排序、分頁完整實作

     編輯:PHP 綜合知識

PHP 圖片相簿分頁顯示完整實作

掃描目錄圖片、按修改時間倒序、分頁顯示是相簿、圖庫常見需求。本文提供完整單檔案實作,含圖片過濾、分頁邏輯、前端顯示,並分析安全性與效能優化方向。

完整單檔案實作

<!DOCTYPE html>
<html lang="zh-TW">
<head>
    <meta charset="UTF-8">
    <title>圖片相簿分頁顯示</title>
    <style>
        .gallery { max-width: 90%; margin: 20px auto; border: 1px solid #ccc; text-align: center; padding: 20px; }
        .image-item { display: inline-block; margin: 10px; text-align: center; vertical-align: top; }
        .image-item img { max-width: 200px; max-height: 200px; border: 1px solid #ddd; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
        .image-item .filename { margin-top: 5px; font-size: 0.9em; color: #666; word-break: break-all; }
        .pagination { margin: 20px 0; }
        .pagination a, .pagination span { display: inline-block; padding: 8px 12px; margin: 0 3px; border: 1px solid #ddd; text-decoration: none; color: #333; border-radius: 3px; }
        .pagination a:hover { background: #007bff; color: white; border-color: #007bff; }
        .pagination .current { background: #007bff; color: white; border-color: #007bff; }
        .pagination .disabled { color: #999; border-color: #ddd; pointer-events: none; }
    </style>
</head>
<body>

<div class="gallery">
    <?php
    // === 設定區 ===
    $imagesPerPage = 10;           // 每頁圖片數
    $imageDir = 'upload';          // 圖片目錄 (相對路徑)
    $allowedExt = ['gif', 'jpg', 'jpeg', 'png', 'webp', 'bmp'];  // 允許副檔名
    
    // === 取得頁碼 ===
    $page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
    
    // === 掃描圖片 ===
    $images = [];
    $handle = @opendir($imageDir);
    
    if ($handle) {
        while (($file = readdir($handle)) !== false) {
            $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
            if (in_array($ext, $allowedExt, true) && is_file($imageDir . DIRECTORY_SEPARATOR . $file)) {
                $fullPath = $imageDir . DIRECTORY_SEPARATOR . $file;
                $images[] = [
                    'name' => $file,
                    'path' => $fullPath,
                    'mtime' => filemtime($fullPath),
                    'size' => filesize($fullPath),
                    'ext' => $ext
                ];
            }
        }
        closedir($handle);
    }
    
    // === 排序:修改時間倒序 (最新在前) ===
    usort($images, fn($a, $b) => $b['mtime'] <=> $a['mtime']);
    
    // === 分頁計算 ===
    $totalImages = count($images);
    $totalPages = max(1, (int)ceil($totalImages / $imagesPerPage));
    $page = min($page, $totalPages);  // 修正超出範圍頁碼
    $offset = ($page - 1) * $imagesPerPage;
    $pageImages = array_slice($images, $offset, $imagesPerPage);
    
    // === 顯示圖片 ===
    if (empty($pageImages)) {
        echo '<p style="color: #999; padding: 40px;">該目錄無圖片或無讀取權限</p>';
    } else {
        foreach ($pageImages as $img) {
            $safeName = htmlspecialchars($img['name']);
            $safePath = htmlspecialchars($img['path']);
            $mtime = date('Y-m-d H:i:s', $img['mtime']);
            $size = formatBytes($img['size']);
            
            echo "<div class='image-item'>";
            echo "<img src='{$safePath}' alt='{$safeName}' loading='lazy'>";
            echo "<div class='filename' title='{$mtime} | {$size}'>{$safeName}</div>";
            echo "</div>";
        }
    }
    
    // === 分頁導航 ===
    if ($totalPages > 1) {
        echo "<div class='pagination'>";
        
        // 首頁
        if ($page > 1) {
            echo "<a href='?page=1'>首頁</a>";
            echo "<a href='?page=" . ($page - 1) . "'>上一頁</a>";
        } else {
            echo "<span class='disabled'>首頁</span>";
            echo "<span class='disabled'>上一頁</span>";
        }
        
        // 頁碼範圍 (顯示當前頁前後各 2 頁)
        $start = max(1, $page - 2);
        $end = min($totalPages, $page + 2);
        
        if ($start > 1) echo "<span class='disabled'>...</span>";
        
        for ($i = $start; $i <= $end; $i++) {
            if ($i === $page) {
                echo "<span class='current'>{$i}</span>";
            } else {
                echo "<a href='?page={$i}'>{$i}</a>";
            }
        }
        
        if ($end < $totalPages) echo "<span class='disabled'>...</span>";
        
        // 末頁
        if ($page < $totalPages) {
            echo "<a href='?page=" . ($page + 1) . "'>下一頁</a>";
            echo "<a href='?page={$totalPages}'>末頁</a>";
        } else {
            echo "<span class='disabled'>下一頁</span>";
            echo "<span class='disabled'>末頁</span>";
        }
        
        echo "</div>";
    }
    
    // 輔助函數:位元組格式化
    function formatBytes($bytes, $precision = 2) {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $bytes = max($bytes, 0);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);
        $bytes /= (1024 ** $pow);
        return round($bytes, $precision) . ' ' . $units[$pow];
    }
    ?>
</div>

</body>
</html>

核心邏輯解析

1. 安全掃描與過濾

<?php
// ✅ 使用 pathinfo 取得副檔名 (避免多點檔名誤判)
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));

// ✅ 嚴格白名單驗證
$allowedExt = ['gif', 'jpg', 'jpeg', 'png', 'webp', 'bmp'];
if (!in_array($ext, $allowedExt, true)) continue;  // true = 嚴格比較

// ✅ 確認是檔案非目錄 (防止目錄遍歷)
if (!is_file($fullPath)) continue;

// ✅ 路徑安全:使用 DIRECTORY_SEPARATOR 而非硬編碼 /
$fullPath = $imageDir . DIRECTORY_SEPARATOR . $file;
?>

2. 分頁邏輯細節

<?php
// 總頁數計算 (向上取整)
$totalPages = max(1, (int)ceil($totalImages / $imagesPerPage));

// 頁碼修正 (防止負數、超出範圍)
$page = max(1, min($page, $totalPages));

// 偏移量計算
$offset = ($page - 1) * $imagesPerPage;

// 陣列切片 (不修改原陣列)
$pageImages = array_slice($images, $offset, $imagesPerPage);
?>

3. 分頁導航 UX 優化

  • 首頁/末頁固定:使用者可快速跳轉
  • 當前頁高亮:視覺回饋當前位置
  • 省略號折疊:頁數過多時僅顯示當前頁附近
  • 禁用狀態:首頁時隱藏「上一頁」連結
  • SEO 友善:使用 `` 連結而非 JS 跳轉

進階功能擴充

圖片資訊快取 (避免重複 stat 系統調用)

<?php
$cacheFile = __DIR__ . '/.image_cache.php';
$cacheTime = 300;  // 5 分鐘

if (file_exists($cacheFile) && time() - filemtime($cacheFile) < $cacheTime) {
    $images = include $cacheFile;
} else {
    // 掃描邏輯...
    // 產生快取檔案
    $cacheContent = '

縮圖生成與顯示

<?php
function getThumbnail($imagePath, $maxWidth = 200, $maxHeight = 200, $quality = 80) {
    $thumbDir = dirname($imagePath) . '/.thumbs';
    if (!is_dir($thumbDir)) mkdir($thumbDir, 0755, true);
    
    $thumbPath = $thumbDir . '/' . basename($imagePath);
    
    // 若縮圖不存在或原圖較新,重新生成
    if (!file_exists($thumbPath) || filemtime($thumbPath) < filemtime($imagePath)) {
        $srcInfo = getimagesize($imagePath);
        if (!$srcInfo) return $imagePath;  // 非圖片
        
        $srcW = $srcInfo[0];
        $srcH = $srcInfo[1];
        $ratio = min($maxWidth / $srcW, $maxHeight / $srcH);
        $dstW = (int)($srcW * $ratio);
        $dstH = (int)($srcH * $ratio);
        
        $srcImg = match($srcInfo[2]) {
            IMAGETYPE_JPEG => imagecreatefromjpeg($imagePath),
            IMAGETYPE_PNG => imagecreatefrompng($imagePath),
            IMAGETYPE_GIF => imagecreatefromgif($imagePath),
            IMAGETYPE_WEBP => imagecreatefromwebp($imagePath),
            default => null
        };
        
        if ($srcImg) {
            $dstImg = imagecreatetruecolor($dstW, $dstH);
            
            // 保持透明度 (PNG/GIF)
            if ($srcInfo[2] === IMAGETYPE_PNG || $srcInfo[2] === IMAGETYPE_GIF) {
                imagealphablending($dstImg, false);
                imagesavealpha($dstImg, true);
                $transparent = imagecolorallocatealpha($dstImg, 255, 255, 255, 127);
                imagefilledrectangle($dstImg, 0, 0, $dstW, $dstH, $transparent);
            }
            
            imagecopyresampled($dstImg, $srcImg, 0, 0, 0, 0, $dstW, $dstH, $srcW, $srcH);
            
            switch ($srcInfo[2]) {
                case IMAGETYPE_JPEG: imagejpeg($dstImg, $thumbPath, $quality); break;
                case IMAGETYPE_PNG: imagepng($dstImg, $thumbPath); break;
                case IMAGETYPE_GIF: imagegif($dstImg, $thumbPath); break;
                case IMAGETYPE_WEBP: imagewebp($dstImg, $thumbPath, $quality); break;
            }
            
            imagedestroy($srcImg);
            imagedestroy($dstImg);
        }
    }
    
    return file_exists($thumbPath) ? $thumbPath : $imagePath;
}
?>

無限捲動 (AJAX 載入更多)

<script>
// 前端 JS
let currentPage = 1;
let loading = false;

window.addEventListener('scroll', () => {
    if (loading) return;
    if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 500) {
        loadMore();
    }
});

async function loadMore() {
    loading = true;
    currentPage++;
    
    try {
        const response = await fetch(`?page=${currentPage}&ajax=1`);
        const html = await response.text();
        
        if (html.trim()) {
            document.querySelector('.gallery').insertAdjacentHTML('beforeend', html);
        } else {
            currentPage--;  // 無更多資料
        }
    } catch (e) {
        console.error(e);
        currentPage--;
    }
    
    loading = false;
}
</script>

<?php
// 後端支援 AJAX
if (isset($_GET['ajax'])) {
    // 僅輸出圖片項目 HTML,不含分頁導航
    foreach ($pageImages as $img) { /* ... */ }
    exit;
}
?>

效能與安全檢查清單

項目風險對策
目錄遍歷攻擊者輸入 `../../etc/passwd`固定目錄、不接受使用者輸入路徑、realpath 驗證
檔案型別偽裝上傳 `shell.php.jpg`白名單副檔名、getimagesize 驗證、儲存時重命名
大量檔案效能單目錄超過 1 萬檔案掃描緩慢分級目錄 (年/月/日)、資料庫索引、快取
記憶體耗盡大量圖片同時載入分頁、懶載入 (loading="lazy")、縮圖
XSS檔名含惡意腳本htmlspecialchars 輸出編碼、Content-Security-Policy

此實作適合中小型相簿專案。大規模應用建議:資料庫儲存檔案中繼資料、物件儲存 (S3/OSS)、CDN 分發、非同步縮圖處理佇列。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved