真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

基于Vue如何實(shí)現(xiàn)移動(dòng)端圖片裁剪組件功能

小編給大家分享一下基于Vue如何實(shí)現(xiàn)移動(dòng)端圖片裁剪組件功能,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!

成都創(chuàng)新互聯(lián)公司網(wǎng)站建設(shè)服務(wù)商,為中小企業(yè)提供成都網(wǎng)站建設(shè)、做網(wǎng)站服務(wù),網(wǎng)站設(shè)計(jì),網(wǎng)站托管等一站式綜合服務(wù)型公司,專業(yè)打造企業(yè)形象網(wǎng)站,讓您在眾多競爭對(duì)手中脫穎而出成都創(chuàng)新互聯(lián)公司

最近項(xiàng)目上要做一個(gè)車牌識(shí)別的功能。本來以為很簡單,只需要將圖片扔給后臺(tái)就可以了,但是經(jīng)測(cè)試后識(shí)別率只有20-40%。因此產(chǎn)品建議拍攝圖片后,可以對(duì)圖片進(jìn)行拖拽和縮放,然后裁剪車牌部分上傳給后臺(tái)來提高識(shí)別率。剛開始的話還是百度了一下看看有沒有現(xiàn)成的組件,但是找來找去都沒有找到一個(gè)合適的,還好這個(gè)功能不是很著急,因此自己周末就在家里研究一下。

Demo地址:https://vivialex.github.io/demo/imageClipper/index.html

下載地址:https://github.com/vivialex/vue-imageClipper

因?yàn)橐苿?dòng)端是用vue,所以就寫成了一個(gè)vue組件,下面就說說自己的一些實(shí)現(xiàn)思路(本人技術(shù)有限,各位大神請(qǐng)?bào)w諒。另外展示的代碼不一定是某個(gè)功能的完整代碼),先看看效果: 

基于Vue如何實(shí)現(xiàn)移動(dòng)端圖片裁剪組件功能 基于Vue如何實(shí)現(xiàn)移動(dòng)端圖片裁剪組件功能  

一、組件的初始化參數(shù)

1、圖片img(url或者base64 data-url)

2、截圖的寬clipperImgWidth

3、截圖的高clipperImgHeight

props: {
  img: String, //url或dataUrl
  clipperImgWidth: {
    type: Number,
    default: 500
  },
  clipperImgHeight: {
    type: Number,
    default: 200
  }
}

 二、布局

在Z軸方向看主要是由4層組成。第1層是一個(gè)占滿整個(gè)容器的canvas(稱cCanvas);第2層是一個(gè)有透明度的遮罩層;第3層是裁剪的區(qū)域(示例圖中的白色方框),里面包含一個(gè)與裁剪區(qū)域大小相等的canvas(稱pCanvas);第4層是一個(gè)透明層gesture-mask,用作綁定touchstart,touchmove,touchend事件。其中兩個(gè)canvas都會(huì)加載同一張圖片,只是起始坐標(biāo)不一樣。為什么需要兩個(gè)canvas?因?yàn)橄胱龀霎?dāng)手指離開屏幕時(shí),裁剪區(qū)域外的部分表面會(huì)有一個(gè)遮罩層的效果,這樣能突出裁剪區(qū)域的內(nèi)容。


  
  
  
    
      
    
  
          取消     確認(rèn)   
           

 三、初始化canvas

canvas繪制的圖片在hdpi顯示屏上會(huì)出現(xiàn)模糊,具體原因這里不作分析,可以參考下這里。我這里的做法是讓canvas的width與height為其css width/height的devicePixelRatio倍,以及調(diào)用canvas api時(shí)所傳入的參數(shù)都要乘以window.devicePixelRatio。最后還要記錄一下兩個(gè)canvas坐標(biāo)原點(diǎn)的x, y差值(originXDiff與originYDiff)。如下

