Cara Membuat Upload Gambar Tanpa Reload Menggunakan PHP dan AJAX

Cara Membuat Upload Gambar Tanpa Reload Menggunakan PHP dan AJAX
Kucatat.com,

Mengelola gambar dengan baik merupakan bagian penting dalam pengembangan website. Ukuran gambar yang terlalu besar dapat memperlambat proses upload, memperberat penyimpanan, dan memengaruhi kecepatan halaman ketika gambar ditampilkan kepada pengunjung.

Salah satu solusinya adalah membuat sistem upload gambar menggunakan PHP dan AJAX, kemudian memproses gambar dengan GD Library untuk menghasilkan ukuran yang lebih kecil serta thumbnail secara otomatis.

Teknik ini cocok diterapkan pada website berita, blog, galeri foto, katalog produk, dan marketplace.

1. Memahami Alur Upload Gambar

Sistem yang akan dibuat bekerja melalui beberapa tahap:

Baca Juga:

  1. Pengguna memilih gambar melalui form.
  2. JavaScript mengirim file ke server menggunakan AJAX.
  3. PHP memvalidasi file yang diterima.
  4. GD Library membaca dan memproses gambar.
  5. Sistem membuat gambar hasil resize dan thumbnail.
  6. File disimpan ke folder server.
  7. Server mengirim respons JSON agar hasil upload dapat ditampilkan tanpa memuat ulang halaman.

Dalam contoh ini, kita menggunakan fetch() dan FormData sebagai mekanisme AJAX modern. Tidak diperlukan jQuery.

2. Menyiapkan Struktur Folder

Buat struktur proyek seperti berikut:

upload-gambar/
โ”œโ”€โ”€ index.php
โ”œโ”€โ”€ upload.php
โ””โ”€โ”€ uploads/
    โ”œโ”€โ”€ original/
    โ”œโ”€โ”€ resized/
    โ””โ”€โ”€ thumbnails/

Folder original menyimpan gambar asli, resized menyimpan gambar hasil resize, sedangkan thumbnails menyimpan gambar berukuran kecil.

Pastikan PHP memiliki izin untuk menulis ke folder uploads. Pada server produksi, sebaiknya folder penyimpanan tidak mengizinkan file PHP dieksekusi.

Baca Juga:

Aktifkan juga ekstensi GD pada PHP yang digunakan.

3. Membuat Form Upload dengan HTML

Buat file index.php dan tambahkan kode berikut.

<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Upload Gambar PHP AJAX</title>

    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 720px;
            margin: 40px auto;
            padding: 0 16px;
        }

        form {
            padding: 20px;
            border: 1px solid #ddd;
            border-radius: 8px;
        }

        button {
            margin-top: 12px;
            padding: 10px 16px;
            cursor: pointer;
        }

        #result img {
            max-width: 100%;
            height: auto;
            margin-top: 12px;
        }

        #message {
            margin-top: 16px;
        }
    </style>
</head>
<body>

    <h1>Upload Gambar</h1>

    <form id="uploadForm">
        <label for="gambar">Pilih gambar JPG, PNG, atau WebP:</label>

        <input
            type="file"
            name="gambar"
            id="gambar"
            accept="image/jpeg,image/png,image/webp"
            required
        >

        <button type="submit" id="uploadButton">
            Upload Gambar
        </button>
    </form>

    <p id="message" role="status" aria-live="polite"></p>
    <div id="result"></div>

    <script>
        const form = document.getElementById("uploadForm");
        const button = document.getElementById("uploadButton");
        const message = document.getElementById("message");
        const result = document.getElementById("result");

        form.addEventListener("submit", async function (event) {
            event.preventDefault();

            const fileInput = document.getElementById("gambar");

            if (!fileInput.files.length) {
                message.textContent = "Silakan pilih gambar.";
                return;
            }

            const formData = new FormData(form);

            button.disabled = true;
            message.textContent = "Sedang mengunggah gambar...";
            result.replaceChildren();

            try {
                const response = await fetch("upload.php", {
                    method: "POST",
                    body: formData
                });

                const data = await response.json();

                if (!response.ok || !data.success) {
                    throw new Error(
                        data.message || "Upload gambar gagal."
                    );
                }

                message.textContent = data.message;

                const heading = document.createElement("h2");
                heading.textContent = "Thumbnail";

                const image = document.createElement("img");
                image.src = data.thumbnail;
                image.alt = "Thumbnail hasil upload";

                result.append(heading, image);
                form.reset();

            } catch (error) {
                message.textContent = error.message;
            } finally {
                button.disabled = false;
            }
        });
    </script>

</body>
</html>

Form ini tidak mengirim pengguna ke halaman baru. Ketika tombol ditekan, JavaScript mengirim data menggunakan fetch() dan menampilkan respons dari server.

Perlu diperhatikan, kita tidak menetapkan header Content-Type secara manual ketika mengirim FormData. Browser akan mengaturnya beserta boundary yang diperlukan untuk mengirim file.

Baca Juga:

4. Membuat Script Upload dan Validasi PHP

Selanjutnya, buat file upload.php. File ini bertugas menerima gambar, memeriksa formatnya, dan menyiapkan proses resize.

