前面曾經寫過一篇《H5圖片裁剪升級版》,但里面需要借助第三方手勢庫,這次就不需要使用手勢庫,全部封裝在代碼中。
下圖是裁剪的展示,下面就做了拖放和裁剪,沒有做縮放,在插件中需要用到大量的計算。veImage的源碼可以在此處瀏覽。
一、原理
1)拖動、縮放、裁剪都是借助Canvas實現的。Canvas的基礎概念可以參考《讓自己也能使用Canvas》。
2)拖動是通過設置Canvas畫布左上角的起點實現的。使用CanvasRenderingContext2D.translate方法。
3)縮放是通過設置目標畫布上繪制圖像的寬度和高度實現的。使用CanvasRenderingContext2D.drawImage方法。
4)裁剪是通過計算比例后(效果圖中的畫布尺寸與實際網頁中的畫布尺寸不符),在原畫布上面截取計算后的尺寸,畫到一張新畫布中,新畫布的寬高就是需要截取的寬高。
5)拖動、縮放是需要計算兩個坐標的差值,而這兩個坐標是通過e.touches獲取到,這個參數可以參考《觸屏touch事件記錄》。
6)將畫布的起始點,設置在畫布的中心,也就是寬高的一半。這樣設置后,縮放的效果看上去就是向四周縮放。否則效果是固定在圖片左上角,然后縮放,如下圖所示。
二、參數
參數目前就3個。
1)Canvas:畫布,可以通過DOM獲取到。
2)Image:圖片,Image對象,這里先做的簡單點,不是傳地址。
3)relativeWidth:相對寬度,裁剪的時候可計算比例。
HTML代碼如下:
<canvas id="captureCanvas2" class="car-img car-canvas"></canvas>
JavaScript代碼如下:
var imgEdit2; var img = new Image(); img.src = 'img/car-demo2.jpg'; img.onload = function() { imgEdit2 = new veImage({canvas:document.getElementById('captureCanvas2'), image:this}); };
三、事件綁定
拖動、縮放涉及的事件是touchstart,touchmove,touchend。
給Canvas畫布添加上述事件,模擬出手勢效果。
事件綁定用到了“handleEvent”方式綁定。前面的《Slider圖片滑動插件》也是用相同的方式綁定。
/** * 綁定事件 */ veImagePrototype._bind = function() { this.canvas.addEventListener(events.start, this); this.canvas.addEventListener(events.move, this); this.canvas.addEventListener(events.end, this); }; /** * 高級的綁定方法 */ veImagePrototype.handleEvent = function(e) { switch (e.type) { case events.start: this.startEvt(e); break; case events.move: this.moveEvt(e); break; case events.end: this.endEvt(e); break; } };
四、拖動與縮放
1)touchstart
1. 在touchstart事件中記錄開始的坐標。
2. 通過手指的數量,設置當前的模式,簡單處理,1根手指是拖動,2根手指是縮放。
this.start = _finger(e.touches);//記錄開始的坐標 this._mode(e.touches);//模式初始化
2)touchmove
1. 記錄移動中的坐標。
2. 當模式是1的時候,才做拖動。
3. 原先是通過手指數量來判斷,拖動還是縮放,不過后面發現縮放后,移除手指的時候,會出現一個手指還停留在頁面上,導致位移一段距離。如下圖所示:
e.preventDefault(); //禁止滾動 var fingers = _finger(e.touches); //記錄移動中的坐標 //不能僅僅通過手指數量來判斷 因為當縮放后,移除手指的時候,會出現一個手指還停留在頁面上,導致位移 if (this.mode == 1) { //位移 this._translate(fingers); } else if (this.mode == 2) { //縮放 this._zoom(fingers); } //將start的坐標復為移動中的坐標 時時計算偏移值,不然會變得非常大,圖片在移動中會飛出畫面 this.start = fingers;
3)移動計算
1. 最普通的計算方式,移動中的坐標點減去剛開始觸屏的坐標點。
2. 向左或上移動,就是負數。
//計算手指的位移 this._draw( fingers[0].x - this.start[0].x, fingers[0].y - this.start[0].y );
4)縮放計算
1. 先計算上一次手指兩個X軸和Y軸之間的距離。
2. 再計算當前的手指兩個X軸和Y軸之間的距離。
3. 都取絕對值,縮放率分母是上一次手指,分子是當前手指兩個X軸和Y軸之間的距離。
//上一次手指兩個X軸和Y軸之間的距離 var lastOffset = { x: Math.abs(this.start[0].x - this.start[1].x), y: Math.abs(this.start[0].y - this.start[1].y) }; if (lastOffset.x == 0 || lastOffset.y == 0) { //防止分母是0 return; } //縮放不需要坐標軸偏移 但計算縮放值 需要偏移值 //縮放率分母是上一次手指,分子是當前手指兩個X軸和Y軸之間的距離 this._draw( 0, 0, Math.abs(fingers[0].x - fingers[1].x) / lastOffset.x, Math.abs(fingers[0].y - fingers[1].y) / lastOffset.y );
五、畫圖
1)清空畫並保存狀態
1. 如果不清空畫布,那么剛剛拖動的圖片還會在畫布上,形成了拖影。
2. 保存狀態(CanvasRenderingContext2D.save)是為了下面能夠還原到清空的畫布。
//清空畫布 擦除之前繪制的所有內容 不清空的話會顯示各個步驟的畫布 this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); this.context.save(); //保存狀態
2)拖動消息
拖動是由translate實現的,先計算原點和偏移值的和,再設置畫布的原點。
this.origin.x += dx || 0; //計算X軸坐標 this.origin.y += dy || 0; //計算Y軸坐標 this.context.translate(this.origin.x, this.origin.y); //位移
3)縮放效果
縮放是通過改變目標畫布的寬高實現的。
zoomWidth = zoomWidth || 1; zoomHeight = zoomHeight || 1; var zoom = zoomWidth > zoomHeight ? zoomWidth : zoomHeight; //統一按一個比例做縮放 this.dWidth *= zoom; //寬度縮放 this.dHeight *= zoom; //高度縮放
4)畫圖
由drawImage實現。語法如下:
1. sx, sy, sWidth, sHeight分別是圖片的坐標點和寬高。
2. dx, dy, dWidth, dHeight分別是畫布縮放后的中心點和縮放后的寬高。
3. 由於原點是在畫布的中心點,所以要畫到合適的位置,dx, dy就需要用負數坐標點。
this.context.drawImage( this.image, 0, 0, this.image.width, this.image.height, -this.dWidth / 2, -this.dHeight / 2, this.dWidth, this.dHeight ); //目標畫布的中心點
5)還原狀態
如果不還原(restore),就會像下圖那樣,上一張圖還在畫布中,也不能拖動或縮放了。
由於前面translate設置了原點,所以下一次畫的時候會從這個原點開始。
將原點設置的小一些,就會像下圖那樣,出現諜影,上圖其實也是諜影,只是已經超出畫布了,所以看不到。
this.origin = { x: 30, y: 30 };
六、裁剪
1)新建一張畫布
裁剪后的圖片畫到這張新畫布中。
var canvas = document.createElement("canvas"), context = canvas.getContext("2d");
2)計算比例
例如效果圖上畫布的寬是750,那么裁剪的尺寸是相對於750來說的,而實際畫布的寬度可能是360,那么這個時候就要做比例計算了。
rate = this.canvas.width / this.opts.relativeWidth;
3)計算尺寸
在獲取到比例后,計算真實畫布中的坐標和尺寸。
x *= rate; y *= rate; canvas.width = w * rate; canvas.height = h * rate; context.drawImage( this.canvas, x, y, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height );