简体   繁体   English

我应该如何使用 jcrop 在客户端裁剪图像并上传它?

[英]How should I crop the image at client side using jcrop and upload it?

I am working on a component in which there is file-upload HTML control, upon selecting the image using the control, the image would be rendered on the HTML5 Canvas element我正在处理一个组件,其中有文件上传 HTML 控件,使用控件选择图像后,图像将呈现在 HTML5 Canvas 元素上

Here is JSFiddle with sample code: https://jsfiddle.net/govi20/spmc7ymp/这是带有示例代码的 JSFiddle: https ://jsfiddle.net/govi20/spmc7ymp/

id=target is the jcrop image target id=target是 jcrop 图像目标
id=photograph is a fileupload control id=photograph是一个文件上传控件
id=preview is a canvas element id=preview是一个画布元素
id=clear_selection is a button which would clear the canvas id=clear_selection是一个可以清除画布的按钮

Third-party JS libraries used:使用的第三方 JS 库:

<script src="./js/jquery.min.js"></script>
<script src="./js/jquery.Jcrop.js"></script>
<script src="./js/jquery.color.js"></script>

Setting up the JCrop:设置 JCrop:

<script type="text/javascript">

jQuery(function($){

var api;

$('#target').Jcrop({
  // start off with jcrop-light class
  bgOpacity: 0.5,
  keySupport: false,
  bgColor: 'black',
  minSize:[240,320],
  maxSize:[480,640],
  onChange : updatePreview,
  onSelect : updatePreview, 
  height:160,
  width:120,
  addClass: 'jcrop-normal'
},function(){
  api = this;
  api.setSelect([0,0,240,320]);
  api.setOptions({ bgFade: true });
  api.ui.selection.addClass('jcrop-selection');
  });

});

clear canvas button click event:清除画布按钮单击事件:

jQuery('#clear_selection').click(function(){
  $('#target').Jcrop({    

      setSelect: [0,0,0,0],
    });
});

to display uploaded image on jcrop panel在 jcrop 面板上显示上传的图像

function readURL(input) {

    if (input.files && input.files[0]) {
        var reader = new FileReader();
        reader.onload = function (e) {
            $('#target').attr('src', e.target.result);
            setProperties();       
        }
        reader.readAsDataURL(input.files[0]);
    }
}

function setProperties(){
   $('#target').Jcrop({         
              setSelect: [0,0,240,320]
        }); 
}
$("#photograph").change(function(){
    readURL(this);     
});

I am using following code to crop and render an image on the canvas.我正在使用以下代码在画布上裁剪和渲染图像。

    function make_base() {
        console.log("make_base called");
        var base_image = new Image();
        base_image.src = '';
        base_image.onload = function () {
            context.drawImage(base_image, 0, 0);
        }
    }

    var canvas = document.getElementById('preview'),
    context = canvas.getContext('2d');

    make_base();
    function updatePreview(c) {
        console.log("called");
        if(parseInt(c.w) > 0) {
            // Show image preview
            var imageObj = $("#target")[0];
            var canvas = $("#preview")[0];
            var context = canvas.getContext("2d");
            context.drawImage(imageObj, c.x, c.y, c.w, c.h, 0, 0, canvas.width, canvas.height);
        }
    };

Here are a bunch of issues I am facing with above setup:以下是我在上述设置中面临的一系列问题:

  1. updatePreview function is not getting called on selection, hence the canvas is not getting rendered. updatePreview 函数没有在选择时被调用,因此画布没有被渲染。
  2. crop selection box is not draggable (I am using bootstrap CSS, I suspect it is due to missing/mismatching dependency).裁剪选择框不可拖动(我正在使用引导 CSS,我怀疑这是由于缺少/不匹配的依赖性)。
  3. Canvas is HTML5 element, which means the end-user must have an HTML5 compatible browser, I am working on an application that has millions of users. Canvas 是HTML5元素,这意味着最终用户必须拥有兼容HTML5浏览器,我正在开发一个拥有数百万用户的应用程序。 Forcing users to use the latest browser is not a feasible option.强制用户使用最新的浏览器并不是一个可行的选择。 What should be the fallback mechanism here?这里的回退机制应该是什么?

Here's basic html 5 code:这是基本的 html 5 代码:

https://jsfiddle.net/zm7e0jev/ https://jsfiddle.net/zm7e0jev/