<?php

header("Content-Type: application/json; charset=utf-8");

function respond(int $status, array $data): never
{
    http_response_code($status);
    echo json_encode($data);
    exit;
}

if ($_SERVER["REQUEST_METHOD"] !== "POST") {
    respond(405, [
        "success" => false,
        "message" => "Metode permintaan tidak diizinkan."
    ]);
}

if (
    !isset($_FILES["gambar"]) ||
    !is_array($_FILES["gambar"])
) {
    respond(400, [
        "success" => false,
        "message" => "File gambar tidak ditemukan."
    ]);
}

$file = $_FILES["gambar"];

if ($file["error"] !== UPLOAD_ERR_OK) {
    respond(400, [
        "success" => false,
        "message" => "Upload gagal. Periksa ukuran file dan coba kembali."
    ]);
}

$maxSize = 5 * 1024 * 1024;

if ($file["size"] <= 0 || $file["size"] > $maxSize) {
    respond(400, [
        "success" => false,
        "message" => "Ukuran gambar maksimal 5 MB."
    ]);
}

if (!is_uploaded_file($file["tmp_name"])) {
    respond(400, [
        "success" => false,
        "message" => "File upload tidak valid."
    ]);
}

$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($file["tmp_name"]);

$allowedTypes = [
    "image/jpeg" => "jpg",
    "image/png"  => "png",
    "image/webp" => "webp"
];

if (!isset($allowedTypes[$mimeType])) {
    respond(400, [
        "success" => false,
        "message" => "Format gambar harus JPG, PNG, atau WebP."
    ]);
}

$sourceImage = @imagecreatefromstring(
    file_get_contents($file["tmp_name"])
);

if ($sourceImage === false) {
    respond(400, [
        "success" => false,
        "message" => "File tidak dapat dibaca sebagai gambar."
    ]);
}

Kode di atas menggunakan MIME type berdasarkan isi file, bukan hanya ekstensi. Ini penting karena nama file dapat diubah pengguna.

Contoh tersebut menggunakan sintaks never yang tersedia pada PHP 8.1 ke atas. Jika server masih menggunakan PHP 8.0, ubah tipe pengembalian fungsi respond() menjadi void.

5. Melakukan Resize Gambar

Setelah gambar berhasil dibaca, tentukan batas dimensi. Misalnya, gambar utama maksimal berukuran 500 ร— 500 piksel.

Baca Juga:

Kita akan mempertahankan rasio asli agar gambar tidak terdistorsi.

$oldWidth = imagesx($sourceImage);
$oldHeight = imagesy($sourceImage);

$maxWidth = 500;
$maxHeight = 500;

$scale = min(
    $maxWidth / $oldWidth,
    $maxHeight / $oldHeight,
    1
);

$newWidth = max(1, (int) round($oldWidth * $scale));
$newHeight = max(1, (int) round($oldHeight * $scale));

$resizedImage = imagecreatetruecolor(
    $newWidth,
    $newHeight
);

imagecopyresampled(
    $resizedImage,
    $sourceImage,
    0,
    0,
    0,
    0,
    $newWidth,
    $newHeight,
    $oldWidth,
    $oldHeight
);

Fungsi imagecopyresampled() membantu menghasilkan gambar hasil resize yang lebih halus.

Sebagai contoh, gambar berukuran 1.600 ร— 1.200 piksel akan menjadi 500 ร— 375 piksel. Gambar yang sudah lebih kecil dari batas tidak akan diperbesar.

Untuk gambar PNG atau WebP yang memiliki transparansi, canvas perlu disiapkan dengan pengaturan alpha channel sebelum proses penyalinan. Contoh dasar ini belum mempertahankan transparansi secara khusus, sehingga untuk gambar transparan diperlukan penyesuaian tambahan.

Baca Juga:

6. Membuat Thumbnail Otomatis

Thumbnail biasanya digunakan untuk pratinjau gambar, daftar artikel, atau galeri. Pada contoh ini, thumbnail dibuat dengan batas dimensi maksimal 100 ร— 100 piksel.

$thumbWidth = 100;
$thumbHeight = 100;

$thumbScale = min(
    $thumbWidth / $oldWidth,
    $thumbHeight / $oldHeight
);

$actualThumbWidth = max(
    1,
    (int) round($oldWidth * $thumbScale)
);

$actualThumbHeight = max(
    1,
    (int) round($oldHeight * $thumbScale)
);

$thumbnail = imagecreatetruecolor(
    $actualThumbWidth,
    $actualThumbHeight
);

imagecopyresampled(
    $thumbnail,
    $sourceImage,
    0,
    0,
    0,
    0,
    $actualThumbWidth,
    $actualThumbHeight,
    $oldWidth,
    $oldHeight
);

Dengan cara ini, thumbnail mempertahankan rasio gambar asli. Jadi, hasilnya tidak selalu persegi tepat 100 ร— 100 piksel.

Jika website memerlukan thumbnail persegi untuk kartu artikel, kamu dapat menggunakan teknik center crop, yaitu memotong bagian tengah gambar setelah menentukan ukuran kanvas persegi.