_ratio(size) {
  return parseInt(window.devicePixelRatio * size);
},
_initCanvas() {
  let $canvas = this.$refs.canvas,
    $pCanvas = this.$refs.pCanvas,
    clipperClientRect = this.$refs.clipper.getBoundingClientRect(),
    clipperWidth = parseInt(this.clipperImgWidth / window.devicePixelRatio),
    clipperHeight = parseInt(this.clipperImgHeight / window.devicePixelRatio);

  this.ctx = $canvas.getContext('2d');
  this.pCtx = $pCanvas.getContext('2d');

  //判斷clipperWidth與clipperHeight有沒有超過容器值
  if (clipperWidth < 0 || clipperWidth > clipperClientRect.width) {
    clipperWidth = 250
  }

  if (clipperHeight < 0 || clipperHeight > clipperClientRect.height) {
    clipperHeight = 100
  }

  //因?yàn)閏anvas在手機(jī)上會(huì)被放大,因此里面的內(nèi)容會(huì)模糊,這里根據(jù)手機(jī)的devicePixelRatio來放大canvas,然后再通過設(shè)置css來收縮,因此關(guān)于canvas的所有值或坐標(biāo)都要乘以devicePixelRatio
  $canvas.style.width = clipperClientRect.width + 'px';
  $canvas.style.height = clipperClientRect.height + 'px';
  $canvas.width = this._ratio(clipperClientRect.width);
  $canvas.height = this._ratio(clipperClientRect.height);

  $pCanvas.style.width = clipperWidth + 'px';
  $pCanvas.style.height = clipperHeight + 'px';
  $pCanvas.width = this._ratio(clipperWidth);
  $pCanvas.height = this._ratio(clipperHeight);

  //計(jì)算兩個(gè)canvas原點(diǎn)的x y差值
  let cClientRect = $canvas.getBoundingClientRect(),
    pClientRect = $pCanvas.getBoundingClientRect();

  this.originXDiff = pClientRect.left - cClientRect.left;
  this.originYDiff = pClientRect.top - cClientRect.top;
  this.cWidth = cClientRect.width;
  this.cHeight = cClientRect.height;
}

 四、加載圖片

加載圖片比較簡單,首先是創(chuàng)建一個(gè)Image對(duì)象并監(jiān)聽器onload事件(因?yàn)榧虞d的圖片有可能是跨域的,因此要設(shè)置其crossOrigin屬性為Anonymous,然后服務(wù)器上要設(shè)置Access-Control-Allow-Origin響應(yīng)頭)。加載的圖片如果寬高大于容器的寬高,要對(duì)其進(jìn)行縮小處理。最后垂直水平居中顯示()(這里注意的是要保存圖片繪制前的寬高值,因?yàn)槿蘸罂s放圖片是以該值為基礎(chǔ)再乘以縮放倍率,這里取imgStartWidth,imgStartHeight)如下

_loadImg() {
  if (this.imgLoading || this.loadImgQueue.length === 0) {
    return;
  }
  let img = this.loadImgQueue.shift();
  if (!img) {
    return;
  }
  let $img = new Image(),
    onLoad = e => {
      $img.removeEventListener('load', onLoad, false);
      this.$img = $img;
      this.imgLoaded = true;
      this.imgLoading = false;
      this._initImg($img.width, $img.height);
      this.$emit('loadSuccess', e);
      this.$emit('loadComplete', e);
      this._loadImg();
    },
    onError = e => {
      $img.removeEventListener('error', onError, false);
      this.$img = $img = null;
      this.imgLoading = false;
      this.$emit('loadError', e);
      this.$emit('loadComplete', e);
      this._loadImg();
    };
  this.$emit('beforeLoad');
  this.imgLoading = true;
  this.imgLoaded = false;
  $img.src = this.img;
  $img.crossOrigin = 'Anonymous'; //因?yàn)閏anvas toDataUrl不能操作未經(jīng)允許的跨域圖片,這需要服務(wù)器設(shè)置Access-Control-Allow-Origin頭
  $img.addEventListener('load', onLoad, false);
  $img.addEventListener('error', onError, false);
}
_initImg(w, h) {
  let eW = null,
    eH = null,
    maxW = this.cWidth,
    maxH = this.cHeight - this.actionBarHeight;
  //如果圖片的寬高都少于容器的寬高,則不做處理
  if (w <= maxW && h <= maxH) {
    eW = w;
    eH = h;
  } else if (w > maxW && h <= maxH) {
    eW = maxW;
    eH = parseInt(h / w * maxW);
  } else if (w <= maxW && h > maxH) {
    eW = parseInt(w / h * maxH);
    eH = maxH;
  } else {
    //判斷是橫圖還是豎圖
    if (h > w) {
      eW = parseInt(w / h * maxH);
      eH = maxH;
    } else {
      eW = maxW;
      eH = parseInt(h / w * maxW);
    }
  }
  if (eW <= maxW && eH <= maxH) {
    //記錄其初始化的寬高,日后的縮放功能以此值為基礎(chǔ)
    this.imgStartWidth = eW;
    this.imgStartHeight = eH;
    this._drawImage((maxW - eW) / 2, (maxH - eH) / 2, eW, eH);
  } else {
    this._initImg(eW, eH);
  }
}