This code crops the image, shows a preview and sets the value of an input element to the base64 encoded cropped image.此代码裁剪图像,显示预览并将输入元素的值设置为 base64 编码的裁剪图像。

You can fetch the image file in php the following way:您可以通过以下方式在 php 中获取图像文件:

//File destination
$destination = "/folder/cropped_image.png";
//Get convertable base64 image string
$image_base64 = $_POST["png"];
$image_base64 = str_replace("data:image/png;base64,", "", $image_base64);
$image_base64 = str_replace(" ", "+", $image_base64);
//Convert base64 string to image data
$image = base64_decode($image_base64);
//Save image to final destination
file_put_contents($destination, $image);

Submitting base64 image string as a post variable has it's server post size limits and base64 encoding makes the cropped image file size even bigger (~33%) then the raw data of the cropped image would be which makes the upload take even longer.提交 base64 图像字符串作为 post 变量有它的服务器 post 大小限制,base64 编码使裁剪后的图像文件大小更大(~33%),然后裁剪后的图像的原始数据将使得上传时间更长。

To set the post size limit: What is the size limit of a post request?设置帖子大小限制: 帖子请求的大小限制是多少?

Keep in mind that an increased post size limit can be abused for a DoS attack as example.请记住,例如,可以滥用增加的帖子大小限制进行 DoS 攻击。

Instead I suggest converting the base64 cropped image to a data blob and then add it to the form on submit as a file:相反,我建议将 base64 裁剪图像转换为数据 blob,然后将其添加到表单中作为文件提交:

https://jsfiddle.net/g3ysk6sf/ https://jsfiddle.net/g3ysk6sf/

Then you can fetch the image file in php the following way:然后你可以通过以下方式在php中获取图像文件:

//File destination
$destination = "/folder/cropped_image.png";
//Get uploaded image file it's temporary name
$image_tmp_name = $_FILES["cropped_image"]["tmp_name"][0];
//Move temporary file to final destination
move_uploaded_file($image_tmp_name, $destination);

Update:更新:

FormData() is only partially support in IE10 and not supported in older versions of IE FormData() 仅在 IE10 中部分支持,在旧版本的 IE 中不支持

So I suggest sending the base64 string as a fallback, though this will cause problems with bigger images so it needs to check the filesize and show an error popup when the image is above a specific size.所以我建议发送 base64 字符串作为后备,尽管这会导致更大的图像出现问题,因此它需要检查文件大小并在图像超过特定大小时显示错误弹出窗口。

I'll post an update with the fallback code below when I've got it working.当我让它工作时,我会发布一个带有后备代码的更新。

Update 2:更新 2:

I added a fallback for IE10 and below:我为 IE10 及以下添加了一个后备:

https://jsfiddle.net/oupxo3pu/ https://jsfiddle.net/oupxo3pu/

The only limitation is the image size that can be submitted when using IE10 and below, in case the image size is too big the js code will throw an error.唯一的限制是IE10及以下可以提交的图片大小,如果图片过大js代码会报错。 The maximum size to work for post values is different between each server, the js code has a variable to set the maximum size.每个服务器之间为 post 值工作的最大大小是不同的,js 代码有一个变量来设置最大大小。

The php code below is adapted to work with above fallback:下面的 php 代码适用于上述回退:

//File destination
$destination = "/folder/cropped_image.png";
if($_POST["png"]) {//IE10 and below
    //Get convertable base64 image string
    $image_base64 = $_POST["png"];
    $image_base64 = str_replace("data:image/png;base64,", "", $image_base64);
    $image_base64 = str_replace(" ", "+", $image_base64);
    //Convert base64 string to image data
    $image = base64_decode($image_base64);
    //Save image to final destination
    file_put_contents($destination, $image);
} else if($_FILES["cropped_image"]) {//IE11+ and modern browsers
    //Get uploaded image file it's temporary name
    $image_tmp_name = $_FILES["cropped_image"]["tmp_name"][0];
    //Move temporary file to final destination
    move_uploaded_file($image_tmp_name, $destination);
}
There is no fallback code for the canvas element yet, I'm looking into it. 画布元素还没有后备代码,我正在研究它。
The post size limitation in the fallback for older browsers is one of the reasons I dropped support for older browsers myself. 旧浏览器回退中的帖子大小限制是我自己放弃支持旧浏览器的原因之一。

Update 3:更新 3:

