canvas繪制基礎圖形圖像

趁著清明放假的空閑,將之前寫過的代碼整理了一下,發現了一個比較有意思的項目,該項目其實也比較簡單,就是利用Canvas的各種原生API在圖像中繪制一些基礎圖形,以及一些圖形的更改操作。順便借此項目復習一下Canvas基礎。

目前實現功能
基本實現功能
  1. 圖片的放大縮小和拖拽
  2. 繪制多邊形并修改
  3. 繪制矩形并修改
  4. 繪制線段(暫無修改)
  5. 繪制箭頭(暫無修改)
圖片的放大和縮小
  • drawImage() 圖片的繪制
// 繪制圖片
    drawImage = () => {
        if (this.$imageDom) {
            try {
                this._context.drawImage(
                    this.$imageDom,  // 圖片元素
                    0, // 開始剪切的 x 坐標位置
                    0, // 開始剪切的 y 坐標位置
                    this.imageOriginWidth,  //被剪切圖像的寬度
                    this.imageOriginHeight, //被剪切圖像的寬度
                    this.offsetX, // 在畫布上放置圖像的 x 坐標位置
                    this.offsetY, //在畫布上放置圖像的 y 坐標位置
                    this.imageOriginWidth * this.currentRatio, //要使用的圖像的寬度
                    this.imageOriginHeight * this.currentRatio //要使用的圖像的高度
                );

                return Promise.resolve();

            } catch (e) {
                console.log(e)
            }
        }
    }
  • 計算畫布上放置圖像的坐標位置
按照上圖方式去計算要放置的圖像的點坐標
getOffset = (pointX, pointY, scale, ratio, dir) => {
        if (pointX && pointY) {
            // 獲取圖片
            const width = this.imageOriginWidth * (scale - ratio * dir);
            const height = this.imageOriginHeight * (scale - ratio * dir);
            const x = this.offsetX;
            const y = this.offsetY;
            if ((pointX < x) && (pointY >= y && pointY <= y + height)
            ) {
                // 1
                this.offsetY = pointY - (pointY - this.offsetY) / (scale - ratio * dir) * scale;

            } else if ((pointX < x) && pointY >= y + height) {
                // 2
                this.offsetX = x;
                this.offsetY = (ratio * dir * this.imageOriginHeight - this.offsetY) * (-1);
            } else if (pointX > x + width && (pointY >= y && pointY <= y + height)) {
                // 5
                this.offsetY = pointY - (pointY - this.offsetY) / (scale - ratio * dir) * scale;
                this.offsetX = (ratio * dir * this.imageOriginWidth - this.offsetX) * (-1);
            } else if ((pointX >= x && pointX <= x + width) && (pointY > y + height)) {
                // 3
                this.offsetX = pointX - (pointX - this.offsetX) / (scale - ratio * dir) * scale;
                this.offsetY = (ratio * dir * this.imageOriginHeight - this.offsetY) * (-1);
            } else if (pointY < y && (pointX >= x && pointX <= x + width)) {
                // 7
                this.offsetX = pointX - (pointX - this.offsetX) / (scale - ratio * dir) * scale;
                this.offsetY = y;
            } else if (pointX > x + width && (pointY > y + height)) {
                // 4
                this.offsetX = (ratio * dir * this.imageOriginWidth - this.offsetX) * (-1);
                this.offsetY = (ratio * dir * this.imageOriginHeight - this.offsetY) * (-1);
            } else if (pointX > x + width && pointY < y) {
                // 6
                this.offsetY = y;
                this.offsetX = (ratio * dir * this.imageOriginWidth - this.offsetX) * (-1);
            } else if (pointX < x && pointY < y) {
                //  8 
                this.offsetX = x;
                this.offsetY = y;
            } else {
                // 9 
                this.offsetX = pointX - (pointX - this.offsetX) / (scale - ratio * dir) * scale;
                this.offsetY = pointY - (pointY - this.offsetY) / (scale - ratio * dir) * scale;

            }
        }
    }

多邊形的繪制(線的繪制、箭頭的繪制)
  • moveTo()
  • lineTo()
  • closePath()

