成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

canvas中的拖拽、縮放、旋轉(zhuǎn) (下) —— 代碼實現(xiàn)

pumpkin9 / 1148人閱讀

摘要:中的拖拽縮放旋轉(zhuǎn)上數(shù)學(xué)知識準(zhǔn)備。表示整個區(qū)域,表示中的元素。事實上,工作上的需求并沒有要求旋轉(zhuǎn),只需要實現(xiàn)拖拽縮放即可。

寫在前面

本文首發(fā)于公眾號:符合預(yù)期的CoyPan

demo體驗地址及代碼在這里:請用手機或瀏覽器模擬手機訪問

上一篇文章介紹了canvas中的拖拽、縮放、旋轉(zhuǎn)中涉及到的數(shù)學(xué)知識??梢渣c擊下面的鏈接查看。

canvas中的拖拽、縮放、旋轉(zhuǎn) (上) —— 數(shù)學(xué)知識準(zhǔn)備。

代碼準(zhǔn)備 - 如何在canvas中畫出一個帶旋轉(zhuǎn)角度的元素

canvas中,如果一個元素帶有一個旋轉(zhuǎn)角度,可以直接變化canvas的坐標(biāo)軸來畫出此元素。舉個例子,

ctx.save(); // 保存舊的坐標(biāo)系狀態(tài)
ctx.translate(x0 + w / 2, y0 + h / 2); // 坐標(biāo)原點移動到旋轉(zhuǎn)中心
ctx.rotate(angle); // 旋轉(zhuǎn)坐標(biāo)系
ctx.translate(-(x0 + w / 2),  -(y0 + h / 2)); // 坐標(biāo)原點還原
ctx.rect(x0, y0, w, h); // 以新坐標(biāo)系為參照,畫出矩形。
ctx.restore(); // 還原之前的坐標(biāo)系狀態(tài)
代碼整體思路

整個demo的實現(xiàn)思路如下:

用戶開始觸摸(touchstart)時,獲取用戶的觸摸對象,是Sprite的本體?刪除按鈕?縮放按鈕?旋轉(zhuǎn)按鈕?并且根據(jù)各種情況,對變化參數(shù)進行初始化。

用戶移動手指(touchmove)時,根據(jù)手指的坐標(biāo),更新stage中的所有元素的位置、大小,記錄變化參數(shù)。修改對應(yīng)sprite的屬性值。同時對canvas進行重繪。

用戶一旦停止觸摸(touchend)時,根據(jù)變化參數(shù),更新sprite的坐標(biāo),同時對變化參數(shù)進行重置。

需要注意的是,在touchmove的過程中,并不需要更新sprite的坐標(biāo),只需要記錄變化的參數(shù)即可。在touchend過程中,再進行坐標(biāo)的更新。坐標(biāo)的唯一用處,就是判斷用戶點擊時,落點是否在指定區(qū)域內(nèi)。

代碼細節(jié)

首先,聲明兩個類:StageSpriteStage表示整個canvas區(qū)域,Sprite表示canvas中的元素。我們可以在Stage中添加多個Sprite,刪除Sprite。這兩個類的屬性如下。

class Stage {
    constructor(props) {
        
        this.canvas = props.canvas;
        this.ctx = this.canvas.getContext("2d");
        
        // 用一個數(shù)組來保存canvas中的元素。每一個元素都是一個Sprite類的實例。
        this.spriteList = []; 

        // 獲取canvas在視窗中的位置,以便計算用戶touch時,相對與canvas內(nèi)部的坐標(biāo)。
        const pos = this.canvas.getBoundingClientRect(); 
        this.canvasOffsetLeft = pos.left;
        this.canvasOffsetTop = pos.top;

        this.dragSpriteTarget = null; // 拖拽的對象
        this.scaleSpriteTarget = null; // 縮放的對象
        this.rotateSpriteTarget = null; // 旋轉(zhuǎn)的對象

        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的當(dāng)前大小
        this.baseSize = props.size; // sprite的初始化大小
        this.minSize = props.minSize; // sprite縮放時允許的最小size
        this.maxSize = props.maxSize; // sprite縮放時允許的最大size
        
        // 中心點坐標(biāo)
        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;

        // 四個頂點的坐標(biāo),順序為:左上,右上,左下,右下
        this.coordinate = this.setCoordinate(this.pos, this.size); 

        this.rotateAngle = 0; // 累計旋轉(zhuǎn)的角度
        this.rotateAngleDir = 0; // 每次旋轉(zhuǎn)角度

        this.scalePercent = 1; // 縮放比例
        
    }
}