五、繪制圖片

下面的_drawImage有四個(gè)參數(shù),分別是圖片對(duì)應(yīng)cCanvas的x,y坐標(biāo)以及圖片目前的寬高w,h。函數(shù)首先會(huì)清空兩個(gè)canvas的內(nèi)容,方法是重新設(shè)置canvas的寬高。然后更新組件實(shí)例中對(duì)應(yīng)的值,最后再調(diào)用兩個(gè)canvas的drawImage去繪制圖片。對(duì)于pCanvas來說,其繪制的圖片坐標(biāo)值為x,y減去對(duì)應(yīng)的originXDiff與originYDiff(其實(shí)相當(dāng)于切換坐標(biāo)系顯示而已,因此只需要減去兩個(gè)坐標(biāo)系原點(diǎn)的x,y差值即可)。看看代碼

_drawImage(x, y, w, h) {
  this._clearCanvas();
  this.imgX = parseInt(x);
  this.imgY = parseInt(y);
  this.imgCurrentWidth = parseInt(w);
  this.imgCurrentHeight = parseInt(h);
  //更新canvas
  this.ctx.drawImage(this.$img, this._ratio(x), this._ratio(y), this._ratio(w), this._ratio(h));
  //更新pCanvas,只需要減去兩個(gè)canvas坐標(biāo)原點(diǎn)對(duì)應(yīng)的差值即可
  this.pCtx.drawImage(this.$img, this._ratio(x - this.originXDiff), this._ratio(y - this.originYDiff), this._ratio(w), this._ratio(h));
},
_clearCanvas() {
  let $canvas = this.$refs.canvas,
    $pCanvas = this.$refs.pCanvas;
  $canvas.width = $canvas.width;
  $canvas.height = $canvas.height;
  $pCanvas.width = $pCanvas.width;
  $pCanvas.height = $pCanvas.height;
}

六、移動(dòng)圖片

移動(dòng)圖片實(shí)現(xiàn)非常簡單,首先給gesture-mask綁定touchstart,touchmove,touchend事件,下面分別介紹這三個(gè)事件的內(nèi)容

首先定義四個(gè)變量scx, scy(手指的起始坐標(biāo)),iX,iY(圖片目前的坐標(biāo),相對(duì)于cCanvas)。

1、touchstart

方法很簡單,就是獲取touches[0]的pageX,pageY來更新scx與scy以及更新iX與iY

2、touchmove

獲取touches[0]的pageX,聲明變量f1x存放,移動(dòng)后的x坐標(biāo)等于iX + f1x - scx,y坐標(biāo)同理,最后調(diào)用_drawImage來更新圖片。

看看代碼吧

_initEvent() {
  let $gesture = this.$refs.gesture,
    scx = 0,
    scy = 0;
  let iX = this.imgX,
    iY = this.imgY;
  $gesture.addEventListener('touchstart', e => {
    if (!this.imgLoaded) {
      return;
    }
    let finger = e.touches[0];
      scx = finger.pageX;
      scy = finger.pageY;
      iX = this.imgX;
      iY = this.imgY;  
  }, false);
  $gesture.addEventListener('touchmove', e => {
    e.preventDefault();
    if (!this.imgLoaded) {
      return;
    }
    let f1x = e.touches[0].pageX,
      f1y = e.touches[0].pageY;
      this._drawImage(iX + f1x - scx, iY + f1y - scy, this.imgCurrentWidth, this.imgCurrentHeight);
  }, false);
}

七、縮放圖片(這里不作特別說明的坐標(biāo)都是相對(duì)于cCanvas坐標(biāo)系)

繪制縮放后的圖片無非需要4個(gè)參數(shù),縮放后圖片左上角的坐標(biāo)以及寬高。求寬高相對(duì)好辦,寬高等于imgStartWidth * 縮放比率與imgstartHeight * 縮放倍率(imgStartWidth ,imgstartHeight 上文第四節(jié)有提到)。接下來就是求縮放倍率的問題了,首先在touchstart事件上求取兩手指間的距離d1;然后在touchmove事件上繼續(xù)求取兩手指間的距離d2,當(dāng)前縮放倍率= 初始縮放倍率 + (d2-d1) / 步長(例如每60px算0.1),touchend事件上讓初始縮放倍率=當(dāng)前縮放倍率。

