简体   繁体   中英

Cropper.js, PHP/Laravel upload images without Ajax

How can I upload an image after it was edited with Cropper.js without using Ajax?

HTML

<div class="block-content" id=profile-image>
    <div class="row pb-3">
        <div class="col-12">
            <label>Profile Image</label>
            <div class="options-container">
                <img class="img-fluid options-item image-edit" id="image-place"
                     src="{{ $user->avatar ? asset(App\User::PROFILE_IMAGE_PATH . $user->avatar) : 'media/avatars/avatar-male.jpg' }}"
                     alt="Profile Image" style="max-width: 100%">
            </div>
        </div>
    </div>
    <div class="btn-group">
        <button type="button" class="btn btn-primary" data-method="reset" title="Reset">
            <span class="docs-tooltip" data-toggle="tooltip" data-animation="false" title="">
                <span class="fa fa-sync-alt"></span>
            </span>
        </button>
        <label class="btn btn-primary btn-upload" for="fileEdit" title="Upload image file">
            <input type="file" class="sr-only" id="fileEdit" name="file" accept=".jpg,.jpeg,.png,.gif,.bmp,.tiff">
            <input type="hidden" class="sr-only" id="hiddenBlob" name="hiddenBlob">
            <span class="docs-tooltip" data-toggle="tooltip" data-animation="false" title=""
                  data-original-title="Import image with Blob URLs">
                <span class="fa fa-upload"></span>
            </span>
        </label>
    </div>
    <div class="row">
        <div class="col-12">
            <div class="form-group text-center">
                <button type="submit" class="btn btn-block btn-hero-lg btn-hero-primary">
                    <i class="si si-reload mr-1"></i> Update Profile
                </button>
            </div>
        </div>
    </div>
</div>

JS

