原文:https://segmentfault.com/a/1190000012344970
插件介绍
这是一个我在写以前的项目的途中发现的一个国人写的jQuery图像裁剪插件,当时想实现用户资料的头像上传功能,并且能够预览图片,和对图片进行简单的裁剪、旋转,花了不少时间才看到了这个插件,感觉功能挺全面,代码实现起来也挺简单,再加上用的是Bootstrap,对移动端操作也有适配,于是就用了。现在稍微有点时间就记录一下,方便以后再用的时候查阅。另外也有对应的js版本。
官方文档(英文)
-
jQuery
-
js
兼容性
兼容所有支持了Canvas的浏览器(IE9+),一小部分功能例外,具体请查看官方文档。
得到裁剪到的图像的canvas,如果没有裁剪,那么就返回的是整个原图图像的canvas。
这是最重要的一个方法,通过这个方法就可以得到裁剪后的图像,再使用toDataURL()
得到base64 dataURL(不指定格式的话会是png格式)或者toBlob()
得到Blob,然后就可以很轻松地将图片上传至服务器上或者显示在某个img标签中了。例如:
// 转换为png格式的dataURL var dataURL = $().cropper('getCroppedCanvas', { 100, height:100 }).toDataURL('image/png'); // 转换为Blob后显示在img标签中 var URL = window.URL || window.webkitURL; $().cropper('getCroppedCanvas', { 100, height:100 }).toBlob(function (blob) { $().attr('src',URL.createObjectURL(blob)); });
简单实例
在页面直接使用cropper
接下来只是实现一个简单的功能:网页中可以上传图片,然后对图片进行裁剪,点击确定后会显示出裁剪后的图片。
代码如下:
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <title>裁剪图片</title> <link href="https://cdn.bootcss.com/cropper/3.1.3/cropper.min.css" rel="stylesheet"> <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> <style> .row{ margin-bottom: 5px; } #photo { max- 100%; } .img-preview { 100px; height: 100px; overflow: hidden; } button { margin-top:10px; } #result { 150px; height: 150px; } </style> </head> <body> <div class="container"> <div class="row"> <div class="col-sm-12 text-center"> <label for="input" class="btn btn-danger" id=""> <span>选择图片</span> <input type="file" id="input" class="sr-only"> </label> </div> </div> <div class="row"> <div class="col-sm-6 col-sm-offset-2"> <img src="" id="photo"> </div> <div class="col-sm-2"> <div> <p> 预览(100*100): </p> <div class="img-preview"> </div> </div> <button class="btn btn-primary" onclick="crop()">裁剪图片</button> <div> <br/> <p> 结果: </p> <img src="" alt="裁剪结果" id="result"> </div> </div> </div> </div> <!-- Scripts --> <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script> <script src="https://cdn.bootcss.com/cropper/3.1.3/cropper.min.js"></script> <script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> <script> // 修改自官方demo的js var initCropper = function (img, input){ var $image = img; var options = { aspectRatio: 1, // 纵横比 viewMode: 2, preview: '.img-preview' // 预览图的class名 }; $image.cropper(options); var $inputImage = input; var uploadedImageURL; if (URL) { // 给input添加监听 $inputImage.change(function () { var files = this.files; var file; if (!$image.data('cropper')) { return; } if (files && files.length) { file = files[0]; // 判断是否是图像文件 if (/^image/w+$/.test(file.type)) { // 如果URL已存在就先释放 if (uploadedImageURL) { URL.revokeObjectURL(uploadedImageURL); } uploadedImageURL = URL.createObjectURL(file); // 销毁cropper后更改src属性再重新创建cropper $image.cropper('destroy').attr('src', uploadedImageURL).cropper(options); $inputImage.val(''); } else { window.alert('请选择一个图像文件!'); } } }); } else { $inputImage.prop('disabled', true).addClass('disabled'); } } var crop = function(){ var $image = $('#photo'); var $target = $('#result'); $image.cropper('getCroppedCanvas',{ 300, // 裁剪后的长宽 height:300 }).toBlob(function(blob){ // 裁剪后将图片放到指定标签 $target.attr('src', URL.createObjectURL(blob)); }); } $(function(){ initCropper($('#photo'),$('#input')); }); </script> </body> </html>
在bootstrap模态框中使用cropper
虽然在模态框中可以像上面一样使用cropper,甚至我以前写的项目也是跟上面一样,但是这次整理的时候突然发现了一个bug:当隐藏模态框后调整浏览器大小(甚至按f12),再打开模态框后cropper的容器会改变,导致难以使用。于是,我在GitHub中翻找了issue,在官方的example中找到了对应的解决方法。但其实这个解决方法也是一种暴力解法,即模态框隐藏后销毁cropper,打开后重新创建cropper,可能会有别的方法,因为不确定会不会有别的bug,所以暂时还是用官方的方法比较好。
代码如下:
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <title>上传头像</title> <link href="https://cdn.bootcss.com/cropper/3.1.3/cropper.min.css" rel="stylesheet"> <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> <style type="text/css"> body{ text-align: center; } #user-photo { 300px; height:300px; margin-top: 10px; } #photo { max-100%; max-height:350px; } .img-preview-box { text-align: center; } .img-preview-box > div { display: inline-block;; margin-right: 10px; } .img-preview { overflow: hidden; } .img-preview-box .img-preview-lg { 150px; height: 150px; } .img-preview-box .img-preview-md { 100px; height: 100px; } .img-preview-box .img-preview-sm { 50px; height: 50px; border-radius: 50%; } </style> </head> <body> <button class="btn btn-primary" data-target="#changeModal" data-toggle="modal">打开</button><br/> <div class="user-photo-box"> <img id="user-photo" src=""> </div> </div> <div class="modal fade" id="changeModal" tabindex="-1" role="dialog" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> <h4 class="modal-title text-primary"> <i class="fa fa-pencil"></i> 更换头像 </h4> </div> <div class="modal-body"> <p class="tip-info text-center"> 未选择图片 </p> <div class="img-container hidden"> <img src="" alt="" id="photo"> </div> <div class="img-preview-box hidden"> <hr> <span>150*150:</span> <div class="img-preview img-preview-lg"> </div> <span>100*100:</span> <div class="img-preview img-preview-md"> </div> <span>30*30:</span> <div class="img-preview img-preview-sm"> </div> </div> </div> <div class="modal-footer"> <label class="btn btn-danger pull-left" for="photoInput"> <input type="file" class="sr-only" id="photoInput" accept="image/*"> <span>打开图片</span> </label> <button class="btn btn-primary disabled" disabled="true" onclick="sendPhoto();">提交</button> <button class="btn btn-close" aria-hidden="true" data-dismiss="modal">取消</button> </div> </div> </div> </div> <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script> <script src="https://cdn.bootcss.com/cropper/3.1.3/cropper.min.js"></script> <script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> <script type="text/javascript"> var initCropperInModal = function(img, input, modal){ var $image = img; var $inputImage = input; var $modal = modal; var options = { aspectRatio: 1, // 纵横比 viewMode: 2, preview: '.img-preview' // 预览图的class名 }; // 模态框隐藏后需要保存的数据对象 var saveData = {}; var URL = window.URL || window.webkitURL; var blobURL; $modal.on('show.bs.modal',function () { // 如果打开模态框时没有选择文件就点击“打开图片”按钮 if(!$inputImage.val()){ $inputImage.click(); } }).on('shown.bs.modal', function () { // 重新创建 $image.cropper( $.extend(options, { ready: function () { // 当剪切界面就绪后,恢复数据 if(saveData.canvasData){ $image.cropper('setCanvasData', saveData.canvasData); $image.cropper('setCropBoxData', saveData.cropBoxData); } } })); }).on('hidden.bs.modal', function () { // 保存相关数据 saveData.cropBoxData = $image.cropper('getCropBoxData'); saveData.canvasData = $image.cropper('getCanvasData'); // 销毁并将图片保存在img标签 $image.cropper('destroy').attr('src',blobURL); }); if (URL) { $inputImage.change(function() { var files = this.files; var file; if (!$image.data('cropper')) { return; } if (files && files.length) { file = files[0]; if (/^image/w+$/.test(file.type)) { if(blobURL) { URL.revokeObjectURL(blobURL); } blobURL = URL.createObjectURL(file); // 重置cropper,将图像替换 $image.cropper('reset').cropper('replace', blobURL); // 选择文件后,显示和隐藏相关内容 $('.img-container').removeClass('hidden'); $('.img-preview-box').removeClass('hidden'); $('#changeModal .disabled').removeAttr('disabled').removeClass('disabled'); $('#changeModal .tip-info').addClass('hidden'); } else { window.alert('请选择一个图像文件!'); } } }); } else { $inputImage.prop('disabled', true).addClass('disabled'); } } var sendPhoto = function(){ $('#photo').cropper('getCroppedCanvas',{ 300, height:300 }).toBlob(function(blob){ // 转化为blob后更改src属性,隐藏模态框 $('#user-photo').attr('src',URL.createObjectURL(blob)); $('#changeModal').modal('hide'); }); } $(function(){ initCropperInModal($('#photo'),$('#photoInput'),$('#changeModal')); }); </script> </body> </html>
使用cropper来上传图片到服务器
由于cropper可以得到两种裁剪后图片的数据(即blob和dataURL),所以对应的上传到后台也会有两种方法,在这里我只写一种使用ajax上传base64 dataURL的,另一种方法如果有兴趣,可以自己尝试。
页面中,将上面的sendPhoto方法改为:
var sendPhoto = function () { // 得到PNG格式的dataURL var photo = $('#photo').cropper('getCroppedCanvas', { 300, height: 300 }).toDataURL('image/png'); $.ajax({ url: '上传地址', // 要上传的地址 type: 'post', data: { 'imgData': photo }, dataType: 'json', success: function (data) { if (data.status == 0) { // 将上传的头像的地址填入,为保证不载入缓存加个随机数 $('.user-photo').attr('src', '头像地址?t=' + Math.random()); $('#changeModal').modal('hide'); } else { alert(data.info); } } }); }
后台中,Java的主要代码如下:(使用了jdk8的Base64,,如果是低版本请自行替换)
/** * 将Base64位编码的图片进行解码,并保存到指定目录 */ public static void decodeBase64DataURLToImage(String dataURL, String path, String imgName) throws IOException { // 将dataURL开头的非base64字符删除 String base64 = dataURL.substring(dataURL.indexOf(",") + 1); FileOutputStream write = new FileOutputStream(new File(path + imgName)); byte[] decoderBytes = Base64.getDecoder().decode(base64); write.write(decoderBytes); write.close(); }