fbpx

Nieodłącznym elementem interaktywnej strony internetowej jest możliwość wysyłania własnych zdjęć. Często chcemy dać użytkownikowi możliwość samodzielnego przycięcia wysyłanego pliku, tak by przechowywane przez nas grafiki były zbliżonych rozmiarów, a ich proporcje były zawsze równe.  Okazuje się, że taka czynność wcale nie musi być skomplikowana, kiedy użyjemy jednej z popularnych bibliotek do przycinania zdjęć.

Ja postanowiłem skorzystać z bliblioteki Cropper JS, możemy zobaczyć jej możliwości pod adresem –  https://fengyuanchen.github.io/cropperjs/. Będziemy chcieli stworzyć prosty mechanizm, który pozwala użytkownikowi wybrać własne zdjęcie, następnie zostanie ono wyświetlone wraz z edytowalnym obszarem zaznaczania. Dzięki temu, użytkownik będzie mógł wybrać, który fragment zdjęcia ma być przycięty. Następnie wybrane przez użytkownika parametry wraz ze zdjęciem prześlemy za pomocą fetch API do naszego prostego pliku PHP – będzie on miał za zadanie przyjąć przesłane parametry i na ich podstawie odpowiednio przyciąć i zapisać zdjęcie na serwerze. Co więcej, użytkownik bez przeładowania strony będzie miał możliwość podglądu obciętego zdjęcia.

Weźmy się więc do pracy, na początku zastanówmy się czego będziemy potrzebować. Skoro korzystamy ze skryptu PHP, to potrzebujemy serwera lokalnego – ja użyję oprogramowania XAMPP. Stwórzmy nowy folder, gdzie umieścimy wszystkie pliki. Ja nazwałem go cropp.


Chcesz dowiedzieć się więcej? Sprawdź nasz kurs PHP gdzie tworzymy stronę od podstaw.


Zacznijmy od stworzenia prostego pliku HTML, na początek utwórzmy podstawową strukturę dokumentu.

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Cropp</title>
</head>
<body>
    
</body>
</html>

Następnie zastanówmy się jakich elementów HTML potrzebujemy do pobrania od użytkownika zdjęcia, wyświetlenia go wraz z obszarem do przycinania, a także obszaru do wyświetlenia finalnej wersji zdjęcia.

Dodajmy input, który umożliwi użytkownikowi dodanie własnego zdjęcia, wrzućmy go do kontenera, aby później było go łatwiej ostylować.

    <div id="choosePhotoContainer">
        <input type="file" id="image-file" name="ourImage" />
    </div>

Następnie dodajmy nasz obszar roboczy do przycinania. Dodajmy obrazek, w którym będziemy wyświetlać nasze zdjęcie i je przycinać. Na koniec wstawmy jeszcze przycisk, który pobierze wybrane przez nas

 <div id="cropperContainer" >
            <div id='ourImageHolder'>
                <img id="ourImage" style="display:none" class="img-fluid w-100" />
            </div>
            <div id="setContainer">
                <button id="set">Przytnij</button>
            </div>
        </div>

Dodajmy również miejsce na wyświetlenie już obciętego obrazka, umieśćmy je na końcu naszego dokumentu HTML.

       <div id="responseContainer" style="display:none">
            <img id="response">
        </div>

Umieśćmy również podstawowe stylowanie – tak żeby nasz interfejs był czytelny.

<style>
        #ourImageHolder,
        #responseContainer {
            width: 800px;
            height: 600px;
            margin: 0 auto;
            text-align: center;
        }

        #responseContainer img {
            width: 100%;
        }

        #set {
            margin: 0 auto;
            visibility: hidden;
            padding: 5px 15px;
        }

        #setContainer {
            display: flex;
            justify-content: center;
            padding: 25px;
        }

        #choosePhotoContainer {
            display: flex;
            justify-content: center;
        }
    </style>

Pamiętajmy również o załączeniu skryptów i styli biblioteki

<link rel="stylesheet" href="cropper/dist/cropper.css">
    <script src="cropper/dist/cropper.js"></script>

