简体   繁体   English

Cropper.js、PHP/Laravel 上传图片,无需 Ajax

[英]Cropper.js, PHP/Laravel upload images without Ajax

How can I upload an image after it was edited with Cropper.js without using Ajax?如何在不使用 Ajax 的情况下上传使用 Cropper.js 编辑的图像?

HTML 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 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.由于“不可能”设置文件输入元素的值,因此您只能使用XMLHttpRequestfetch 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.您可以将文件编码为 base64 数据,然后将其存储在隐藏的输入元素中,但对于大文件,大量的 base64 编码数据将导致浏览器内存不足。 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.幸运的是,在最新版本的 Firefox 和 Chrome 上,可以使用DataTransfer对象来更新文件输入元素files属性,这将允许同步上传。 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/有关编辑和上传文件数据的更多信息,请访问: https : //pqina.nl/blog/the-trouble-with-editing-and-uploading-files-in-the-browser/

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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