H5實現圖片預覽、裁剪、批量上傳

前端代碼: css

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="A basic demo of Cropper.">
  <meta name="keywords" content="HTML, CSS, JS, JavaScript, jQuery plugin, image cropping, image crop, image move, image zoom, image rotate, image scale, front-end, frontend, web development">
  <meta name="author" content="Fengyuan Chen">
  <title>Cropper Test</title>
  <link rel="stylesheet" href="../dist/cropper.css">
  <style>
  .upload-img {
      width: 35%;
      overflow: hidden;
      margin-right: 20px;
  }
  .upload-preview,.upload-opr {
      overflow: hidden;
  }
  .upload-preview {
      margin-top: 50px;
  }
  .img-preview {
      float: left;
      overflow: hidden;
      border: 1px solid #7E7E7E;
      margin-right: 10px;
  }
  </style>
</head>
<body>
    <div class="upload-opr">
    <button id="upload-btn" type="button" style="display: none;">上傳圖片</button>
    <button id="enable-cropper" type="button" style="display: none;">從新裁剪</button>
    <input type="file" id="select-image" accept="image/*" />
    </div>
    <div class="upload-img">
    <img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/+IH6ElDQ19QUk9GSUxFAAEBAAAH2GFwcGwCIAAAbW50clJHQiBYWVogB9kAAgAZAAsAGgALYWNzcEFQUEwAAAAAYXBwbAAAAAAAAAAAAAAAAAAAAAAAAPbWAAEAAAAA0y1hcHBsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALZGVzYwAAAQgAAABvZHNjbQAAAXgAAAWcY3BydAAABxQAAAA4d3RwdAAAB0wAAAAUclhZWgAAB2AAAAAUZ1hZWgAAB3QAAAAUYlhZWgAAB4gAAAAUclRSQwAAB5wAAAAOY2hhZAAAB6wAAAAsYlRSQwAAB5wAAAAOZ1RSQwAAB5wAAAAOZGVzYwAAAAAAAAAUR2VuZXJpYyBSR0IgUHJvZmlsZQAAAAAAAAAAAAAAFEdlbmVyaWMgUkdCIFByb2ZpbGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG1sdWMAAAAAAAAAHwAAAAxza1NLAAAAKAAAAYRkYURLAAAALgAAAaxjYUVTAAAAJAAAAdp2aVZOAAAAJAAAAf5wdEJSAAAAJgAAAiJ1a1VBAAAAKgAAAkhmckZVAAAAKAAAAnJodUhVAAAAKAAAApp6aFRXAAAAFgAAAsJuYk5PAAAAJgAAAthjc0NaAAAAIgAAAv5oZUlMAAAAHgAAAyBpdElUAAAAKAAAAz5yb1JPAAAAJAAAA2ZkZURFAAAALAAAA4prb0tSAAAAFgAAA7ZzdlNFAAAAJgAAAth6aENOAAAAFgAAA8xqYUpQAAAAGgAAA+JlbEdSAAAAIgAAA/xwdFBPAAAAJgAABB5ubE5MAAAAKAAABERlc0VTAAAAJgAABB50aFRIAAAAJAAABGx0clRSAAAAIgAABJBmaUZJAAAAKAAABLJockhSAAAAKAAABNpwbFBMAAAALAAABQJydVJVAAAAIgAABS5hckVHAAAAJgAABVBlblVTAAAAJgAABXYAVgFhAGUAbwBiAGUAYwBuAP0AIABSAEcAQgAgAHAAcgBvAGYAaQBsAEcAZQBuAGUAcgBlAGwAIABSAEcAQgAtAGIAZQBzAGsAcgBpAHYAZQBsAHMAZQBQAGUAcgBmAGkAbAAgAFIARwBCACAAZwBlAG4A6AByAGkAYwBDHqUAdQAgAGgA7ABuAGgAIABSAEcAQgAgAEMAaAB1AG4AZwBQAGUAcgBmAGkAbAAgAFIARwBCACAARwBlAG4A6QByAGkAYwBvBBcEMAQzBDAEOwRMBD0EOAQ5ACAEPwRABD4ERAQwBDkEOwAgAFIARwBCAFAAcgBvAGYAaQBsACAAZwDpAG4A6QByAGkAcQB1AGUAIABSAFYAQgDBAGwAdABhAGwA4QBuAG8AcwAgAFIARwBCACAAcAByAG8AZgBpAGyQGnUoACAAUgBHAEIAIIJyX2ljz4/wAEcAZQBuAGUAcgBpAHMAawAgAFIARwBCAC0AcAByAG8AZgBpAGwATwBiAGUAYwBuAP0AIABSAEcAQgAgAHAAcgBvAGYAaQBsBeQF6AXVBeQF2QXcACAAUgBHAEIAIAXbBdwF3AXZAFAAcgBvAGYAaQBsAG8AIABSAEcAQgAgAGcAZQBuAGUAcgBpAGMAbwBQAHIAbwBmAGkAbAAgAFIARwBCACAAZwBlAG4AZQByAGkAYwBBAGwAbABnAGUAbQBlAGkAbgBlAHMAIABSAEcAQgAtAFAAcgBvAGYAaQBsx3y8GAAgAFIARwBCACDVBLhc0wzHfGZukBoAIABSAEcAQgAgY8+P8GWHTvZOAIIsACAAUgBHAEIAIDDXMO0w1TChMKQw6wOTA7UDvQO5A7oDzAAgA8ADwQO/A8YDrwO7ACAAUgBHAEIAUABlAHIAZgBpAGwAIABSAEcAQgAgAGcAZQBuAOkAcgBpAGMAbwBBAGwAZwBlAG0AZQBlAG4AIABSAEcAQgAtAHAAcgBvAGYAaQBlAGwOQg4bDiMORA4fDiUOTAAgAFIARwBCACAOFw4xDkgOJw5EDhsARwBlAG4AZQBsACAAUgBHAEIAIABQAHIAbwBmAGkAbABpAFkAbABlAGkAbgBlAG4AIABSAEcAQgAtAHAAcgBvAGYAaQBpAGwAaQBHAGUAbgBlAHIAaQENAGsAaQAgAFIARwBCACAAcAByAG8AZgBpAGwAVQBuAGkAdwBlAHIAcwBhAGwAbgB5ACAAcAByAG8AZgBpAGwAIABSAEcAQgQeBDEESQQ4BDkAIAQ/BEAEPgREBDgEOwRMACAAUgBHAEIGRQZEBkEAIAYqBjkGMQZKBkEAIABSAEcAQgAgBicGRAY5BicGRQBHAGUAbgBlAHIAaQBjACAAUgBHAEIAIABQAHIAbwBmAGkAbABldGV4dAAAAABDb3B5cmlnaHQgMjAwNyBBcHBsZSBJbmMuLCBhbGwgcmlnaHRzIHJlc2VydmVkLgBYWVogAAAAAAAA81IAAQAAAAEWz1hZWiAAAAAAAAB0TQAAPe4AAAPQWFlaIAAAAAAAAFp1AACscwAAFzRYWVogAAAAAAAAKBoAABWfAAC4NmN1cnYAAAAAAAAAAQHNAABzZjMyAAAAAAABDEIAAAXe///zJgAAB5IAAP2R///7ov///aMAAAPcAADAbP/AABEIAMgAyAMBIgACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2wBDAAICAgICAgMCAgMFAwMDBQYFBQUFBggGBgYGBggKCAgICAgICgoKCgoKCgoMDAwMDAwODg4ODg8PDw8PDw8PDw//2wBDAQICAgQEBAcEBAcQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/3QAEAA3/2gAMAwEAAhEDEQA/AP1AoooryzQKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//9D9QKKKK8s0CiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP//R/UCiiivLNAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD//0v1AoooryzQKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//9P9QKKKK8s0CiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP//U/UCiiivLNAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD//1f1AoooryzQKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//9b9QKKKK8s0CiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP//X/UCiiivLNAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD//0P1AoooryzQKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//9H9QKKKK8s0CiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP//S/UCiiivLNAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD//0/1AoooryzQKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//9k="/>
    </div>
    <div class="upload-preview">
    <div class="img-preview" style="width: 400px;height: 300px;"></div>
    <div class="img-preview" style="width: 240px;height: 180px;"></div>
    <div class="img-preview"  style="width: 120px;height: 90px;"></div>
    </div>
    <!-- Scripts -->
    <script src="../assets/js/jquery.min.js"></script>
    <script src="../dist/cropper.js"></script>
    <script src="../assets/js/async.js"></script>
    <script>
        $(function(){
            var $image = $('.upload-img img');
            $image.cropper({
                preview: '.img-preview', //不一樣尺寸預覽區
                aspectRatio: 4/3, //裁剪比例,NaN-自由選擇區域
                autoCropArea: 0.9, //初始裁剪區域佔圖片比例
                crop: function(data) { //裁剪操做回調
                }
            });
            var fileName; //選擇上傳的文件名
            $('#select-image').change(function(){
                var file = this.files[0];
                fileName = file.name;
                var reader = new FileReader();
                //reader回調,從新初始裁剪區
                reader.onload = function(){
                    // 經過 reader.result 來訪問生成的 DataURL
                    var url = reader.result;
                    //選擇圖片後從新初始裁剪區
                    $('.upload-img img').attr('src', url);
                    $image.cropper('reset', true).cropper('replace', url);
                    //顯示上傳按鈕
                    $('#upload-btn').show();
                    //隱藏從新裁剪按鈕
                    $('#enable-cropper').hide();
                };
                reader.readAsDataURL(file);
            });

            /*解除裁剪區鎖定*/
            $('#enable-cropper').click(function() {
                $image.cropper('enable');
                $('#upload-btn').show();
                $(this).hide();
            });

            /*
             * 上傳圖片
             */
            $('#upload-btn').click(function() {
                var type = $image.attr('src').split(';')[0].split(':')[1];
                //series 中的方法順序執行裁剪不一樣尺寸的圖片獲得blob後調用callback上傳圖片
                async.series({
                    '_': function(callback) {
                        $image.cropper("getCroppedCanvas").toBlob(function(blob) {
                            callback(null, blob);
                        }, type);
                    },
                    '400x300': function(callback) {
                        $image.cropper("getCroppedCanvas", { "width": 400, "height": 300 }).toBlob(function(blob) {
                            callback(null, blob);
                        }, type);
                    },
                    '240x180': function(callback) {
                        $image.cropper("getCroppedCanvas", { "width": 240, "height": 180 }).toBlob(function(blob) {
                            callback(null, blob);
                        }, type);
                    },
                    '120x90': function(callback) {
                        $image.cropper("getCroppedCanvas", { "width": 120, "height": 90 }).toBlob(function(blob) {
                            callback(null, blob);
                        }, type);
                    }
                },
                //異步最終回調,上傳圖片
                function(err, results){
                    //results: {'_', blob Object, '400x300': blob Object, '240x180': blob Object, ...}
                    console.log("results", JSON.stringify(results));
                    var formData = new FormData();
                    for(var key in results) {
                        var wh = key.split('x',2);
                        var sizeName = fileName;
                        if(wh.length == 2) {
                            var idx = fileName.lastIndexOf('.')
                            sizeName = fileName.substring(0, idx)+'_' + key + fileName.substring(idx)
                        }
                        //把要上傳的圖片blob加到formData中
                        formData.append("file", results[key], sizeName);
                    }
                    $.ajax({
                        type: "POST",
                        url: '/multiUpload',
                        data: formData,
                        contentType: false, //必須
                        processData: false, //必須
                        dataType: "json",
                        success: function(retJson){
                            //上傳成功回調函數
                            if(uploadCallback && typeof uploadCallback == 'function') {
                                uploadCallback(retJson);
                            } else {
                                console.log(JSON.stringify(retJson));
                            }
                            if(retJson.code == 0) {
                                $('#upload-btn').hide();
                                $image.cropper("disable");
                                $('#enable-cropper').show();
                            }
                        },
                        error : function() {
                            $('#upload-btn').hide();
                            $image.cropper("disable");
                            $('#enable-cropper').show();
                            console.log('failed');
                            alert('上傳失敗');
                        },
                        beforeSend: function(){
                        },
                        complete: function(){

                        }
                    });
                });
            });
        })
    </script>
</body>
</html>

服務端代碼:結合Spring MVC實現上傳,關於spring mvc上傳網上不少,配置就不詳細說明了html

@RestController
public class MainController {
    private static SimpleDateFormat df = new SimpleDateFormat("YYYY/MM/dd/");

    private String uploadUrlPrefix = "http://localhost/upload";
    private String uploadDir = "/data/upload";
    
    @RequestMapping(value = "/multiUpload", name = "批量上傳圖片")
    public String uploadMulti(@RequestParam("file") CommonsMultipartFile[] files) throws IOException {
        JSONObject json = new JSONObject();
        if(files == null) {
            json.put("code", -1);
            json.put("message", "沒有上傳文件");
            return json.toJSONString();
        }
        JSONArray jsonArray = new JSONArray();
        json.put("data", jsonArray);
        String uuid = UUID.randomUUID().toString();
        for(CommonsMultipartFile file : files) {
            String oriFileName = file.getOriginalFilename();
            StringBuilder newFileName = new StringBuilder();
            boolean isBase = false;
            if(oriFileName.matches(".*_\\d+x\\d+\\.\\w+")) {
                newFileName.append(oriFileName.replaceFirst("(.*)(_\\d+x\\d+\\.\\w+)", uuid+"$2"));
            } else {
                newFileName.append(uuid).append(oriFileName.substring(oriFileName.lastIndexOf('.')));
                isBase = true;
            }
            String timeDir = df.format(new Date());
            String imgPath = uploadDir + File.separatorChar + timeDir + fileName;
            log.info("save file name: {}", newFileName);
            
            File imgFile = new File(imgPath);
            if(!imgFile.getParentFile().exists()) {
                imgFile.getParentFile().mkdirs();
            }
            //保存文件
            file.transferTo(imgFile);
            JSONObject imgJson = new JSONObject();
            imgJson.put("oriFileName", oriFileName);
            imgJson.put("state", "SUCCESS");
            imgJson.put("url", uploadUrlPrefix + "/" + uri);
            jsonArray.add(imgJson);
            if(isBase) {
                json.put("baseUrl", imgJson.getString("url"));
            }
        }
        json.put("code", 0);
        json.put("message", "上傳成功");
        return json.toJSONString();
    }
}

cropper js 參考:https://fengyuanchen.github.io/cropper/前端

async異步庫參考:https://github.com/caolan/asyncjava

大致思路:jquery

  1.  用cropper js 實現圖片預覽,裁剪
  2.  調用cropper js的getCroppedCanvas來獲取不一樣尺寸的圖片canvas
  3.  結合async 實現順序地從canvas.toBlob的回調中獲取到blob,很好的解決了異步的問題,這點很是重要
  4. 用FormData裝載圖片,ajax上傳

效果:git

相關文章
相關標籤/搜索