Czas na skrypt. Po pierwsze dodajmy funkcjonalność pozwalającą na wyświetlenie wstawionego obrazka zaraz po kliknięciu przycisku. Za Pomocą zmiennej _PREVIEW_URL uzyskamy ścieżkę do wstawionego pliku. Podstawiamy ją jako atrybut src dla naszego obrazka. Na koniec wywołujemy funkcję crop(), stwórzmy ją więc…

    var _PREVIEW_URL;
    /* Selected File has changed */
    document.querySelector("#image-file").addEventListener('change', function () {
        var file = this.files[0];

        // object url
        _PREVIEW_URL = URL.createObjectURL(file);

        // set src of image and show
        document.querySelector("#ourImage").setAttribute('src', _PREVIEW_URL);
        document.querySelector("#ourImage").style.display = 'inline-block';
        crop();
    });

Po kolei, włączamy widoczność naszego przycisku, którym ustawimy wybrany obszar obrazka. Następnie tworzymy obrazek o src tego, wstawionego przez użytkownika. Następnie ustawiamy parametry obszaru zaznaczania, ja uznałem że powinien on się mieścić w przedziale 10-70% maksymalnej wielkości zdjęcia. Dodatkowo ustalamy proporcje na 8:6. Tworząc nowy obiekt klasy Cropper również podajemy parametry takie jak viewMode czy dragMode. Następnie nasz skrypt sprawdza czy maksymalne rozmiary nie są przekroczone. Potem ustawione zostają parametry naszego przyciętego obrazka.

function crop() {
        var image = document.querySelector('#ourImage');
        document.getElementById('set').style.visibility = 'visible';
        var img = new Image();
        img.src = image.src;
        img.onload = function () {
            ratio = 8 / 6;
            minCroppedWidth = (0.1 * img.naturalWidth);
            minCroppedHeight = (0.1 * img.naturalHeight);
            maxCroppedWidth = (0.7 * img.naturalWidth);
            maxCroppedHeight = (0.7 * img.naturalHeight);

            var cropper = new Cropper(image, {
                viewMode: 6,
                dragMode: 'move',
                center: true,
                aspectRatio: ratio,

                crop: function (event) {
                    var width = event.detail.width;
                    var height = event.detail.height;
                    if (
                        width < minCroppedWidth ||
                        height < minCroppedHeight ||
                        width > maxCroppedWidth ||
                        height > maxCroppedHeight
                    ) {
                        cropper.setData({
                            width: Math.max(minCroppedWidth, Math.min(
                                maxCroppedWidth,
                                width)),
                            height: Math.max(minCroppedHeight, Math.min(
                                maxCroppedHeight, height)),
                        });
                    }
                    parameters = JSON.stringify(cropper.getData(true));

                },
            });
        }
}

Wewnątrz tej funkcji musimy obsłużyć wysyłanie tych parametrów do pliku php. Po kliknięciu przycisku potwierdzającego za pomocą Fetch API wyślemy do pliku cropp.php zdjęcie, a także parametry przycinania. Następnie oczekujemy na odpowiedź – chcemy aby była to ścieżka do zapisanego, przyciętego pliku.

document.getElementById('set').onclick = function () {

            ourImage = document.querySelector('#image-file').files[0];
            var formData = new FormData();
            formData.append("parameters", parameters);
            formData.append("ourImage", ourImage);

            fetch('cropp.php', {
                method: "POST",
                body: formData
            }).then(response => response.json()).then(res => {
                document.querySelector('#cropperContainer').setAttribute('style', 'display:none;');
                document.querySelector('#responseContainer').setAttribute('style', 'display:block;');
                document.querySelector('#response').src = res;
            });
        }


W końcu nasz plik php – przyjmujemy przesłane POSTem parametry zdjęcia, a także samo zdjęcie z tablicy $_FILES. W zmiennej $src tworzymy nasze zdjęcie (używamy imagecreatefromjpg() – dobrze jest sprawdzić jakie rozszerzenie ma nasze zdjęcie i w razie potrzeby użyć innej funkcji ). $to_cropp_array zawiera tablicę z przesłanymi parametrami. Kolejny krok to zmienna $dest – tutaj przycinamy nasz obrazek. Na końcu wybieramy ścieżkę naszego nowego pliku, zapisujemy go za pomocą funkcji imagejpg() i w razie powodzenia tej operacji – wysyłamy odpowiedź w formacie json zawierającą ścieżkę do pliku.

<?php