demo中,點擊canvas下方的紅色方塊時,會實例化一個sprite,調(diào)用stage.append時,會將實例化的sprite直接push到StagespriteList屬性內(nèi)。

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內(nèi)
    append(sprite) {}

    // 監(jiān)聽事件
    initEvent() {}

    // 處理touchstart
    handleTouchStart(e) {}

    // 處理touchmove
    handleTouchMove(e) {}

    // 處理touchend
    handleTouchEnd() {}

    // 初始化sprite的拖拽事件
    initDragEvent(sprite, { touchX, touchY }) {}

    // 初始化sprite的縮放事件
    initScaleEvent(sprite, { touchX, touchY }) {}

    // 初始化sprite的旋轉(zhuǎn)事件
    initRotateEvent(sprite, { touchX, touchY }) {}

    // 通過觸摸的坐標(biāo)重新計算sprite的坐標(biāo)
    reCalSpritePos(sprite, touchX, touchY) {}

    // 通過觸摸的【橫】坐標(biāo)重新計算sprite的大小
    reCalSpriteSize(sprite, touchX, touchY) {}

    // 重新計算sprite的角度
    reCalSpriteRotate(sprite, touchX, touchY) {}

    // 返回當(dāng)前touch的sprite
    getTouchSpriteTarget({ touchX, touchY }) {}

    // 判斷是否touch在了sprite中的某一部分上,返回這個sprite
    getTouchTargetOfSprite({ touchX, touchY }, part) {}

    // 返回觸摸點相對于canvas的坐標(biāo)
    normalizeTouchEvent(e) {}

    // 判斷是否在在某個sprite中移動。當(dāng)前默認所有的sprite都是長方形的。
    checkIfTouchIn({ touchX, touchY }, sprite) {}

    // 從場景中刪除
    remove(sprite) {}

    // 畫出stage中的所有sprite
    drawSprite() {}

    // 清空畫布
    clearStage() {}
}

Sprite的方法:

class Sprite {

    constructor(props) {}

    // 設(shè)置四個頂點的初始化坐標(biāo)
    setCoordinate(pos, size) {}
    
    // 根據(jù)旋轉(zhuǎn)角度更新sprite的所有部分的頂點坐標(biāo)
    updateCoordinateByRotate() {}
    
    // 根據(jù)旋轉(zhuǎn)角度更新頂點坐標(biāo)
    updateItemCoordinateByRotate(target, center, angle){}

    // 根據(jù)縮放比例更新頂點坐標(biāo)
    updateItemCoordinateByScale(sprite, center, scale) {}

    // 根據(jù)按鈕icon的頂點坐標(biāo)獲取icon中心點坐標(biāo)
    getIconCenter(iconCoordinate) {}

    // 根據(jù)按鈕icon的中心點坐標(biāo)獲取icon的頂點坐標(biāo)
    getIconCoordinateByIconCenter(center) {}

    // 根據(jù)縮放比更新頂點坐標(biāo)
    updateCoordinateByScale() {}

    // 畫出該sprite
    draw(ctx) {}

    // 畫出該sprite對應(yīng)的按鈕icon
    drawIcon(ctx, icon) {}

    // 對sprite進行初始化
    init() {}

    // 初始化刪除按鈕,左下角
    initDelIcon() {}

    // 初始化縮放按鈕,右上角
    initScaleIcon() {}

    // 初始化旋轉(zhuǎn)按鈕,左上角
    initRotateIcon() {}

