摘要:中的拖拽縮放旋轉上數學知識準備。表示整個區域,表示中的元素。事實上,工作上的需求并沒有要求旋轉,只需要實現拖拽縮放即可。
寫在前面
本文首發于公眾號:符合預期的CoyPan
demo體驗地址及代碼在這里:請用手機或瀏覽器模擬手機訪問
上一篇文章介紹了canvas中的拖拽、縮放、旋轉中涉及到的數學知識。可以點擊下面的鏈接查看。
canvas中的拖拽、縮放、旋轉 (上) —— 數學知識準備。
代碼準備 - 如何在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的實現思路如下:
用戶開始觸摸(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屬性內。
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的方法:
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的詳細實現過程。代碼還有很大的優化空間。事實上,工作上的需求并沒有要求【旋轉】,只需要實現【拖拽】、【縮放】即可。在只實現【拖拽】和【縮放】的情況下,會容易很多,不需要用到四個頂點的坐標以及之前的那些復雜的數學知識。而在自己實現【旋轉】的過程中,也學到了很多。符合預期。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/101699.html
摘要:參考了很多別人寫的代碼,最后終于弄明白了其中的原理,自己也寫了一個。效果圖如下地址如下拖拽類封裝代碼使用方法引入和對應的。如果沒有為的結構,就創建。鼠標移動時,記錄再次計算鼠標位置距離中心位置的的反正切函數。 在公司做一個h5編輯平臺,中間需要對元素進行拖拽、放大縮小、旋轉等操作,且需要對文本、圖片、音樂組件等不同元素都可以具備這些功能。參考了很多別人寫的代碼,最后終于弄明白了其中的原...
這只是個開頭 說在最前面,本文是一個系列文章的開頭, 這個系列里我會講如何用typescript開發一款支持pc和手機端的手勢庫any-touch, 以及通過jest讓你的代碼測試覆蓋率100%. showImg(https://segmentfault.com/img/bVbp3B0?w=936&h=246); 目錄 用TypeScript開發手勢庫 - (2)tsconfig.json & r...
摘要:移動的過程中可以通過拿到元素的坐標,記為。向上滾動放大,向下滾動縮小這里要注意控制最小縮放值。還要注意的是圖片在邊界的縮放,不然圖片可能會移動在屏幕外。代碼實現控制滾輪縮放計算縮放后的大小每一次滾輪限制最小不讓由于縮小消失在視野中 cxj-react-image 用法如下: yarn add cxj-react-image // npm i cxj-react-image import...
摘要:隨后會陸續發布及相關版本的插件。這和圖片查看器的操作方式是相同的。目前的調整大小存在一點,但不影響整體的使用。鍵盤控制和照片查看器的按鍵是一樣的。除了照片查看器,的圖片查看器也非常的高大上。 showImg(https://segmentfault.com/img/remote/1460000012565638?w=750&h=375); 前言 因為一些特殊的業務需求,經過一個多月的蟄...
閱讀 2741·2023-04-25 21:26
閱讀 1528·2021-11-25 09:43
閱讀 1961·2019-08-30 15:52
閱讀 942·2019-08-30 14:05
閱讀 2627·2019-08-29 16:10
閱讀 425·2019-08-29 13:48
閱讀 1870·2019-08-29 12:47
閱讀 1310·2019-08-23 18:04