至于如何求取縮放后圖片左上角的坐標(biāo)值,在草稿紙上畫來畫去,畫了很久......終于有點(diǎn)眉目。首先要找到一個(gè)縮放中心(這里做法是取雙指的中點(diǎn)坐標(biāo),但是這個(gè)坐標(biāo)必須要位于圖片上,如果不在圖片上,則取圖片上離該中點(diǎn)坐標(biāo)最近的點(diǎn)),然后存在下面這個(gè)等式

(縮放中心x坐標(biāo) - 縮放后圖片左上角x坐標(biāo))/ 縮放后圖片的寬度 = (縮放中心x坐標(biāo) - 縮放前圖片左上角x坐標(biāo))/ 縮放前圖片的寬度;(y坐標(biāo)同理)

接下來看看下面這個(gè)例子(在visio找了很久都沒有畫坐標(biāo)系的功能,所以只能手工畫了)

基于Vue如何實(shí)現(xiàn)移動(dòng)端圖片裁剪組件功能

綠色框是一張10*5的圖片,藍(lán)色框是寬高放大兩倍后的圖片20*10,根據(jù)上面的公式推算的x2 = sx - w2(sx - x1) / w1,y2 = sy - h3(sy - y1) / h2。

堅(jiān)持...繼續(xù)看看代碼吧