    // 重置icon的位置與大小
    resetIconPos() {}

    // 根據(jù)移動的距離重置sprite所有部分的位置
    resetPos(dirX, dirY) {}

    // 根據(jù)觸摸點移動的距離計算縮放比,并重置sprite的尺寸
    resetSize(dir) {}

    // 設(shè)置sprite的旋轉(zhuǎn)角度
    setRotateAngle(angleDir) {}
}

Stage的方法主要是處理和用戶交互的邏輯,得到用戶操作的交互參數(shù),然后根據(jù)交互參數(shù)調(diào)用Sprite的方法來進行變化。

代碼在這里:https://coypan.info/demo/canvas-drag-scale-rotate.html

寫在后面

本文介紹了文章開頭給出的demo的詳細實現(xiàn)過程。代碼還有很大的優(yōu)化空間。事實上,工作上的需求并沒有要求【旋轉(zhuǎn)】,只需要實現(xiàn)【拖拽】、【縮放】即可。在只實現(xiàn)【拖拽】和【縮放】的情況下,會容易很多,不需要用到四個頂點的坐標(biāo)以及之前的那些復(fù)雜的數(shù)學(xué)知識。而在自己實現(xiàn)【旋轉(zhuǎn)】的過程中,也學(xué)到了很多。符合預(yù)期。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/101699.html

相關(guān)文章

  • 基于javascript的拖拽類封裝

    摘要:參考了很多別人寫的代碼,最后終于弄明白了其中的原理,自己也寫了一個。效果圖如下地址如下拖拽類封裝代碼使用方法引入和對應(yīng)的。如果沒有為的結(jié)構(gòu),就創(chuàng)建。鼠標(biāo)移動時,記錄再次計算鼠標(biāo)位置距離中心位置的的反正切函數(shù)。 在公司做一個h5編輯平臺,中間需要對元素進行拖拽、放大縮小、旋轉(zhuǎn)等操作,且需要對文本、圖片、音樂組件等不同元素都可以具備這些功能。參考了很多別人寫的代碼,最后終于弄明白了其中的原...

    afishhhhh 評論0 收藏0
  • 用typescript開發(fā)手勢庫 - (1)web開發(fā)常用手勢有哪些?

    這只是個開頭 說在最前面,本文是一個系列文章的開頭, 這個系列里我會講如何用typescript開發(fā)一款支持pc和手機端的手勢庫any-touch, 以及通過jest讓你的代碼測試覆蓋率100%. showImg(https://segmentfault.com/img/bVbp3B0?w=936&h=246); 目錄 用TypeScript開發(fā)手勢庫 - (2)tsconfig.json & r...

    raise_yang 評論0 收藏0
  • 多功能React影像組件(拖拽、水印、縮放、切換、旋轉(zhuǎn))

    摘要:移動的過程中可以通過拿到元素的坐標(biāo),記為。向上滾動放大,向下滾動縮小這里要注意控制最小縮放值。還要注意的是圖片在邊界的縮放,不然圖片可能會移動在屏幕外。代碼實現(xiàn)控制滾輪縮放計算縮放后的大小每一次滾輪限制最小不讓由于縮小消失在視野中 cxj-react-image 用法如下: yarn add cxj-react-image // npm i cxj-react-image import...

    soasme 評論0 收藏0
  • jQuery 圖片查看插件 Magnify 開發(fā)簡介(仿 Windows 照片查看器)

    摘要:隨后會陸續(xù)發(fā)布及相關(guān)版本的插件。這和圖片查看器的操作方式是相同的。目前的調(diào)整大小存在一點,但不影響整體的使用。鍵盤控制和照片查看器的按鍵是一樣的。除了照片查看器,的圖片查看器也非常的高大上。 showImg(https://segmentfault.com/img/remote/1460000012565638?w=750&h=375); 前言 因為一些特殊的業(yè)務(wù)需求,經(jīng)過一個多月的蟄...

    anyway 評論0 收藏0

發(fā)表評論

0條評論

pumpkin9

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<