$(function () {
    'use strict';

    let console     = window.console || { log: function () { } }
    let URL         = window.URL || window.webkitURL;
    let $image      = $('.image-edit');
    let $dataX      = $('#dataX');
    let $dataY      = $('#dataY');
    let $dataHeight = $('#dataHeight');
    let $dataWidth  = $('#dataWidth');
    let $dataRotate = $('#dataRotate');
    let $dataScaleX = $('#dataScaleX');
    let $dataScaleY = $('#dataScaleY');
    let options     = {
        aspectRatio: 16 / 9,
        preview: '.img-preview',
        crop: function (e) {
            $dataX.val(Math.round(e.detail.x));
            $dataY.val(Math.round(e.detail.y));
            $dataHeight.val(Math.round(e.detail.height));
            $dataWidth.val(Math.round(e.detail.width));
            $dataRotate.val(e.detail.rotate);
            $dataScaleX.val(e.detail.scaleX);
            $dataScaleY.val(e.detail.scaleY);
        }
    }

    let originalImageURL  = $image.attr('src');
    let uploadedImageName = 'cropped.jpg';
    let uploadedImageType = 'image/jpeg';
    let uploadedImageURL;

    let cropper = new window.Cropper(document.getElementById('image-place'), options)

    // Cropper
    $image.on({
        ready: function (e) {
            // console.log(e.type);
        },
        cropstart: function (e) {
            // console.log(e.type, e.detail.action);
        },
        cropmove: function (e) {
            // console.log(e.type, e.detail.action);
        },
        cropend: function (e) {
            // console.log(e.type, e.detail.action);
        },
        crop: function (e) {



            cropper.getCroppedCanvas().toBlob(function(blob){
                console.log(blob.text())
            })

        },
        zoom: function (e) {
            // console.log(e.type, e.detail.ratio);
        }
    }).cropper(options);

    // Buttons
    if (!$.isFunction(document.createElement('canvas').getContext)) {
        $('button[data-method="getCroppedCanvas"]').prop('disabled', true);
    }

    if (typeof document.createElement('cropper').style.transition === 'undefined') {
        $('button[data-method="rotate"]').prop('disabled', true);
        $('button[data-method="scale"]').prop('disabled', true);
    }

    // Options
    $('.docs-toggles').on('change', 'input', function () {
        let $this = $(this);
        let name = $this.attr('name');
        let type = $this.prop('type');
        let cropBoxData;
        let canvasData;

        if (!$image.data('cropper')) {
            return;
        }

        if (type === 'checkbox') {
            options[name] = $this.prop('checked');
            cropBoxData = $image.cropper('getCropBoxData');
            canvasData = $image.cropper('getCanvasData');

            options.ready = function () {
                $image.cropper('setCropBoxData', cropBoxData);
                $image.cropper('setCanvasData', canvasData);
            }
        } else if (type === 'radio') {
            options[name] = $this.val();
        }

        $image.cropper('destroy').cropper(options);

        $image.cropper('getCroppedCanvas').toBlob(function (blob) {
            console.log(blob);

        })
    });

    // Methods
    $('#profile-image').on('click', '[data-method]', function () {
        let $this = $(this);
        let data = $this.data();
        let cropper = $image.data('cropper');
        let cropped;
        let $target;
        let result;

        if ($this.prop('disabled') || $this.hasClass('disabled')) {
            return;
        }

        if (cropper && data.method) {
            data = $.extend({}, data); // Clone a new one

            if (typeof data.target !== 'undefined') {
                $target = $(data.target);

                if (typeof data.option === 'undefined') {
                    try {
                        data.option = JSON.parse($target.val());
                    } catch (e) {
                        console.log(e.message);
                    }
                }
            }

            cropped = cropper.cropped;

            switch (data.method) {
                case 'rotate':
                    if (cropped && options.viewMode > 0) {
                        $image.cropper('clear');
                    }

                    break;

                case 'getCroppedCanvas':
                    if (uploadedImageType === 'image/jpeg') {
                        if (!data.option) {
                            data.option = {}
                        }

                        data.option.fillColor = '#fff';
                    }

                    break;
            }

            result = $image.cropper(data.method, data.option, data.secondOption);

            switch (data.method) {
                case 'rotate':
                    if (cropped && options.viewMode > 0) {
                        $image.cropper('crop');
                    }

                    break;

                case 'scaleX':
                case 'scaleY':
                    $(this).data('option', -data.option);
                    break;

                case 'getCroppedCanvas':
                    if (result) {
                        Swal.fire({
                            title: 'Info!',
                            html: result,
                            type: 'success',
                        })
                    }

                    break;

                case 'destroy':
                    if (uploadedImageURL) {
                        URL.revokeObjectURL(uploadedImageURL);
                        uploadedImageURL = '';
                        $image.attr('src', originalImageURL);
                    }

                    break;
            }

            if ($.isPlainObject(result) && $target) {
                try {
                    $target.val(JSON.stringify(result));
                } catch (e) {
                    console.log(e.message);
                }
            }
        }
    });

    // Keyboard
    $(document.body).on('keydown', function (e) {
        if (e.target !== this || !$image.data('cropper') || this.scrollTop > 300) {
            return;
        }

        switch (e.which) {
            case 37:
                e.preventDefault();
                $image.cropper('move', -1, 0);
                break;

            case 38:
                e.preventDefault();
                $image.cropper('move', 0, -1);
                break;

            case 39:
                e.preventDefault();
                $image.cropper('move', 1, 0);
                break;

            case 40:
                e.preventDefault();
                $image.cropper('move', 0, 1);
                break;
        }
    });

    // Import image
    let $inputImage = $('#fileEdit');

    if (URL) {
        $inputImage.change(function () {
            let files = this.files;
            let file;

            if (!$image.data('cropper')) {
                return;
            }

            if (files && files.length) {
                file = files[0];


                if (/^image\/\w+$/.test(file.type)) {
                    uploadedImageName = file.name;
                    uploadedImageType = file.type;

                    if (uploadedImageURL) {
                        URL.revokeObjectURL(uploadedImageURL);
                    }

                    uploadedImageURL = URL.createObjectURL(file);
                    $image.cropper('destroy').attr('src', uploadedImageURL).cropper(options);

                } else {
                    window.alert('Please choose an image file.');
                }
            }
        });
    } else {
        $inputImage.prop('disabled', true).parent().addClass('disabled');
    }
});

As it's "impossible" to set the value of a file input element you can only upload newly created files/images using XMLHttpRequest or fetch aka Ajax.

You could encode the file as base64 data and then store it in a hidden input element BUT for big files the huge amount of base64 encoded data will cause browsers to run out of memory. It can also trigger automated security measures on the server.

<input type="file">
<input type="hidden">

<script>
document.querySelector('input[type="file"]').onchange = e => {
    const reader = new FileReader();
    reader.onloadend = () => {
        document.querySelector('input[type="hidden"]').value = reader.result;
    };
    reader.readAsDataURL(e.target.files[0]);
};
</script>

Luckily on the latest versions of Firefox and Chrome it's possible to use the DataTransfer object to update the file input element files property, this would allow a synchronous upload. Unfortunately at the time of this writing this is only supported by those two browsers.

<input type="file">

<script>
// Create a DataTransfer instance and add a newly created file
const dataTransfer = new DataTransfer();
dataTransfer.items.add(new File(['hello world'], 'This_Works.txt'))

// Assign the DataTransfer files list to the file input
document.querySelector('input').files = dataTransfer.files;
</script>

More information on editing and uploading file data can be found here: https://pqina.nl/blog/the-trouble-with-editing-and-uploading-files-in-the-browser/

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM