手機端上傳照片實現 壓縮、拖放、縮放、裁剪、合成拼圖等功能

Mr.聶發表於2019-06-28

一、序

  如題,最近工作中遇到一個移動端使用者上傳照片,然後線上編輯新增一些別的圖片的合成的功能,類似於超級簡化版美圖秀秀。總結了一下大致的操作包含 上傳圖片,圖片壓縮、觸控拖動圖片、放大/縮小、新增別的圖片進行合成,最後生成一張新圖片。功能比較多,問遍了度娘,也沒什麼系統的有用資訊。蛋疼。。。於是挽起袖子自己擼程式碼。此過程略痛苦,手機型號不同,相容性問題比較多。這個我會一一細說。

  額外話,這個demo還涉及到  微信分享,手機驗證碼,這些程式碼也沒有刪,感興趣的可以看看。

  廢話不多說,下面是目錄。先看效果圖,然後我再簡單說說實現過程,把原始碼貼出來, 有需要的可以去我的 github 下載原始碼。

  1.效果圖
  2.原理
  3.問題分析、解決方案
  4.demo下載

1.效果圖

 


2.原理

  圖片處理,直接想到 canvas , 這裡也是用畫布來做的,分析下流程需要上傳、編輯、生成新圖 這3個步驟。

  a. 上傳。 input 檔案上傳,然後將上傳的檔案轉換為 base64 的格式,這裡主要做圖片的處理。

  b. 壓縮。通過畫布將上傳的圖片進行等比例壓縮,根據你的選擇,圖片一般會壓縮到100KB左右,具體的看寬高設定,會大、會小。不壓縮,無法線上編輯,圖片太大了,會卡的要命。

  c. 拖拽。將圖片設定為背景圖,捕捉 touch 座標,進行計算,調整圖片的橫縱座標。此 demo 電腦手機通用,只需要將 touch 部分的座標值改成 mouseX,mouseY 或者 clientX、clientY即可。

  d. 圖片的裁剪、放大、縮小,旋轉。裁剪是根據背景圖的座標,設定畫布大小裁剪圖片。縮放、旋轉,canvas有api,主要做資料的計算。這裡這些操作沒有采用touch的手勢計算。因為我的素材很小,手指操作不方便。所以加的按鈕,如果想做手勢的邏輯類似,做一下座標轉換即可。

  d. 圖片合成。還是canvas的api,新增圖片,然後編輯一番,最終儲存成base64,傳給後臺,生成一張圖片,本地生成沒辦法直接使用。所以配合服務端生成最合適。

  e. demo引入了 jQuery ,請使用1.1以上版本的 jQuery ,我用的是 jquery-1.11.3.min.js 。

 

  貼一下核心程式碼

  

(function (win) {
  var obj = {
    a: 1,
    oldX: "",
    oldY: "",
    ratio: 1,
    ratioBig: 0,
    jsonUrl: 'https://xxxxxxx/api/xxxx',
    radioSmall: 0,
    num: 1,
    setWidth: 432,    //圖片需要壓縮到的尺寸   216,135
    theDataBase: "",
    yaSuoBase64: "",
    hcBase64: "",
    image: new Image(),    //放到畫布的img
    bookCode: 12221212,
    flag: true,
    bodyWidth: '',
    bodyHeight: '',
    ifImgUpload: false,
    base64Zong: "",
    imgNewName: "",
//首頁的js。橫豎屏、微信分享、滑動跳轉
    page1Init: function () {
      var w = this;
      if ($(win).width() >= $(win).height()) {
        w.bodyWidth = parseInt($(window).height()) + 64;
        w.bodyHeight = $(window).width();
        $('body,html').width(w.bodyWidth);
        $('body,html').height(w.bodyHeight);
      } else {
        w.bodyWidth = $(window).width();
        w.bodyHeight = $(window).height();
        $('body,html').width(w.bodyWidth);
        $('body,html').height(w.bodyHeight);
      }

      window.addEventListener("onorientationchange" in window ? "orientationchange" : "resize", function () {
        if (window.orientation === 180 || window.orientation === 0) {
          $('body,html').width(bodyWidth);
          $('body,html').height(bodyHeight);
        }
        if (window.orientation === 90 || window.orientation === -90) {
          $('body,html').width(bodyWidth);
          $('body,html').height(bodyHeight);
        }
      }, false);

      // 初始化微信分享的
      // w.wxConfig();
      // w.wxReady();

      var oldX = 0, oldY = 0;
      $("body").on("touchstart", imgTouchStart);
      $("body").on("touchmove", imgTouchMove);

      //手指按下時,捕捉事件,取座標值,設定引數
      function imgTouchStart(e) {
        //阻止事件冒泡的
        e.stopImmediatePropagation();
        oldX = e.originalEvent.touches[0].clientX;
        oldY = e.originalEvent.touches[0].clientY;
      }

      function imgTouchMove(e) {
        e.stopImmediatePropagation();
        var x = e.originalEvent.touches[0].clientX - oldX;
        var y = e.originalEvent.touches[0].clientY - oldY;
        if (y < -150) {
          window.location.href = 'getBooks.html?';
        }
      }
    },

//上傳照片頁的js。橫豎屏、微信分享、上傳照片
    page2Init: function () {
      var w = this;
      if ($(win).width() >= $(win).height()) {
        w.bodyWidth = parseInt($(window).height()) + 64;
        w.bodyHeight = $(window).width();
        $('body,html').width(w.bodyWidth);
        $('body,html').height(w.bodyHeight);
      } else {
        w.bodyWidth = $(window).width();
        w.bodyHeight = $(window).height();
        $('body,html').width(w.bodyWidth);
        $('body,html').height(w.bodyHeight);
      }

      window.addEventListener("onorientationchange" in window ? "orientationchange" : "resize", function () {
        if (window.orientation === 180 || window.orientation === 0) {
          $('body,html').width(bodyWidth);
          $('body,html').height(bodyHeight);
        }
        if (window.orientation === 90 || window.orientation === -90) {
          $('body,html').width(bodyWidth);
          $('body,html').height(bodyHeight);
        }
      }, false);

      // w.wxConfig();
      // w.wxReady3();

      //上傳圖片進行處理
      w.uploadImg();

      if (localStorage.getItem("imgFlag") == "true") {
        $(".f-scInLock").hide();
      }

      //選好照片,準備製作
      $('.m-loadBtn').on("tap", function () {
        $('.m-loadBtn').css({"color": "#C64100"});
        setTimeout(function () {
          $('.m-loadBtn').css({"color": "white"});
        }, 300);
        if (w.ifImgUpload == true) {
          $('.g-wrap2').hide();
          $('.g-wrap3').show();
          $("#m-loadImg").attr("src", w.yaSuoBase64);
        } else {
          $('.j-pop p').text("還沒有選擇喜歡照片吆!");
          $('.j-pop').show();
          setTimeout(function () {
            $('.j-pop').hide();
          }, 1500);
        }
      });

      $(".m-step1 .f-scIn1").on("tap", function () {
        $(".m-sucaiOut").show();
        $(".m-sucaiOut").css({"width": "70px", "opacity": "1"});
        $(".m-imgBox .f-sucai").attr("src", "img/sucai1.png");
      });
      $(".m-step1 .f-scIn2").on("tap", function () {
        $(".m-sucaiOut").show();
        $(".m-sucaiOut").css({"width": "70px", "opacity": "1"});
        $(".m-imgBox .f-sucai").attr("src", "img/sucai2.png");
      });
      $(".m-step1 .f-scIn3").on("tap", function () {
        var sucai3 = localStorage.getItem("imgFlag");
        if (sucai3 == "true") {
          $(".m-sucaiOut").show();
          $(".m-sucaiOut").css({"width": "70px", "opacity": "1"});
          $(".m-imgBox .f-sucai").attr("src", "img/sucai3.png");
        } else {
          $('.j-pop p').text("分享後可解鎖吆!");
          $('.j-pop').show();
          setTimeout(function () {
            $('.j-pop').hide();
          }, 1500);
        }
      })
      $(".f-sucaiDelete").on("tap", function () {
        $(".m-sucaiOut").css({"width": "1px", "opacity": "0", "top": "4px", "left": "100px"});
        $(".m-imgBox .f-sucai").attr("src", "img/wu.png");
      });


      $(".m-step2 .f-scIn1").on("tap", function () {
        $(".imgKuang .imgK").attr("src", "img/sucai4.png");
      });
      $(".m-step2 .f-scIn2").on("tap", function () {
        $(".imgKuang .imgK").attr("src", "img/sucai5.png");
      });


      $(".m-makeBtn").on("tap", function () {
        $("#loadingSection").show();
        setTimeout(function () {
          $("#loadingSection").hide();
        }, 1800);

        w.imageMake();
      });

      $(".m-getBtn").on("tap", function () {
        $(".m-login").show();
        $(".m-mengBan").show();
      });
      $(".m-closeLogin").on("tap", function () {
        $(".m-login").hide();
        $(".m-mengBan").hide();
      });

      $(".wx-share").on("tap", function () {
        $(".wx-share").hide();
        $(".m-mengBan").hide();
      });

      $(".m-oldManBtn").on("tap", function () {
        $(".wx-share").show();
        $(".m-mengBan").show();
        w.sendImgSouce();
        w.wxReady2();
      });

      w.initEvent();
    },


    resize: function () {},

    wxConfig: function () {
      var w = this;
      var jsapi_ticket, nonceStr, signature, timestamp, getCode;
      var url = 'http://xxxxxxx/'; //正式庫
      //獲取config配置
      var Url = window.location.href;
      $.ajax({
        type: 'get',
        url: 'http:/xxxxxxx',
        data: {
          weixinurl: Url
        },
        timeout: 10000, //10秒超時
        callbackParameter: 'callback',
        async: false,
        jsonp: "jsonpcallback",
        success: function (o) {
          jsapi_ticket = o.jsapi_ticket;
          nonceStr = o.nonceStr;
          signature = o.signature;
          timestamp = o.timestamp;
        }
      });
      wx.config({
        debug: false, // 開啟除錯模式,呼叫的所有api的返回值會在客戶端alert出來,若要檢視傳入的引數,可以在pc端開啟,引數資訊會通過log打出,僅在pc端時才會列印。
        appId: 'xxxxxxxxx', // 必填,公眾號的唯一標識
        timestamp: timestamp, // 必填,生成簽名的時間戳
        nonceStr: nonceStr, // 必填,生成簽名的隨機串
        signature: signature, // 必填,簽名,見附錄1
        jsApiList: [
          'checkJsApi',
          'onMenuShareTimeline',
          'onMenuShareAppMessage',
          'onMenuShareQQ',
          'onMenuShareWeibo',
          'onMenuShareQZone',
          'hideMenuItems',
          'showMenuItems',
          'hideAllNonBaseMenuItem',
          'showAllNonBaseMenuItem',
          'translateVoice',
          'startRecord',
          'stopRecord',
          'onVoiceRecordEnd',
          'playVoice',
          'onVoicePlayEnd',
          'pauseVoice',
          'stopVoice',
          'uploadVoice',
          'downloadVoice',
          'chooseImage',
          'previewImage',
          'uploadImage',
          'downloadImage',
          'getNetworkType',
          'openLocation',
          'getLocation',
          'hideOptionMenu',
          'showOptionMenu',
          'closeWindow',
          'scanQRCode',
          'chooseWXPay',
          'openProductSpecificView',
          'addCard',
          'chooseCard',
          'openCard'
        ] // 必填,需要使用的JS介面列表,所有JS介面列表見附錄2
      });
    },
    wxReady: function () {
      wx.ready(function () {
        wx.onMenuShareAppMessage({
          title: '嘿,我願意做你的聖誕老人!', // 分享標題
          link: 'http://xxxxxxxxx/index.html', // 分享連結
          desc: '叮叮噹,叮叮噹,今年你願意做我的聖誕老人嗎……',
          imgUrl: 'http://xxxxxxx/wxbanner.png', // 分享圖示
          trigger: function (res) {
          },
          success: function (res) {
            localStorage.setItem("imgFlag", "true");
          },
          cancel: function (res) {
          }
        });
        //alert('已註冊獲取“傳送給朋友圈”狀態事件');
        wx.onMenuShareAppMessage({
          title: '嘿,我願意做你的聖誕老人!', // 分享標題
          link: 'http://xxxxxxx/index.html', // 分享連結
          desc: '叮叮噹,叮叮噹,今年你願意做我的聖誕老人嗎……',
          imgUrl: 'http://xxxxxxx/wxbanner.png', // 分享圖示
          trigger: function (res) {
          },
          success: function (res) {
            localStorage.setItem("imgFlag", "true");
          },
          cancel: function (res) {
          }
        });
      });
    },
    wxReady3: function () {
      wx.ready(function () {
        wx.onMenuShareAppMessage({
          title: '嘿,我願意做你的聖誕老人!', // 分享標題
          link: 'http://xxxxxx/index.html', // 分享連結
          desc: '叮叮噹,叮叮噹,今年你願意做我的聖誕老人嗎……',
          imgUrl: 'http://xxxxxxxxx/wxbanner.png', // 分享圖示
          trigger: function (res) {
          },
          success: function (res) {
            localStorage.setItem("imgFlag", "true");
            $(".f-scInLock").hide();
            $(".f-scInLock").css({"width": "0px", "height": "0px", "display": "none"});
          },
          cancel: function (res) {
          }
        });
        //alert('已註冊獲取“傳送給朋友圈”狀態事件');
        wx.onMenuShareAppMessage({
          title: '嘿,我願意做你的聖誕老人!', // 分享標題
          link: 'http://xxxxxxx/index.html', // 分享連結
          desc: '叮叮噹,叮叮噹,今年你願意做我的聖誕老人嗎……',
          imgUrl: 'http://xxxxxxx/img/wxbanner.png', // 分享圖示
          trigger: function (res) {
          },
          success: function (res) {
            localStorage.setItem("imgFlag", "true");
            $(".f-scInLock").hide();
            $(".f-scInLock").css({"width": "0px", "height": "0px", "display": "none"});
          },
          cancel: function (res) {
          }
        });
      });
    },
    wxReady2: function () {
      var w = this;
      wx.ready(function () {
        wx.onMenuShareAppMessage({
          title: '嘿,我願意做你的聖誕老人!', // 分享標題
          link: 'http://xxxxxxx/sharePage.html?imgName=' + w.imgNewName, // 分享連結
          desc: '叮叮噹,叮叮噹,今年你願意做我的聖誕老人嗎……',
          imgUrl: 'http://xxxxx/img/wxbanner.png', // 分享圖示
          trigger: function (res) {
          },
          success: function (res) {
            localStorage.setItem("imgFlag", "true");
            $(".wx-share").hide();
            $(".m-mengBan").hide();
            $(".f-scInLock").hide();
            $(".f-scInLock").css({"width": "0px", "height": "0px", "display": "none"});
          },
          cancel: function (res) {
          }
        });
        //alert('已註冊獲取“傳送給朋友圈”狀態事件');
        wx.onMenuShareAppMessage({
          title: '嘿,我願意做你的聖誕老人!', // 分享標題
          link: 'http://xxxxx/sharePage.html?imgName=' + w.imgNewName, // 分享連結
          desc: '叮叮噹,叮叮噹,今年你願意做我的聖誕老人嗎……',
          imgUrl: 'http://xxxxxx/img/wxbanner.png', // 分享圖示
          trigger: function (res) {
          },
          success: function (res) {
            localStorage.setItem("imgFlag", "true");
            $(".wx-share").hide();
            $(".m-mengBan").hide();
            $(".f-scInLock").hide();
            $(".f-scInLock").css({"width": "0px", "height": "0px", "display": "none"});
          },
          cancel: function (res) {
          }
        });
      });
    },


    //照片上傳函式
    uploadImg: function () {
      var w = this;
      var loadFile = document.getElementById("file");
      loadFile.addEventListener("change", function () {
        $(".m-thewen33").hide();
        $(".m-thewen").show();

        $(".f-theImg").css({"width": "100%"});
        w.ifImgUpload = true;

        $("#loadingSection").show();
        setTimeout(function () {
          $("#loadingSection").hide();
        }, 2700);

        var oFile = loadFile.files[0];
        console.log(oFile);
        if (!new RegExp("(jpg|jpeg|png)+", "gi").test(oFile.type)) {
          alert("照片上傳:檔案型別必須是JPG、JPEG、PNG");
          return;
        }
        //新建一個檔案物件
        var reader = new FileReader();
        //讀出這個檔案的 base64 的資料流,這個函式是專門轉base64的,不加他,下面沒法用base64
        reader.readAsDataURL(oFile);

        //因為  iOS 不支援 FileReader 的onload回撥,所以,這裡只能加延遲處理了
        setTimeout(function () {
          //先取照片的拍攝方向角
          EXIF.getData(oFile, function () {
            EXIF.getAllTags(this);
            var zzz = EXIF.getTag(this, 'Orientation');
            var spanData = document.getElementsByClassName("tspanme");
            if (zzz == 1 || zzz == undefined) {
              spanData[0].innerText = 0;
//                            alert("0度");
//                            console.log("0度");
            } else if (zzz == 6) {
              spanData[0].innerText = 90;
//                            alert("90度");
//                            console.log("90度");
            } else if (zzz == 8) {
              spanData[0].innerText = 270;
//                            alert("270度");
//                            console.log("270度");
            } else if (zzz == 3) {
              spanData[0].innerText = 180;
//                            alert("180度");
//                            console.log("180度");
            }
          });


          var img = new Image();
          img.src = reader.result;
          //根據拍攝角度不同,把圖片旋轉適當角度,糾正圖片
          //除了修改方向,不做其他任何修改
          setTimeout(function () {
            var spanData = document.getElementsByClassName("tspanme");
            var theText = spanData[0].innerText;

            console.log("這個儲存的角度" + theText);
            var canvas = document.createElement("canvas");
            var cantent = canvas.getContext("2d");
            var width = img.naturalWidth,
              height = img.naturalHeight;

            if (theText == 0) { //0
              canvas.width = width;
              canvas.height = height;
              cantent.drawImage(img, 0, 0, width, height, 0, 0, width, height);
              console.log("0");
            } else if (theText == 90) {
              canvas.width = height;
              canvas.height = width;
              cantent.save();
              cantent.rotate(90 * Math.PI / 180);
              cantent.drawImage(img, 0, -height);
              cantent.restore();
              console.log("90");
            } else if (theText == 180) {
              canvas.width = width;
              canvas.height = height;
              cantent.save();
              cantent.rotate(180 * Math.PI / 180);
              cantent.drawImage(img, -width, -height);
              cantent.restore();
              console.log("180");
            } else if (theText == 270) {
              canvas.width = height;
              canvas.height = width;
              cantent.save();
              cantent.rotate(270 * Math.PI / 180);
              cantent.drawImage(img, -width, 0);
              cantent.restore();
              console.log("270");
            }
            w.theDataBase = canvas.toDataURL();
            setTimeout(function () {
              w.yaSuoImg();
            }, 200);
          }, 300);
        }, 400);
      })
    },


    //處理圖片的函式
    imageMake: function () {
      var w = this,
        theAngle = 0,
        imgHat = document.getElementsByClassName("f-sucai")[0];

      theAngle = getEletAngle();
      console.log("當前素材的角度:" + theAngle);

      var canvasHat = document.createElement("canvas"),
        cantentHat = canvasHat.getContext("2d"),
        widthHat = imgHat.naturalWidth,
        heightHat = imgHat.naturalWidth;

      console.log(widthHat);
      console.log(heightHat);
      console.log(theAngle);
      canvasHat.width = widthHat;
      canvasHat.height = heightHat;
      cantentHat.save();
      cantentHat.translate(widthHat / 2, heightHat / 2);
      cantentHat.rotate(theAngle * Math.PI / 180);
      cantentHat.translate(-widthHat / 2, -heightHat / 2);
      cantentHat.drawImage(imgHat, 0, 0);
      cantentHat.restore();

      imgHat.src = canvasHat.toDataURL();

      setTimeout(function () {
        var img1 = document.getElementById("m-loadImg"),
          width = img1.naturalWidth,
          height = img1.naturalHeight,
          canvas = document.createElement("canvas"),
          cantent = canvas.getContext("2d"),
          oldTop = $("#m-loadImg")[0].offsetTop,
          oldLeft = $("#m-loadImg")[0].offsetLeft,
          imgK = document.getElementsByClassName("imgK")[0],
          imgKuangOut = document.getElementsByClassName("imgKuang")[0],
          imgSuCai = document.getElementsByClassName("f-sucai")[0],
          imgSuCaiOut = document.getElementsByClassName("m-sucaiOut")[0],

          //縮放之後圖片的寬度
          scWidth = imgSuCai.width,
          scHeight = imgSuCai.height,

          scOffsetTop = imgSuCaiOut.offsetTop,
          scOffsetLeft = imgSuCaiOut.offsetLeft;

        // console.log("------------------------");
        // console.log(imgSuCai);
        // console.log(imgSuCaiOut);
        // console.log(scWidth);
        // console.log(scHeight);
        // console.log(scOffsetTop);
        // console.log(scOffsetLeft);

        canvas.width = 270;
        canvas.height = 194;

        // console.log("原大小:");
        // console.log(img1.naturalWidth);
        // console.log(img1.naturalHeight);
        // console.log(imgK.naturalWidth);
        // console.log(imgK.naturalHeight);


        // console.log("相框的上定位" + imgK.offsetTop);
        // console.log("相框的下定位" + imgK.offsetLeft);
        //
        // console.log("插入素材的資料:");
        // console.log(scWidth);
        // console.log(scHeight);
        // console.log(scOffsetTop);
        // console.log(scOffsetLeft);


        var yasuobi = width / 270;
        // console.log("壓縮之後的寬度" + width);
        // console.log("position的上定位" + oldTop);
        // console.log("position的左定位" + oldLeft);

//                alert(-oldLeft*yasuobi+"水平位移");
//                alert(-oldTop*yasuobi+"垂直位移");


        setTimeout(function () {
          //畫上照片
          cantent.drawImage(img1, 0, 0, width, height, oldLeft, oldTop, 270, height / yasuobi);
          //畫上素材
          cantent.drawImage(imgSuCai, 0, 0, imgSuCai.naturalWidth, imgSuCai.naturalHeight, scOffsetLeft, scOffsetTop, scWidth, scHeight);
          //畫上相框
          cantent.drawImage(imgK, 0, 0, imgK.naturalWidth, imgK.naturalHeight, imgKuangOut.offsetLeft, imgKuangOut.offsetTop, imgK.width, imgK.height);
          w.hcBase64 = canvas.toDataURL();


          //做最終的合成
          var imgZong = new Image();
          imgZong.src = w.hcBase64;

          setTimeout(function () {
            var canvas111 = document.createElement("canvas"),
              cantent111 = canvas111.getContext("2d"),
              imgKuangTrue = document.getElementById("g-kuangTrue"),
              theEWM = document.getElementById("g-ewm");

            canvas.width = 326.5;
            canvas.height = 335;

            cantent.drawImage(imgKuangTrue, 0, 0, imgKuangTrue.naturalWidth, imgKuangTrue.naturalHeight, 0, 0, 326.5, 335);
            cantent.drawImage(imgZong, 0, 0, imgZong.naturalWidth, imgZong.naturalHeight, 28, 47, 270, 194);
            cantent.drawImage(theEWM, 0, 0, theEWM.naturalWidth, theEWM.naturalHeight, 236, 249, 64, 64);
            w.base64Zong = canvas.toDataURL();
            setTimeout(function () {
              $(".g-wrap3").hide();
              $(".g-wrap4").show();
              $(".f-shuchu").attr("src", w.base64Zong);
            }, 400)
          }, 500)
          // console.log("合成的圖片");
          //console.log(w.theDataBase.length/1024+"KB");
        }, 400)

      }, 700)
    },


    //壓縮函式
    yaSuoImg: function () {
      var w = this;
      yaWidth = w.setWidth / 2,    //  yaWidth  這個是實際照片一半的大小,通過設定它實現壓縮
        canvas = document.createElement("canvas"),
        cantent = canvas.getContext("2d"),
        img = new Image();
      img.src = w.theDataBase;
      //這個iOS是支援的,iOS不支援的是file物件的onload函式
      img.onload = function () {
        var width = img.naturalWidth,
          height = img.naturalHeight,
          //圖片的壓縮比
          theRadio = img.naturalWidth / yaWidth;
        //如果圖片尺寸小於設定畫布的尺寸,不壓縮,輸出原圖
        if (theRadio <= 1) {
          theRadio = 1;
          canvas.width = img.naturalWidth;
          canvas.height = img.naturalHeight;
          canvas.style.width = img.naturalWidth / 2 + "px";
          canvas.style.height = img.naturalHeight / 2 + "px";
          cantent.drawImage(img, 0, 0, width, height, 0, 0, width, height);
        } else {
          //為了避免失真,canvas實際大小設定為顯示大小的2倍
          canvas.width = yaWidth * 2;
          canvas.height = (height / theRadio) * 2;
          canvas.style.width = yaWidth + "px";
          canvas.style.height = height / theRadio + "px";
          //注意,圖片要取實際大小裁剪,但是顯示大小選擇和canvas同樣的大小,這樣顯示的不失真、小,但實際的大。不失真
          cantent.drawImage(img, 0, 0, width, height, 0, 0, yaWidth * 2, height / theRadio * 2);
          console.log("壓縮之後的圖片大小,寬:  " + yaWidth * 2 + "高:  " + height / theRadio * 2);
        }

        // console.log("************************");
        // console.log(theRadio);
        // console.log(width);
        // console.log(height);
        // console.log(yaWidth * 2);
        // console.log((height / theRadio) * 2);
        w.yaSuoBase64 = canvas.toDataURL();
        setTimeout(function () {
          $(".g-wrap2 .f-theImg").attr("src", w.yaSuoBase64);
          // console.log("壓縮之後大小");
          // console.log(w.yaSuoBase64.length / 1024 + "KB");
//                    alert("壓縮之後大小:"+w.yaSuoBase64.length/1024+"KB");
        }, 200)
      };
    },


    initEvent: function () {
      var w = this;
      $('.getCode').on('tap', function () {
        $('.m-phone').blur();
        $('.m-code').blur();
        var phoneNum = $('.m-phone').val();
        if (phoneNum == '') {
          $('.j-pop').fadeIn();
          $('.j-pop p').text('手機號不能為空');
          setTimeout(function () {
            $('.j-pop').fadeOut();
          }, 2000);
          return false;
        } else if (!/^1[3|4|5|7|8][0-9]{9}$/.test(phoneNum)) {
          $('.j-pop').fadeIn();
          $('.j-pop p').text('手機號格式不正確');
          setTimeout(function () {
            $('.j-pop').fadeOut();
          }, 2000);
          return false;
        }
        if (w.flag == true) {
          w.codeAjax();
          var time = 60;
          $('.getCode').attr("disabled", "disabled");
          w.flag = false;
          var t = setInterval(function () {
            time--;
            $('.getCode').text(time + "s後再傳送");
            $('.getCode').css("background", '#EFEFEF');
            $('.getCode').css("color", 'gray');
            if (time == 0) {
              $('.getCode').removeAttr('disabled');
              clearInterval(t);
              $('.getCode').text("獲取驗證碼");
              w.flag = true;
              $('.getCode').css("color", '#CB402F');
            }
          }, 1000);
        }
      });
      $('.m-getBook').on('tap', function () {
        var codeTxt = $('.m-code').val();
        var phoneNum = $('.m-phone').val();
        if (phoneNum == '') {
          $('.j-pop').fadeIn();
          $('.j-pop p').text('手機號不能為空');
          setTimeout(function () {
            $('.j-pop').fadeOut();
          }, 2000);
        } else if (codeTxt == '') {
          $('.j-pop').fadeIn();
          $('.j-pop p').text('驗證碼不能為空');
          setTimeout(function () {
            $('.j-pop').fadeOut();
          }, 2000);
          return false;
        } else if (/^1[3|4|5|7|8][0-9]{9}$/.test(phoneNum) && codeTxt != "") {
          $.ajax({
            type: 'post',
            url: w.jsonUrl,
            data: "method=xxxx" + "&content=" + JSON.stringify({
              customerName: phoneNum,
              sendBookActivityCode: w.bookCode,
              checkCode: codeTxt
            }),
            callbackParameter: 'callback',
            async: true,
            jsonp: "jsonpcallback",
            success: function (o) {
              if (o.status == 1) {
                $('.j-pop').fadeIn();
                $('.j-pop p').text('領取成功!');
                setTimeout(function () {
                  $('.j-pop').fadeOut();
                  window.location.href = "http://xxxxx";
                }, 800);
              } else if (o.status == 2) {
                $('.j-pop').fadeIn();
                $('.j-pop p').text("您已領取過該書籍");
                setTimeout(function () {
                  $('.j-pop').fadeOut();
                  window.location.href = "http://xxxx";
                }, 2000);
              }
            }
          });

        } else if (!/^1[3|4|5|7|8][0-9]{9}$/.test(phoneNum)) {
          $('.j-pop').fadeIn();
          $('.j-pop p').text('手機號錯誤');
          setTimeout(function () {
            $('.j-pop').fadeOut();
          }, 2000);
        }
      });
    },

    codeAjax: function () {
      var w = this;
      var phoneNum = $('.m-phone').val();
      if (/^1[3|4|5|6|7|8][0-9]{9}$/.test(phoneNum)) {
        $.ajax({
          type: 'post',
          url: w.jsonUrl,
          timeout: 2000, //10秒超時
          data: "method=xxxx" + "&content=" + JSON.stringify({
            mobileNum: phoneNum,
            type: 'LOGIN_CHECK'
          }),
          callbackParameter: 'callback',
          async: false,
          jsonp: "jsonpcallback",
          success: function (o) {
            if (o.status == 1) {
              $('.j-pop').fadeIn();
              $('.j-pop p').text('簡訊傳送成功');
              setTimeout(function () {
                $('.j-pop').fadeOut();
              }, 2000);
            } else if (o.status == 0) {
              if (o.code == "10002006") {
                $('.j-pop').fadeIn();
                $('.j-pop p').text('請求過於頻繁,一分鐘只能請求一次');
                setTimeout(function () {
                  $('.j-pop').fadeOut();
                }, 2000);
              } else if (o.code == "10002003") {
                $('.j-pop').fadeIn();
                $('.j-pop p').text('簡訊傳送失敗');
                setTimeout(function () {
                  $('.j-pop').fadeOut();
                }, 2000);
              }
            }
          }
        });
      } else {
        $('.j-pop').fadeIn();
        $('.j-pop p').text('手機號格式不正確');
        setTimeout(function () {
          $('.j-pop').fadeOut();
        }, 2000);
      }
    },

    //上傳做好的圖片,並獲取後臺生成的圖片路徑
    sendImgSouce: function () {
      var w = this;
      console.log("開始上傳");
      $.ajax({
        type: 'post',
        url: 'http://xxxxxxx/upload/activityBase64',
        data: {
          pictureStream: w.hcBase64,
          activityId: "1508342400"
        },
        timeout: 10000, //10秒超時
        callbackParameter: 'callback',
        async: false,
        jsonp: "jsonpcallback",
        success: function (o) {
          console.log("回撥");
          console.log(o);
          var b = o.data.split("?");
          var c = b[0].split("event/");
          w.imgNewName = c[1];
          console.log(w.imgNewName);
        }
      });
      console.log("上傳成功");
    }

  }
  win.page = obj;
})(window)
//圖片操作獲取具體資料的函式
function imageMaker(eleID,otherID){
    var oldX=0,
        oldY=0,
        ratio=1;
        
    $(""+eleID+"").on("touchstart",imgTouchStart);
    $(""+eleID+"").on("touchmove",imgTouchMove);
    $(""+eleID+"").on("touchend",imgTouchEnd);
    
    //手指按下時,捕捉事件,取座標值,設定引數
    function imgTouchStart(e){
        //阻止事件冒泡的
        e.stopImmediatePropagation();
        e.preventDefault(); 
        $(""+eleID+"").attr("draggable",true);

        oldX = e.originalEvent.touches[0].clientX;
        oldY = e.originalEvent.touches[0].clientY;
    }
        
    function imgTouchMove(e){
        e.stopImmediatePropagation();
        //阻止事件冒泡,避免,移動照片時,整個頁面也會隨滾動條移動
        e.preventDefault();
        if($(""+eleID+"").attr("draggable")) {
            var x = e.originalEvent.touches[0].clientX - oldX;
            var y = e.originalEvent.touches[0].clientY - oldY;
            var oldTop = $(""+eleID+"")[0].offsetTop;
            var oldLeft = $(""+eleID+"")[0].offsetLeft;
            var NewTop = y + parseInt(oldTop);
            var newLeft = x + parseInt(oldLeft);
            $(""+eleID+"").css({"top":NewTop+"px","left":newLeft+"px"});
            oldX = e.originalEvent.touches[0].clientX;
            oldY = e.originalEvent.touches[0].clientY;
        }
    }
    
    //手指拿開時,設定引數
    function imgTouchEnd(e) {
        e.stopImmediatePropagation();
        e.preventDefault();
        $(""+eleID+"").attr("draggable",false);
    }
    
    
    $(".shape1").on("touchstart",function(e){
        setImgSmall();
    });
    
    $(".shape3").on("touchstart",function(e){
        setImgBig();
    });
    
    $(".shape2").on("touchstart",function(e){
        setImgAngle();
    });
    
    //放大、縮小的
    function setImgBig() {
        var width = parseInt($(""+otherID+"").width()) * 1.03;
        var height = parseInt($(""+otherID+"").height()) * 1.03;

        $(""+otherID+"").css({
            'width': width+"px",
            'height': height+"px",
        });
        console.log("列印我"+width);
    }
    
    function setImgSmall() {
        var width = parseInt($(""+otherID+"").width()) * 0.97;
        var height = parseInt($(""+otherID+"").height()) * 0.97;

        $(""+otherID+"").css({
            'width': width+"px",
            'height': height+"px",
        });
    }
    
    function setImgAngle(){
        //這裡只取圖片的角度,值旋轉圖片。外框不做操作了
        var theAngle = getEletAngle();
        var angleNow = theAngle+10;
        $(".f-sucai").css({'transform': "rotate("+angleNow+"deg)"});
    }
}


