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

Posted

技术标签:

【中文标题】我应该如何使用 jcrop 在客户端裁剪图像并上传?【英文标题】:How should I crop an image at client side using jcrop and upload it? 【发布时间】:2016-04-11 14:23:15 【问题描述】:

我正在开发一个包含文件上传 html 控件的组件,使用 file-upload 元素选择图像后,该图像将在 HTML5 Canvas 元素上呈现。

这里是带有示例代码的 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 Canvas 上呈现图像的代码:

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

以下是我在上述设置中面临的一系列问题:

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

【问题讨论】:

【参考方案1】:

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 附加到插入的画布(我们在刷新时使用 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();

画布被添加到一个 div 视图中。

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

【讨论】:

主要是尝试jfiddle,应该是功能齐全的保存上传到jfiddle的东西。 两件事 1) 我正在使用上传控制器上传图片,bootstrap modal 没有显示图片,然后我点击 crop button 现在它正在显示图片。现在,如果我关闭模式并再次上传图像,它会显示旧图像。 图像正在从画布中分割出来,并且正在保存画布。上传的图像是从修改后的画布构建的。您要么需要通过将数据发送回页面并在那里重建画布来从引导模式更新图像,要么直接从引导模式提交修改后的图像。 添加到答案中。希望它有所帮助,尽管这是文件所在的位置,超出了原始问题的范围,有点太远了。它上传并放入网络服务器的临时文件,需要通过访问 $_FILES 中的内容从 POST 中获取。调试 PHP 帮助很大。 @Tata​​rize 感谢代码,让我研究它们,希望我能够将其转换为图像并将其保存在文件夹中。再次感谢您回答我的问题 :)【参考方案2】:

这是基本的 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%),那么裁剪图像的原始数据将使得上传时间更长.

设置帖子大小限制:What is the size limit of a post request?

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

相反,我建议将 base64 裁剪后的图像转换为数据块,然后将其作为文件提交到表单中:

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

尚无画布元素的后备代码,我正在研究它。
旧版浏览器后备中的帖子大小限制是我自己放弃对旧版浏览器支持的原因之一。

更新 3:

我为 IE8 中的 canvas 元素推荐的后备方案:

http://flashcanvas.net/

它支持裁剪代码所需的所有画布功能。

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

【讨论】:

目前在我的手机上,我会在一两个小时后到家:/ 乘火车旅行是一种可怕的缓慢体验...... 另外,上传客户端裁剪图像是一件棘手的事情,我花了一些功夫,在第一时间我将它设为 base64 字符串,但由于它的长度,在提交时遇到了问题。之后,我添加了将base64字符串制作成图像文件blob的代码,然后我使用一些html5代码将其添加到表单中(因此也需要后备)。我最终在项目中放弃了所有旧浏览器,因为我一直遇到这些限制,并且为每个限制进行回退需要太多时间和工作。 回来,给我一分钟让 ajs 小提琴。 好的,我会试试你发布的内容,然后告诉你结果。 不再适用于 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); "

以上是关于我应该如何使用 jcrop 在客户端裁剪图像并上传?的主要内容,如果未能解决你的问题,请参考以下文章

一种上传多个文件并使用 JCrop 裁剪它们的方法?

上传前在浏览器中裁剪图像[关闭]

使用 jcrop 和 CodeIgniter 裁剪图像

在创建图像之前使用 jCrop 和 cloudinary through rails 4 进行裁剪

使用 jcrop 裁剪后预览图像不显示

Rails:使用 Jcrop 和回形针裁剪图像后出现重定向问题