$imageParameters = json_decode($_POST['parameters'], true);
$src = imagecreatefromjpeg($_FILES["ourImage"]["tmp_name"]);
$to_crop_array = array('x' =>$imageParameters['x'] , 'y' => $imageParameters['y'], 'width' => $imageParameters['width'], 'height'=> $imageParameters['height']);
$dest = imagecrop($src, $to_crop_array);
$fileName = 'newFile.jpeg;
if(imagejpeg($dest,$fileName, 100)){
    echo json_encode($fileName);
}

Na koniec jeszcze raz nasz plik html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        #ourImageHolder,
        #responseContainer {
            width: 800px;
            height: 600px;
            margin: 0 auto;
            text-align: center;
        }

        #responseContainer img {
            width: 100%;
        }

        #set {
            margin: 0 auto;
            visibility: hidden;
            padding: 5px 15px;
        }

        #setContainer {
            display: flex;
            justify-content: center;
            padding: 25px;
        }

        #choosePhotoContainer {
            display: flex;
            justify-content: center;
        }
    </style>

    <link rel="stylesheet" href="cropper/dist/cropper.css">
    <script src="cropper/dist/cropper.js"></script>
</head>

<body>
    <div id="choosePhotoContainer">
        <input type="file" id="image-file" name="ourImage" />
    </div>

    <div id="cropperContainer">
        <div id='ourImageHolder'>
            <img id="ourImage" style="display:none" class="img-fluid w-100" />
        </div>
        <div id="setContainer">
            <button id="set">Przytnij</button>
        </div>
    </div>
    <div id="responseContainer" style="display:none">
        <img id="response">
    </div>
    <script>
        function crop() {
            var image = document.querySelector('#ourImage');
            document.getElementById('set').style.visibility = 'visible';
            var img = new Image();
            img.src = image.src;
            img.onload = function () {
                ratio = 8 / 6;
                minCroppedWidth = (0.1 * img.naturalWidth);
                minCroppedHeight = (0.1 * img.naturalHeight);
                maxCroppedWidth = (0.7 * img.naturalWidth);
                maxCroppedHeight = (0.7 * img.naturalHeight);

                var cropper = new Cropper(image, {
                    viewMode: 6,
                    dragMode: 'move',
                    center: true,
                    aspectRatio: ratio,

                    crop: function (event) {
                        var width = event.detail.width;
                        var height = event.detail.height;
                        if (
                            width < minCroppedWidth ||
                            height < minCroppedHeight ||
                            width > maxCroppedWidth ||
                            height > maxCroppedHeight
                        ) {
                            cropper.setData({
                                width: Math.max(minCroppedWidth, Math.min(
                                    maxCroppedWidth,
                                    width)),
                                height: Math.max(minCroppedHeight, Math.min(
                                    maxCroppedHeight, height)),
                            });
                        }
                        parameters = JSON.stringify(cropper.getData(true));

                    },
                });
            }
            document.getElementById('set').onclick = function () {

                ourImage = document.querySelector('#image-file').files[0];
                var formData = new FormData();
                formData.append("parameters", parameters);
                formData.append("ourImage", ourImage);

                fetch('cropp.php', {
                    method: "POST",
                    body: formData
                }).then(response => response.json()).then(res => {
                    console.log(res);
                    document.querySelector('#cropperContainer').setAttribute('style', 'display:none;');
                    document.querySelector('#responseContainer').setAttribute('style', 'display:block;');
                    document.querySelector('#response').src = res;
                });
            }
        }

        var _PREVIEW_URL;
        /* Selected File has changed */
        document.querySelector("#image-file").addEventListener('change', function () {
            var file = this.files[0];

            // object url
            _PREVIEW_URL = URL.createObjectURL(file);

            // set src of image and show
            document.querySelector("#ourImage").setAttribute('src', _PREVIEW_URL);
            document.querySelector("#ourImage").style.display = 'inline-block';
            crop();
        });
    </script>
</body>

</html>

Myślę, że taka funkcjonalność jest bardzo dobrym urozmaiceniem naszej strony internetowej, a przy tym jest naprawdę praktyczna – wszystkie obrazki będą teraz miały jednakowe proporcje, a użytkownicy będą mogli w łatwy sposób wybierać interesujące ich wycinki.