//獲取元素旋轉角度
    function getEletAngle(eletClass){
        var el = document.getElementsByClassName("f-sucai")[0];
        console.log(el);
        var st = window.getComputedStyle(el, null);
        var tr = st.getPropertyValue("-webkit-transform") ||
            st.getPropertyValue("-moz-transform") ||
            st.getPropertyValue("-ms-transform") ||
            st.getPropertyValue("-o-transform") ||
            st.getPropertyValue("transform") ||
            "FAIL";
        //console.log('Matrix: ' + tr);
        var values = tr.split('(')[1].split(')')[0].split(',');
        var a = values[0];
        var b = values[1];
        var angle = Math.round(Math.atan2(b, a) * (180 / Math.PI));
        //console.log('Rotate: ' + angle + 'deg');
        return angle;
    }

 


3.問題分析、解決方案

 現在說一下做這個東西遇到的問題,也說明一下為什麼會寫這麼多程式碼

  a、canvas 相容性,IOS8.0 以上,低了貌似都不支援。安卓不太清楚,低階版本肯定是不行了。市面上的手機基本都是OK的。

  b、reader.readAsDataURL(oFile)  手機上傳圖片,iOS不支援 FileReader 的onload回撥,無奈,加了 setTimeout 做延時處理。

  c、手機上傳的圖片有橫屏的,有豎屏的。超級麻煩,導致我們不知道哪個是寬,而且預設展示總不能倒著展示吧。這裡引入 exif.js 這個js包可以判斷照片的拍攝角度。牛逼啊。拿到拍攝角度之後,我們就可以放到畫布裡面,再把它旋轉,壓縮一下。就OK了。

  d、手機的圖片太大。現在手機照片動不動就好幾MB,畫布處理圖片是轉化成base64計算的,想想吧,一張幾兆的圖,轉換成base64,還得大20%左右,用它編輯,頁面一般就卡死了。所以上傳成功後,先把圖片用畫布壓縮到100KB以內,這個大小清晰度和處理速度是比較合適的。

  e、壓縮圖片的時候還有個問題。canvas 的實際大小一定是顯示大小的2倍,這樣圖片才不會是真,否則,你會發現你的圖片超級模糊,和馬賽克差不多了。

  f、圖片移動,這個比較簡單了,就是注意移動端、PC端獲取到事件物件後,座標屬性名不同,記得別搞錯了。

  g、最後圖片的生成。canvas 最終生成的是base64,他可以直接儲存成圖片,不過儲存之後我們就取不到了,所以建議發到服務端,服務端生成,返回公網連結。


4.demo下載

  以上就是整個專案了,有什麼問題,可以評論區留言。

  原始碼,我會上傳到 github ,如果幫到你了,記得給個 star 奧。 下載

 

相關文章