本文首發於公衆號:符合預期的CoyPan
demo體驗地址及代碼在這裏:請用手機或瀏覽器模擬手機訪問javascript
上一篇文章介紹了canvas中的拖拽、縮放、旋轉中涉及到的數學知識。能夠點擊下面的連接查看。html
canvas中的拖拽、縮放、旋轉 (上) —— 數學知識準備。java
在canvas
中,若是一個元素帶有一個旋轉角度,能夠直接變化canvas
的座標軸來畫出此元素。舉個例子,canvas
ctx.save(); // 保存舊的座標系狀態 ctx.translate(x0 + w / 2, y0 + h / 2); // 座標原點移動到旋轉中心 ctx.rotate(angle); // 旋轉座標系 ctx.translate(-(x0 + w / 2), -(y0 + h / 2)); // 座標原點還原 ctx.rect(x0, y0, w, h); // 以新座標系爲參照,畫出矩形。 ctx.restore(); // 還原以前的座標系狀態
整個demo的實現思路以下:segmentfault
touchstart
)時,獲取用戶的觸摸對象,是Sprite
的本體?刪除按鈕?縮放按鈕?旋轉按鈕?而且根據各類狀況,對變化參數進行初始化。touchmove
)時,根據手指的座標,更新stage
中的全部元素的位置、大小,記錄變化參數。修改對應sprite
的屬性值。同時對canvas
進行重繪。touchend
)時,根據變化參數,更新sprite
的座標,同時對變化參數進行重置。須要注意的是,在touchmove
的過程當中,並不須要更新sprite
的座標,只須要記錄變化的參數便可。在touchend
過程當中,再進行座標的更新。座標的惟一用處,就是判斷用戶點擊時,落點是否在指定區域內。數組
首先,聲明兩個類:Stage
和Sprite
。Stage
表示整個canvas區域,Sprite
表示canvas中的元素。咱們能夠在Stage
中添加多個Sprite
,刪除Sprite
。這兩個類的屬性以下。瀏覽器
class Stage { constructor(props) { this.canvas = props.canvas; this.ctx = this.canvas.getContext('2d'); // 用一個數組來保存canvas中的元素。每個元素都是一個Sprite類的實例。 this.spriteList = []; // 獲取canvas在視窗中的位置,以便計算用戶touch時,相對與canvas內部的座標。 const pos = this.canvas.getBoundingClientRect(); this.canvasOffsetLeft = pos.left; this.canvasOffsetTop = pos.top; this.dragSpriteTarget = null; // 拖拽的對象 this.scaleSpriteTarget = null; // 縮放的對象 this.rotateSpriteTarget = null; // 旋轉的對象 this.dragStartX = undefined; this.dragStartY = undefined; this.scaleStartX = undefined; this.scaleStartY = undefined; this.rotateStartX = undefined; this.rotateStartY = undefined; } } class Sprite { constructor(props) { // 每個sprite都有一個惟一的id this.id = Date.now() + Math.floor(Math.random() * 10); this.pos = props.pos; // 在canvas中的位置 this.size = props.size; // sprite的當前大小 this.baseSize = props.size; // sprite的初始化大小 this.minSize = props.minSize; // sprite縮放時容許的最小size this.maxSize = props.maxSize; // sprite縮放時容許的最大size // 中心點座標 this.center = [ props.pos[0] + props.size[0] / 2, props.pos[1] + props.size[1] / 2 ]; this.delIcon = null; this.scaleIcon = null; this.rotateIcon = null; // 四個頂點的座標,順序爲:左上,右上,左下,右下 this.coordinate = this.setCoordinate(this.pos, this.size); this.rotateAngle = 0; // 累計旋轉的角度 this.rotateAngleDir = 0; // 每次旋轉角度 this.scalePercent = 1; // 縮放比例 } }
demo中,點擊canvas下方的紅色方塊時,會實例化一個sprite
,調用stage.append
時,會將實例化的sprite
直接push到Stage
的spriteList
屬性內。app
window.onload = function () { const stage = new Stage({ canvas: document.querySelector('canvas') }); document.querySelector('.red-box').addEventListener('click', function () { const randomX = Math.floor(Math.random() * 200); const randomY = Math.floor(Math.random() * 200); const sprite = new Sprite({ pos: [randomX, randomY], size: [120, 60], minSize: [40, 20], maxSize: [240, 120] }); stage.append(sprite); }); }
下面是Stage
的方法:dom
class Stage { constructor(props) {} // 將sprite添加到stage內 append(sprite) {} // 監聽事件 initEvent() {} // 處理touchstart handleTouchStart(e) {} // 處理touchmove handleTouchMove(e) {} // 處理touchend handleTouchEnd() {} // 初始化sprite的拖拽事件 initDragEvent(sprite, { touchX, touchY }) {} // 初始化sprite的縮放事件 initScaleEvent(sprite, { touchX, touchY }) {} // 初始化sprite的旋轉事件 initRotateEvent(sprite, { touchX, touchY }) {} // 經過觸摸的座標從新計算sprite的座標 reCalSpritePos(sprite, touchX, touchY) {} // 經過觸摸的【橫】座標從新計算sprite的大小 reCalSpriteSize(sprite, touchX, touchY) {} // 從新計算sprite的角度 reCalSpriteRotate(sprite, touchX, touchY) {} // 返回當前touch的sprite getTouchSpriteTarget({ touchX, touchY }) {} // 判斷是否touch在了sprite中的某一部分上,返回這個sprite getTouchTargetOfSprite({ touchX, touchY }, part) {} // 返回觸摸點相對於canvas的座標 normalizeTouchEvent(e) {} // 判斷是否在在某個sprite中移動。當前默認全部的sprite都是長方形的。 checkIfTouchIn({ touchX, touchY }, sprite) {} // 從場景中刪除 remove(sprite) {} // 畫出stage中的全部sprite drawSprite() {} // 清空畫布 clearStage() {} }
Sprite
的方法:優化
class Sprite { constructor(props) {} // 設置四個頂點的初始化座標 setCoordinate(pos, size) {} // 根據旋轉角度更新sprite的全部部分的頂點座標 updateCoordinateByRotate() {} // 根據旋轉角度更新頂點座標 updateItemCoordinateByRotate(target, center, angle){} // 根據縮放比例更新頂點座標 updateItemCoordinateByScale(sprite, center, scale) {} // 根據按鈕icon的頂點座標獲取icon中心點座標 getIconCenter(iconCoordinate) {} // 根據按鈕icon的中心點座標獲取icon的頂點座標 getIconCoordinateByIconCenter(center) {} // 根據縮放比更新頂點座標 updateCoordinateByScale() {} // 畫出該sprite draw(ctx) {} // 畫出該sprite對應的按鈕icon drawIcon(ctx, icon) {} // 對sprite進行初始化 init() {} // 初始化刪除按鈕,左下角 initDelIcon() {} // 初始化縮放按鈕,右上角 initScaleIcon() {} // 初始化旋轉按鈕,左上角 initRotateIcon() {} // 重置icon的位置與大小 resetIconPos() {} // 根據移動的距離重置sprite全部部分的位置 resetPos(dirX, dirY) {} // 根據觸摸點移動的距離計算縮放比,並重置sprite的尺寸 resetSize(dir) {} // 設置sprite的旋轉角度 setRotateAngle(angleDir) {} }
Stage
的方法主要是處理和用戶交互的邏輯,獲得用戶操做的交互參數,而後根據交互參數調用Sprite
的方法來進行變化。
代碼在這裏:https://coypan.info/demo/canvas-drag-scale-rotate.html
本文介紹了文章開頭給出的demo的詳細實現過程。代碼還有很大的優化空間。事實上,工做上的需求並無要求【旋轉】,只須要實現【拖拽】、【縮放】便可。在只實現【拖拽】和【縮放】的狀況下,會容易不少,不須要用到四個頂點的座標以及以前的那些複雜的數學知識。而在本身實現【旋轉】的過程當中,也學到了不少。符合預期。