根據上述原生API繪制線,多邊形的繪制即為坐標點大于2的路徑的閉合曲線。

判斷點是否在多邊形內
  • isPointInPath

繪制當前閉合路徑,根據該函數判斷點是否在路徑內。

判斷點是否在線上

由于上述方法是判斷點是否在路徑內,就無法判斷點是否在線上了,我采用的方法如下:

  1. 先判斷點的坐標是否在線的坐標范圍內,如果不在則點不在線上
  2. 如果1滿足,則根據直線公式 y = kx + b 通過線段已知兩點坐標求出斜率k和偏移值b;
  3. 根據線段的斜率和垂直線的斜率 k * k1 = -1,求出垂直線斜率,再根據當前點計算出通過該點的垂直線公式 y = (-1/k)x + m;
  4. 根據垂直相交線公式求出交點坐標,根據兩點(當前點和交點坐標)求出線段距離,如果該距離小于誤差范圍值,則認為點在線上,反之則認為不在線上。
判斷點是否在線上
function isPointInLinePath(line, dot, threshold) {
    const x2 = dot[0] ? dot[0] : 0;
    const y2 = dot[1] ? dot[1] : 0;
    const p1 = line[0];
    const p2 = line[1];
    const [p1X, p1Y] = p1;
    const [p2X, p2Y] = p2;
    let l = threshold + 1;
    let x = 0;
    let y = 0;
    if (
        ((p1X <= x2 && x2 <= p2X) || (p2X <= x2 && x2 <= p1X)) &&
        ((p1Y <= y2 && y2 <= p2Y) || (p2Y <= y2 && y2 <= p1Y))
    ) {
        const slop = _calSlop(p1, p2); // 計算斜率
        const verSlop = _calVerticalSlop(slop); // 計算垂直斜率

        const x1 = p1[0] || 0;
        const y1 = p1[1] || 0;

        if (slop != 0 && verSlop != 0) {
            if (y2 == slop * x2 + y1 - slop * y1) {
                // 點在當前直線上
                x = x2;
                y = y2;
            } else {
                x = parseFloat(
                    (y2 - y1 + slop * x1 - verSlop * x2) / (slop - verSlop)
                );
                y = parseFloat(slop * x + y1 - slop * x1);
            }
        } else {
            // 垂直于x軸或平行于x軸
            if (x1 == p2X) {
                // 平行于y軸
                x = x1;
                y = y2;
            } else if (y1 == p2Y) {
                // 平行于x軸
                x = x2;
                y = y1;
            }
        }

        if (
            (p1X <= x && x <= p2X) ||
            (p2X <= x && x <= p1X && (p1Y <= y && y <= p2Y)) ||
            (p2Y <= y && y <= p1Y)
        ) {
            l = parseInt(Math.sqrt(Math.pow(x2 - x, 2) + Math.pow(y2 - y, 2)));
        }
    }

    if (l < threshold) {
        // 說明是點在線上
        return true;
    }
    return false;
}
繪制矩形
  • rect()

根據原生API繪制圖形,修改時的判斷方式同多邊形的判斷。

實現原理就介紹到這里,更多詳細信息請去https://github.com/jdkwky/my-vue-example/tree/master/src/view/canvas中了解~

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內容

  • 1. 矢量減法 設二維矢量 P = (x1,y1) ,Q = (x2,y2) 則矢量減法定義為: P - Q = ...
    潭潭_180閱讀 2,265評論 0 1
  • 基于學生學習共同體培育語文生態課堂文化的研究 近年來,隨著現代教育理念的不斷深入與...
    火車頭123閱讀 2,009評論 0 8
  • 在Canvas中,線段也是路徑中的一種,被稱之為線性路徑。在Canvas中繪制線性路徑主要用到moveTo(x,y...
    王叮叮當當響閱讀 2,967評論 0 2
  • 原文地址:canvas圖形編輯器[https://jeff_zhong.gitee.io/blog/2017/11...
    jeffzhong閱讀 3,429評論 1 8
  • 回顧:七月任務: 1,組織四次讀書會,形式多樣。完成 2,組織兩次親子活動。 未完成 3,和家人每月至少10次電話...
    陳雪云2021閱讀 102評論 0 1