• 【jQuery插件分享】Cropper——一个简单方便的图片裁剪插件


    原文:https://segmentfault.com/a/1190000012344970

    插件介绍

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

      

  • 相关阅读:
    have you declared this activity in your AndroidManifest.xml?
    Android收回输入法的实现
    Android手机Home键重写
    Android屏幕点击事件重写
    拖动ListView列表时背景变黑
    AFNetworking vs ASIHTTPRequest vs MKNetworkKit
    libgif.so
    android.support.v4.widget.DrawerLayout
    Titanium vs PhoneGap
    Non-constant Fields in Case Labels
  • 原文地址:https://www.cnblogs.com/phoebeyue/p/10253882.html
Copyright © 2020-2023  润新知