_initEvent() {
  let $gesture = this.$refs.gesture,
    cClientRect = this.$refs.canvas.getBoundingClientRect(),
    scx = 0, //對(duì)于單手操作是移動(dòng)的起點(diǎn)坐標(biāo),對(duì)于縮放是圖片距離兩手指的中點(diǎn)最近的圖標(biāo)。
    scy = 0,
    fingers = {}; //記錄當(dāng)前有多少只手指在觸控屏幕
  //one finger
  let iX = this.imgX,
    iY = this.imgY;
  //two finger
  let figuredistance = 0,
    pinchScale = this.imgScale;
  $gesture.addEventListener('touchstart', e => {
    if (!this.imgLoaded) {
      return;
    }
    if (e.touches.length === 1) {
      let finger = e.touches[0];
      scx = finger.pageX;
      scy = finger.pageY;
      iX = this.imgX;
      iY = this.imgY;
      fingers[finger.identifier] = finger;
    } else if (e.touches.length === 2) {
      let finger1 = e.touches[0],
        finger2 = e.touches[1],
        f1x = finger1.pageX - cClientRect.left,
        f1y = finger1.pageY - cClientRect.top,
        f2x = finger2.pageX - cClientRect.left,
        f2y = finger2.pageY - cClientRect.top;
      scx = parseInt((f1x + f2x) / 2);
      scy = parseInt((f1y + f2y) / 2);
      figureDistance = this._pointDistance(f1x, f1y, f2x, f2y);
      fingers[finger1.identifier] = finger1;
      fingers[finger2.identifier] = finger2;
      //判斷變換中點(diǎn)是否在圖片中,如果不是則去離圖片最近的點(diǎn)
      if (scx < this.imgX) {
        scx = this.imgX;
      }
      if (scx > this.imgX + this.imgCurrentWidth) {
        scx = this.imgX + this.imgCurrentHeight;
      }
      if (scy < this.imgY) {
        scy = this.imgY;
      }
      if (scy > this.imgY + this.imgCurrentHeight) {
        scy = this.imgY + this.imgCurrentHeight;
      }
    }
  }, false);
  $gesture.addEventListener('touchmove', e => {
    e.preventDefault();
    if (!this.imgLoaded) {
      return;
    }
    this.maskShowTimer && clearTimeout(this.maskShowTimer);
    this.maskShow = false;
    if (e.touches.length === 1) {
      let f1x = e.touches[0].pageX,
        f1y = e.touches[0].pageY;
      this._drawImage(iX + f1x - scx, iY + f1y - scy, this.imgCurrentWidth, this.imgCurrentHeight);
    } else if (e.touches.length === 2) {
      let finger1 = e.touches[0],
        finger2 = e.touches[1],
        f1x = finger1.pageX - cClientRect.left,
        f1y = finger1.pageY - cClientRect.top,
        f2x = finger2.pageX - cClientRect.left,
        f2y = finger2.pageY - cClientRect.top,
        newFigureDistance = this._pointDistance(f1x, f1y, f2x, f2y),
        scale = this.imgScale + parseFloat(((newFigureDistance - figureDistance) / this.imgScaleStep).toFixed(1));
      fingers[finger1.identifier] = finger1;
      fingers[finger2.identifier] = finger2;
      if (scale !== pinchScale) {
        //目前縮放的最小比例是1,最大是5
        if (scale < this.imgMinScale) {
          scale = this.imgMinScale;
        } else if (scale > this.imgMaxScale) {
          scale = this.imgMaxScale;
        }
        pinchScale = scale;
        this._scale(scx, scy, scale);
      }
    }
  }, false);
  $gesture.addEventListener('touchend', e => {
    if (!this.imgLoaded) {
      return;
    }
    this.imgScale = pinchScale;
    //從finger刪除已經(jīng)離開的手指
    let touches = Array.prototype.slice.call(e.changedTouches, 0);
    touches.forEach(item => {
      delete fingers[item.identifier];
    });
    //迭代fingers,如果存在finger則更新scx,scy,iX,iY,因?yàn)榭赡芸s放后立即單指拖動(dòng)
    let i,
      fingerArr = [];
    for(i in fingers) {
      if (fingers.hasOwnProperty(i)) {
        fingerArr.push(fingers[i]);
      }
    }
    if (fingerArr.length > 0) {
      scx = fingerArr[0].pageX;
      scy = fingerArr[0].pageY;
      iX = this.imgX;
      iY = this.imgY;
    } else {
      this.maskShowTimer = setTimeout(() => {
        this.maskShow = true;
      }, 300);
    }
    //做邊界值檢測(cè)
    let x = this.imgX,
      y = this.imgY,
      pClientRect = this.$refs.pCanvas.getBoundingClientRect();
    if (x > pClientRect.left + pClientRect.width) {
      x = pClientRect.left
    } else if (x + this.imgCurrentWidth < pClientRect.left) {
      x = pClientRect.left + pClientRect.width - this.imgCurrentWidth;
    }
    if (y > pClientRect.top + pClientRect.height) {
      y = pClientRect.top;
    } else if (y + this.imgCurrentHeight < pClientRect.top) {
      y = pClientRect.top + pClientRect.height - this.imgCurrentHeight;
    }
    if (this.imgX !== x || this.imgY !== y) {
      this._drawImage(x, y, this.imgCurrentWidth, this.imgCurrentHeight);
    }
  });
},
_scale(x, y, scale) {
  let newPicWidth = parseInt(this.imgStartWidth * scale),
    newPicHeight = parseInt(this.imgStartHeight * scale),
    newIX = parseInt(x - newPicWidth * (x - this.imgX) / this.imgCurrentWidth),
    newIY = parseInt(y - newPicHeight * (y - this.imgY) / this.imgCurrentHeight);
  this._drawImage(newIX, newIY, newPicWidth, newPicHeight);
},
_pointDistance(x1, y1, x2, y2) {
  return parseInt(Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)));
}

說明一下fingers是干嘛的,是用來記錄當(dāng)前有多少只手指在屏幕上觸摸??赡軙?huì)出現(xiàn)這種情況,雙指縮放后,其中一只手指移出顯示屏,而另外一個(gè)手指在顯示屏上移動(dòng)。針對(duì)這種情況,要在touchend事件上根據(jù)e.changedTouches來移除fingers里已經(jīng)離開顯示屏的finger,如果此時(shí)fingers里只剩下一個(gè)finger,則更新scx,scy,iX,iY為移動(dòng)圖片做初始化準(zhǔn)備。

八、裁剪圖片

這里很簡單,就調(diào)用pCanvas的toDataURL方法就可以了

_clipper() {
  let imgData = null;
  try {
    imgData = this.$refs.pCanvas.toDataURL();
  } catch (e) {
    console.error('請(qǐng)?jiān)趓esponse header加上Access-Control-Allow-Origin,否則canvas無法裁剪未經(jīng)許可的跨域圖片');
  }
  this.$emit('sure', imgData);
}

看完了這篇文章,相信你對(duì)“基于Vue如何實(shí)現(xiàn)移動(dòng)端圖片裁剪組件功能”有了一定的了解,如果想了解更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!


名稱欄目:基于Vue如何實(shí)現(xiàn)移動(dòng)端圖片裁剪組件功能
網(wǎng)址分享:http://weahome.cn/article/geheco.html

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部