The fallback I recommend for the canvas element in IE8:我为 IE8 中的 canvas 元素推荐的后备方案:

http://flashcanvas.net/ http://flashcanvas.net/

It supports all the canvas functions the cropping code needs.它支持裁剪代码所需的所有画布功能。

Keep in mind it requires flash.请记住,它需要闪光灯。 There is a canvas fallback (explorercanvas) that does not require flash but it does not support the function toDataURL() which we need to save our cropped image.有一个不需要 flash 的画布回退 (explorercanvas),但它不支持我们需要保存裁剪图像的 toDataURL() 函数。

Seahorsepip's answer is fantastic. Seahorsepip 的回答太棒了。 I made a lot of improvements on the non-fallback answer.我对非后备答案做了很多改进。

http://jsfiddle.net/w1Lh4w2t/ http://jsfiddle.net/w1Lh4w2t/

I would recommend not doing that strange hidden png thing, when an Image object works just as well (so long as we're not supporting fallbacks).我建议不要做那种奇怪的隐藏 png 事情,当 Image 对象也能正常工作时(只要我们不支持回退)。

var jcrop_api;
var canvas;
var context;
var image;
var prefsize;

Though even then we are, you're better off getting that data out of the canvas at the end and putting it in that field only at the end.尽管我们是这样,但最好在最后将数据从画布中取出,并仅在最后将其放入该字段中。

function loadImage(input) {
  if (input.files && input.files[0]) {
    var reader = new FileReader();
    reader.onload = function(e) {
      image = new Image();
      image.src = e.target.result;
      validateImage();
    }
    reader.readAsDataURL(input.files[0]);
  }
}

But, if you want more functions than just crop, if we attach the jcrop to an inserted canvas (which we destroy with the jcrop on refresh).但是,如果你想要更多的功能而不仅仅是裁剪,如果我们将 jcrop 附加到插入的画布(我们在刷新时用 jcrop 销毁它)。 We can easily do anything we can do with a canvas, then validateImage() again and have the updated image visible in place.我们可以轻松地用画布做任何我们可以做的事情,然后再次 validateImage() 并让更新的图像就位可见。

function validateImage() {
  if (canvas != null) {
    image = new Image();
    image.src = canvas.toDataURL('image/png');
  }
  if (jcrop_api != null) {
    jcrop_api.destroy();
  }
  $("#views").empty();
  $("#views").append("<canvas id=\"canvas\">");
  canvas = $("#canvas")[0];
  context = canvas.getContext("2d");
  canvas.width = image.width;
  canvas.height = image.height;
  context.drawImage(image, 0, 0);
  $("#canvas").Jcrop({
    onSelect: selectcanvas,
    onRelease: clearcanvas,
    boxWidth: crop_max_width,
    boxHeight: crop_max_height
  }, function() {
    jcrop_api = this;
  });
  clearcanvas();
}

Then on submit we submit any pending operations, like applyCrop() or applyScale(), adding data into hidden fields for fallback stuff, if we have those things needed.然后在提交时,我们提交任何挂起的操作,如 applyCrop() 或 applyScale(),如果我们需要这些东西,则将数据添加到隐藏字段中以备后备。 We then have a system we can easily just modify the canvas, in any way, then when we submit the canvas data gets sent properly.然后我们有一个系统,我们可以轻松地以任何方式修改画布,然后当我们提交画布数据时,它会被正确发送。

function applyCrop() {
  canvas.width = prefsize.w;
  canvas.height = prefsize.h;
  context.drawImage(image, prefsize.x, prefsize.y, prefsize.w, prefsize.h, 0, 0, canvas.width, canvas.height);
  validateImage();
}

The canvas is added to a div views.画布被添加到 div 视图中。

 <div id="views"></div>

To catch the attached file in PHP (drupal), I used something like:为了在 PHP (drupal) 中捕获附加文件,我使用了类似的东西:

    function makeFileManaged() {
        if (!isset($_FILES['croppedfile']))
            return NULL;
        $path = $_FILES['croppedfile']['tmp_name'];
        if (!file_exists($path))
            return NULL;
        $result_filename = $_FILES['croppedfile']['name'];
        $uri = file_unmanaged_move($path, 'private://' . $result_filename, FILE_EXISTS_RENAME);
        if ($uri == FALSE)
            return NULL;
        $file = File::Create([
                    'uri' => $uri,
        ]);
        $file->save();
        return $file->id();
    }

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

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