7. Menyimpan Gambar ke Folder Server

Sebelum menyimpan hasilnya, buat nama file acak agar tidak bertabrakan dengan file yang sudah ada.

Tambahkan kode berikut pada upload.php, setelah proses resize dan thumbnail selesai.

$baseDir = __DIR__ . "/uploads";
$directories = [
    "original" => $baseDir . "/original",
    "resized" => $baseDir . "/resized",
    "thumbnails" => $baseDir . "/thumbnails"
];

foreach ($directories as $directory) {
    if (
        !is_dir($directory) &&
        !mkdir($directory, 0755, true) &&
        !is_dir($directory)
    ) {
        imagedestroy($sourceImage);
        imagedestroy($resizedImage);
        imagedestroy($thumbnail);

        respond(500, [
            "success" => false,
            "message" => "Folder penyimpanan tidak dapat dibuat."
        ]);
    }
}

$id = bin2hex(random_bytes(16));
$extension = $allowedTypes[$mimeType];

$originalPath = $directories["original"] . "/" . $id . "." . $extension;
$resizedPath = $directories["resized"] . "/" . $id . ".jpg";
$thumbnailPath = $directories["thumbnails"] . "/" . $id . ".jpg";

$originalSaved = move_uploaded_file(
    $file["tmp_name"],
    $originalPath
);

$resizedSaved = imagejpeg($resizedImage, $resizedPath, 85);
$thumbnailSaved = imagejpeg($thumbnail, $thumbnailPath, 85);

imagedestroy($sourceImage);
imagedestroy($resizedImage);
imagedestroy($thumbnail);

if (!$originalSaved || !$resizedSaved || !$thumbnailSaved) {
    @unlink($originalPath);
    @unlink($resizedPath);
    @unlink($thumbnailPath);

    respond(500, [
        "success" => false,
        "message" => "Gagal menyimpan hasil pemrosesan gambar."
    ]);
}

respond(200, [
    "success" => true,
    "message" => "Gambar berhasil diupload dan diproses.",
    "thumbnail" => "uploads/thumbnails/" . $id . ".jpg"
]);

Kode tersebut menggunakan nama file acak yang sulit ditebak. Gambar resize dan thumbnail disimpan sebagai JPEG dengan kualitas 85, sedangkan gambar asli disimpan sesuai format yang telah divalidasi.

Penting: Kode penyimpanan ini merupakan contoh dasar. Untuk produksi, penanganan kegagalan perlu dibuat lebih kuat agar file sementara tidak tertinggal jika salah satu proses gagal. Folder upload juga harus dikonfigurasi agar tidak dapat mengeksekusi skrip PHP.

8. Menampilkan Hasil Upload Tanpa Reload

Pada index.php, JavaScript membaca respons JSON dari upload.php. Jika proses berhasil, thumbnail ditampilkan menggunakan elemen gambar.

Bagian pentingnya adalah:

const response = await fetch("upload.php", {
    method: "POST",
    body: formData
});

const data = await response.json();

if (!response.ok || !data.success) {
    throw new Error(data.message || "Upload gagal.");
}

Dengan mekanisme tersebut, pengguna tidak perlu memuat ulang halaman hanya untuk melihat hasil upload. Pesan keberhasilan atau kegagalan juga dapat ditampilkan langsung di halaman.

9. Hal yang Perlu Diperhatikan untuk Produksi

Agar sistem upload lebih aman dan stabil, perhatikan beberapa hal berikut:

  • Batasi ukuran file dan dimensi gambar. File kecil sekalipun dapat memiliki dimensi sangat besar dan membutuhkan memori tinggi ketika diproses.
  • Validasi format di server. Jangan hanya mengandalkan atribut accept atau ekstensi file.
  • Pertahankan transparansi jika diperlukan. PNG dan WebP memerlukan pengaturan alpha channel yang sesuai.
  • Atur izin folder upload. File yang diunggah tidak boleh dieksekusi sebagai kode.
  • Gunakan autentikasi dan otorisasi. Jika fitur upload hanya untuk administrator, periksa hak akses di server.
  • Tambahkan perlindungan CSRF. Terutama jika endpoint upload digunakan oleh pengguna yang sudah login.
  • Pertimbangkan orientasi foto. Foto dari ponsel dapat memiliki metadata orientasi EXIF yang perlu ditangani.
  • Gunakan penyimpanan yang sesuai. Untuk website dengan banyak gambar, pertimbangkan penyimpanan file atau object storage dan simpan path serta metadata di database.

Kesimpulan

PHP, AJAX, dan GD Library dapat digunakan untuk membangun sistem upload gambar yang membuat versi resize dan thumbnail secara otomatis. AJAX membantu proses upload berlangsung tanpa reload halaman, sementara GD Library menangani pemrosesan gambar di sisi server.

Teknik ini cocok untuk website berita, blog, galeri, dan marketplace. Dengan validasi yang benar, pengelolaan folder yang aman, serta batas ukuran yang jelas, fitur upload dapat menjadi bagian yang lebih andal dalam aplikasi web.

Kucatat Kucatat