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

13

我正在处理一个组件,其中包含文件上传 HTML 控件。通过使用文件上传元素选择图像后,该图像将呈现在 HTML5 画布元素上。

这里是一个带有示例代码的 JSFiddle:https://jsfiddle.net/govi20/spmc7ymp/

id=target => jcrop 元素的选择器
id=photograph => 文件上传元素的选择器
id=preview => 画布元素的选择器
id=clear_selection => 清除画布内容的按钮的选择器

使用的第三方 JS 库:

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

设置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');
  });

});

清除画布事件,在点击清除按钮时将被触发:

jQuery('#clear_selection').click(function(){
  $('#target').Jcrop({    
      
      setSelect: [0,0,0,0],
    });
});

呈现图像在HTML5画布上的代码:

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);     
});

裁剪并在画布上渲染图像的代码:

    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);
        }
    };

    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);
        }
    }

这里有一些我遇到的问题:

  1. updatePreview函数没有在选择时被调用,因此画布没有被渲染。
  2. 裁剪选择框无法拖动(我正在使用Bootstrap CSS,我怀疑是由于缺少/不匹配的依赖关系造成的)。
  3. 画布是HTML5元素,这意味着最终用户必须具有HTML5兼容的浏览器,我正在开发一个拥有数百万用户的应用程序。强迫用户使用最新的浏览器并不可行。这里应该有什么应急机制?
2个回答

20

以下是基本的HTML 5代码:

https://jsfiddle.net/zm7e0jev/

该代码裁剪图像,显示预览,并将输入元素的值设置为Base64编码的裁剪后的图像。

您可以通过以下方式在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);

将base64图像字符串作为post变量提交具有服务器post大小限制,并且base64编码使剪裁后的图像文件大小比剪裁后的原始数据还要大(约33%),这使得上传时间更长。

要设置post大小限制: 什么是POST请求的大小限制?

请记住,增加的post大小限制可能会被滥用为DoS攻击。

我建议将base64剪裁后的图像转换为数据blob,然后在提交时将其作为文件添加到表单中:

https://jsfiddle.net/g3ysk6sf/

然后,您可以以下列方式获取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);

更新:

FormData()仅在IE10中部分支持,在旧版本的IE中不受支持

因此,我建议将base64字符串作为备用方案发送,但这将导致处理较大图像时出现问题,因此需要检查文件大小,并在图像超过特定大小时显示错误弹出窗口。

当我使其正常工作后,将在下面发布包含备用代码的更新。

更新2:

我增加了IE10及以下版本的备选方案:

https://jsfiddle.net/oupxo3pu/

唯一的限制是使用IE10及以下版本时可提交的图像大小,在图像尺寸太大的情况下,js代码会抛出一个错误。每个服务器的最大有效post值都不同,js代码有一个变量来设置最大大小。

下面的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);
}
目前canvas元素还没有后备代码,我正在研究中。
旧版浏览器的回退功能受到限制是我放弃向旧版浏览器提供支持的原因之一。

更新3:

我推荐在IE8中为canvas元素使用以下后备选项:

http://flashcanvas.net/

它支持所有裁剪代码所需的canvas函数。

请记住它需要flash。有一个canvas后备选项(explorercanvas),它不需要flash,但它不支持我们需要保存已裁剪图像的toDataURL()函数。


1
同时,客户端裁剪图像上传是一件棘手的事情,我花了一些时间来解决它。起初,我将其转换为base64字符串,但由于长度问题,在提交时遇到了问题。之后,我添加了代码将base64字符串转换为图像文件blob,然后使用一些html5代码将其添加到表单中(因此还需要回退)。由于我不断遇到这些限制并且为每个限制制作回退需要太多时间和工作,所以最终在项目中放弃了旧版浏览器。 - seahorsepip
花了一点时间删除所有不必要的代码并将其恢复到基础 :P - seahorsepip
@piechuckerr 已更新答案,包含您所需的所有信息,如果您有更多问题,请留言 :D - seahorsepip
1
好的,我会尝试你发布的东西,并告诉你结果。 - Govinda Sakhare
1
不再适用于Firefox或Edge。将context.draw替换为以下内容:“ if (coords.w !== 0 && coords.h !== 0) { context.drawImage(imageObj, coords.x, coords.y, coords.w, coords.h, 0, 0, canvas.width, canvas.height); }” - CountMurphy
显示剩余9条评论

19

Seahorsepip的答案非常棒。我在非回退答案上进行了许多改进。

http://jsfiddle.net/w1Lh4w2t/

我建议不要使用奇怪的隐藏png,因为Image对象同样有效(只要我们不需要支持回退)。

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

即使如此,您最好在最后将数据从画布中获取并仅在最后将其放入该字段中。

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]);
  }
}

但是,如果你想要更多的功能而不仅仅是裁剪,我们可以将jcrop附加到一个插入的画布上(刷新时我们会销毁它),这样我们就可以轻松地使用画布做任何事情,然后再次调用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();
}

然后在提交时,我们会提交任何待处理的操作(如applyCrop()或applyScale()),并将数据添加到隐藏字段中以备不时之需。这样,我们就可以轻松地对画布进行任何修改,然后在提交画布数据时,数据会被正确发送。

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();
}
< p >画布被添加到一个
视图中。

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

为了在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();
    }

1
大多数情况下请尝试使用 JFiddle,应该是完全功能的,只需保存上传到 JFiddle 的内容即可。 - Tatarize
我设法完成了,但它在 PHP 中像一个 $files 元素。它可以上传,这是最重要的事情,但我不得不以一种奇怪的方式从全局变量中获取它。 - Tatarize
我非常确定它已经是一个文件了。它作为附加到帖子数据中的内容发布,并通过某种方式使用 $_FILES 进行上传,但我必须进行调试才能找到确切的位置。@gomeshmunda。我可以检查一下旧的 PHP 中是否仍然存在某些问题。但是,它已经被上传了,您需要将其重命名并移动到 PHP 中正确的部分,由帖子触发。但是,它存储在 $_FILES 中,而不是像所有附加到帖子的上传文件一样存储在 $_POST 中。 - Tatarize
1
已添加到答案中。希望能有所帮助,尽管文件的位置已经超出了原始问题的范围。它会被上传并放置在Web服务器的临时文件中,并需要通过访问$_FILES中的内容从POST中获取。调试PHP有很大帮助。 - Tatarize
1
@Tatarize 感谢您提供的代码,让我好好学习一下,希望能够将其转换为图像并保存在文件夹中。再次感谢您回答我的问题 :) - gomesh munda
显示剩余17条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接