插件介绍

这是一个我在写以前的项目的途中发现的一个国人写的jQuery图像裁剪插件,当时想实现用户资料的头像上传功能,并且能够预览图片,和对图片进行简单的裁剪、旋转,花了不少时间才看到了这个插件,感觉功能挺全面,代码实现起来也挺简单,再加上用的是Bootstrap,对移动端操作也有适配,于是就用了。现在稍微有点时间就记录一下,方便以后再用的时候查阅。另外也有对应的js版本。

官方文档(英文)

  • jQuery
  • GitHub项目地址
  • 官方示例
  • js
  • GitHub项目地址

兼容性

兼容所有支持了Canvas的浏览器(IE9+),一小部分功能例外,具体请查看官方文档。

得到裁剪到的图像的canvas,如果没有裁剪,那么就返回的是整个原图图像的canvas。

这是最重要的一个方法,通过这个方法就可以得到裁剪后的图像,再使用toDataURL()得到base64 dataURL(不指定格式的话会是png格式)或者toBlob()得到Blob,然后就可以很轻松地将图片上传至服务器上或者显示在某个img标签中了。例如:

// 转换为png格式的dataURL
var dataURL = $().cropper('getCroppedCanvas', {
    width:100,
    height:100
}).toDataURL('image/png');

// 转换为Blob后显示在img标签中
var URL = window.URL || window.webkitURL;
$().cropper('getCroppedCanvas', {
    width: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-width: 100%;
        }
        .img-preview {
            width: 100px;
            height: 100px;
            overflow: hidden;
        }
        button {
            margin-top:10px;
        }
        #result {
            width: 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',{
                width:300, // 裁剪后的长宽
                height:300
            }).toBlob(function(blob){
                // 裁剪后将图片放到指定标签
                $target.attr('src', URL.createObjectURL(blob));
            });
        }
        $(function(){
            initCropper($('#photo'),$('#input'));
        });
    </script>
</body>
</html>

  

jQuery jcrop实现截屏_bootstrap

在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 {
        width:300px;
        height:300px;
        margin-top: 10px;
    }
    #photo {
        max-width: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 {
        width: 150px;
        height: 150px;
    }
    .img-preview-box .img-preview-md {
        width: 100px;
        height: 100px;
    }
    .img-preview-box .img-preview-sm {
        width: 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',{
            width: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>

  

jQuery jcrop实现截屏_jQuery jcrop实现截屏_02

jQuery jcrop实现截屏_css_03

 

 

使用cropper来上传图片到服务器

由于cropper可以得到两种裁剪后图片的数据(即blob和dataURL),所以对应的上传到后台也会有两种方法,在这里我只写一种使用ajax上传base64 dataURL的,另一种方法如果有兴趣,可以自己尝试。

页面中,将上面的sendPhoto方法改为:

var sendPhoto = function () {
    // 得到PNG格式的dataURL
    var photo = $('#photo').cropper('getCroppedCanvas', {
        width: 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();
    }