欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JS前端使用canvas搞一個(gè)手勢(shì)識(shí)別

 更新時(shí)間:2022年08月02日 15:26:05   作者:尤水就下  
這篇文章主要為大家介紹了JS前端使用canvas搞一個(gè)手勢(shì)識(shí)別的實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

最近在看一些關(guān)于圖形學(xué)的東西,寫了個(gè)一筆畫手勢(shì)識(shí)別的小 demo,效果大概是下面這個(gè)樣子:

如果你是初次看過(guò)肯定會(huì)覺(jué)得很有意思??。哈哈,話不多說(shuō),讓我們直接開擼吧。

這里可以先花幾秒鐘想一下你會(huì)怎么做???帶著問(wèn)題往下看能夠記得更牢固,比如你可能最關(guān)心的就是怎么識(shí)別怎么對(duì)比。這里先提前貼上項(xiàng)目 demo 地址,有需要的自取。另外這里并不會(huì)涉及什么人工智能、AI識(shí)別、深度學(xué)習(xí)啥的,所以請(qǐng)放心食用??。

具體步驟

發(fā)車?yán)?????????

第一步:手勢(shì)繪制

既然要識(shí)別那肯定得先有手勢(shì)啦,所以第一步要做的就是手勢(shì)繪制,這一步相對(duì)來(lái)說(shuō)比較簡(jiǎn)單,學(xué)習(xí)過(guò) canvas 的同學(xué)應(yīng)該有看過(guò)畫板的實(shí)現(xiàn),這個(gè)也是一樣的,監(jiān)聽 canvas 上的鼠標(biāo)事件,然后在移動(dòng)的時(shí)候?qū)⑹髽?biāo)坐標(biāo)點(diǎn)用線段相連即可,不同的是我們?cè)诶L制過(guò)程中還順便把每個(gè)坐標(biāo)點(diǎn)都畫了一下,核心代碼如下(可跳過(guò)):

handleMousemove(e: MouseEvent) {
    if (!this.isMove) return;
    const curPoint = this.getCanvasPos(e);
    const lastPoint = this.inputPoints[this.inputPoints.length - 1];
    // 畫線段
    CanvasUtils.drawLine(this.ctx2d, lastPoint[0], lastPoint[1], curPoint[0], curPoint[1], 'blue', 3);
    // 畫坐標(biāo)點(diǎn)
    CanvasUtils.drawCircle(this.ctx2d, curPoint[0], curPoint[1], 5);
    // 如果覺(jué)得原始點(diǎn)的數(shù)量太多,可以節(jié)流
    this.inputPoints.push(curPoint);
}

畫完之后大概是下面這個(gè)樣子:

從上圖可以看到繪制出來(lái)的紅點(diǎn)并不均勻,因?yàn)橐还P畫過(guò)程中的手速不一樣,疏密程度也就不一樣,所以為了避免這個(gè)因素的影響,我們需要重新取個(gè)樣。

第二步:重新取樣

不同場(chǎng)景的取樣方式也有所不同。這里我們簡(jiǎn)單的選擇等分線條取樣即可,也就是先計(jì)算出整個(gè)手勢(shì)的長(zhǎng)度(所有線段長(zhǎng)度相加),然后 n 等分取點(diǎn)(隨便幾等分,看效果調(diào)節(jié),不用糾結(jié))。

注意我們并沒(méi)有改變?cè)甲鴺?biāo)點(diǎn)的信息,手勢(shì)的繪制還是要按照原來(lái)的點(diǎn)繪制,所以需要加一個(gè)變量來(lái)存儲(chǔ)新采樣的點(diǎn)(后面的計(jì)算全都是用新的取樣點(diǎn)來(lái)計(jì)算)。這個(gè)計(jì)算還是有點(diǎn)小麻煩的,所以我準(zhǔn)備了一張圖方便大家理解????:

然后就是具體的代碼實(shí)現(xiàn)(大概懂了可跳過(guò)):

export type Point = [number, number];
static resample(inputPoints: Point[], sampleCount: number): Point[] {
    const len = GeoUtils.getLength(inputPoints);
    const unit = len / (sampleCount - 1);
    const outputPoints: Point[] = [[...inputPoints[0]]];
    let curLen = 0;
    let prevPoint = inputPoints[0];
    for (let i = 1; i < inputPoints.length; i++) {
        const curPoint = inputPoints[i];
        let dx = curPoint[0] - prevPoint[0];
        let dy = curPoint[1] - prevPoint[1];
        let tempLen = GeoUtils.getLength([prevPoint, curPoint]);
        while (curLen + tempLen >= unit) {
            const ds = unit - curLen;
            const ratio = ds / tempLen;
            const newPoint: Point = [prevPoint[0] + dx * ratio, prevPoint[1] + dy * ratio];
            outputPoints.push(newPoint);
            curLen = 0;
            prevPoint = newPoint;
            dx = curPoint[0] - prevPoint[0];
            dy = curPoint[1] - prevPoint[1];
            tempLen = GeoUtils.getLength([prevPoint, curPoint]);
        }
        prevPoint = curPoint;
        curLen += tempLen;
    }
    while (outputPoints.length < sampleCount) {
        outputPoints.push([...prevPoint]);
    }
    return outputPoints;
}

重新采樣之后大概是下面這個(gè)效果:

要注意如果你采用了 n 等分,那么所有的手勢(shì)都應(yīng)該是 n 等分的,不能改變,否則難以比較。另外我們順便把手勢(shì)的中心點(diǎn)算了出來(lái)(就是簡(jiǎn)單的把每個(gè)采樣點(diǎn)坐標(biāo)相加取平均值),并且將手勢(shì)的起始點(diǎn)(最后一個(gè)點(diǎn)也行)與中心點(diǎn)相連,這個(gè)你可以粗淺的認(rèn)為它表示的是這個(gè)手勢(shì)的大致方向,不理解可以先跳過(guò),后續(xù)會(huì)講到。

第二步:平移

其實(shí)你要比較任何東西,都是要量化成數(shù)字來(lái)比較的,而不是通過(guò)感覺(jué)。 不能說(shuō)我覺(jué)的兩個(gè)手勢(shì)長(zhǎng)得像它就像,那只是人工沒(méi)有智能,所以我們要怎么解決這個(gè)問(wèn)題呢?我們需要定一個(gè)標(biāo)準(zhǔn),讓所有手勢(shì)都在同一個(gè)模子下進(jìn)行比較(就好像你要找個(gè)對(duì)象,不得有個(gè)衡量標(biāo)準(zhǔn)嗎),比如都變成同樣的大小、同樣的方向。

不然你想想如果我豎著寫了一個(gè)很大的3和橫著寫了一個(gè)很小的3,它們要怎么比較。所以接下來(lái)我們要做的就是把手勢(shì)標(biāo)準(zhǔn)化(其實(shí)每幅示例圖中的虛線框就是我們的架子),為后續(xù)的比較打好基礎(chǔ),為此就需要經(jīng)歷平移、旋轉(zhuǎn)、縮放這幾個(gè)步驟。

關(guān)于平移,剛才我們已經(jīng)計(jì)算過(guò)手勢(shì)的中心點(diǎn),現(xiàn)在只需要把它移動(dòng)到畫布中心即可,簡(jiǎn)單算下平移距離,然后對(duì)所有新的采樣點(diǎn)做平移操作即可,示例代碼如下:

// 對(duì)每個(gè)坐標(biāo)點(diǎn)進(jìn)行平移
static translate(points: Point[], dx: number, dy: number) {
    points.forEach((p) => {
        p[0] += dx;
        p[1] += dy;
    });
}

效果如下:

要注意我們?cè)诶L制的時(shí)候需要將畫布左上角的原點(diǎn)移到到畫布中間,這樣做能夠極大的方便計(jì)算,包括接下來(lái)的旋轉(zhuǎn)和縮放也是在平移坐標(biāo)系的基礎(chǔ)上。

第三步:旋轉(zhuǎn)

細(xì)心的同學(xué)會(huì)發(fā)現(xiàn)除了中間的虛線框,我們還把整個(gè)畫布八等分了,這是為什么呢?其實(shí)上文中有提到,是因?yàn)槭謩?shì)具有方向性,比如 丨和 /,這兩種手勢(shì)本應(yīng)該很相近,但是方向不同,所以就需要進(jìn)行一定的旋轉(zhuǎn)。

而這里的八條等分線就是我們要靠近的方向(幾等分也是你自己隨意取的),于是乎我們可以簡(jiǎn)單地算下手勢(shì)方向(圖中的綠線)離哪條等分線近就往哪邊旋轉(zhuǎn),然后把所有的點(diǎn)都進(jìn)行旋轉(zhuǎn)變換即可,代碼如下(可跳過(guò)):

// 計(jì)算需要旋轉(zhuǎn)到最近輔助線的弧度,center 為中心點(diǎn),startPoint 為手勢(shì)起始點(diǎn),sublineCount 為坐標(biāo)等分?jǐn)?shù)量
static computeRadianToSubline(center: Point, startPoint: Point, sublineCount: number): number {
    const dy = startPoint[1] - center[1];
    const dx = startPoint[0] - center[0];
    let radian = Math.atan2(dy, dx);
    if (radian < 0) radian += TWO_PI;
    const unitRadian = TWO_PI / sublineCount;
    const targetRadian = Math.round(radian / unitRadian) * unitRadian;
    radian -= targetRadian;
    return radian;
}
// 對(duì)每個(gè)坐標(biāo)點(diǎn)進(jìn)行旋轉(zhuǎn)
static rotate(points: Point[], radian: number) {
    const sin = Math.sin(radian);
    const cos = Math.cos(radian);
    points.forEach((p) => {
        let [x, y] = p;
        p[0] = cos * x - sin * y;
        p[1] = sin * x + cos * y;
    });
}

很多同學(xué)可能會(huì)覺(jué)得旋轉(zhuǎn)比平移難,其實(shí)很簡(jiǎn)單的,你只需要知道一個(gè)點(diǎn)是怎么旋轉(zhuǎn)的就行了(線段的旋轉(zhuǎn)就是兩個(gè)端點(diǎn)的旋轉(zhuǎn),多邊形的旋轉(zhuǎn)就是多個(gè)頂點(diǎn)的旋轉(zhuǎn)),這里我畫了張推導(dǎo)圖方便大家理解(不感興趣也可以跳過(guò)):

然后看下這步的效果圖:

第四步:縮放

我們每次繪制的手勢(shì)是有大有小的,所以這里需要統(tǒng)一成一個(gè)大小,也就是做個(gè)縮放。

比如我們要把一個(gè) 600*600 的手勢(shì)放進(jìn)一個(gè) 100*100 的容器中(也就是圖中的虛線框),那就要縮小 6 倍。

那具體要怎么求呢?首先我們要求出手勢(shì)的包圍盒大小,這里采用AABB模型(還有OBB、球模型等)。

那什么是 AABB 包圍盒呢,這個(gè)賊簡(jiǎn)單,就是找出所有采樣點(diǎn)的最大最小 x、y 值即可,就像下面這樣:

現(xiàn)在只要用容器長(zhǎng)度除以 AABB 的最長(zhǎng)邊,得到的就是縮放倍數(shù)。然后同樣的,遍歷所有點(diǎn)進(jìn)行縮放操作,具體代碼如下:

// 再次提醒下因?yàn)槲覀円呀?jīng)把坐標(biāo)系移到了畫布中央,畫布中心和手勢(shì)中心是重合的,所以直接乘以縮放倍速就可以了
static scale(points: Point[], scale: number) {
    points.forEach((p) => {
        let [x, y] = p;
        p[0] = x * scale;
        p[1] = y * scale;
    });
}

效果圖如下:

注意不是說(shuō)縮放之后的圖形一定要在虛線框里面,而是縮放之后的圖形大小和虛線框差不多。

第五步:手勢(shì)錄入

這個(gè)就是簡(jiǎn)單的保存數(shù)據(jù),一共可分為兩步:

  • 縮略圖:動(dòng)態(tài)地創(chuàng)建一個(gè) canvas 來(lái)繪制手勢(shì),再通過(guò) drawImage 繪制到畫布上,這個(gè)其實(shí)和第一步是一樣的,只不過(guò)圖變小了。用原始點(diǎn)或采樣點(diǎn)畫都可以(原始點(diǎn)比較精確),畢竟是縮略圖,看不出來(lái)太大差別。
  • 保存數(shù)據(jù):采樣坐標(biāo)點(diǎn)肯定是要保存的,畢竟我們辛辛苦苦標(biāo)準(zhǔn)化了這么久,其它的想保存啥就保存啥。

第六步:比較(重點(diǎn))

假設(shè)我們已經(jīng)有了兩個(gè)標(biāo)準(zhǔn)化后的手勢(shì),那怎樣才能知道他們相似呢?如果你沒(méi)看過(guò)相關(guān)知識(shí),大概率是不懂的,我。也是??。。。同樣的,這里也可以停下來(lái)思考幾秒種??。。。 ok,其實(shí)手勢(shì)相似與否可以轉(zhuǎn)成兩組采樣點(diǎn)是否足夠靠近的問(wèn)題,一種直觀的解法就是計(jì)算兩組采樣點(diǎn)之間的距離,看是否小于某個(gè)閾值,類似下面這樣:

不懂的話想成一個(gè)采樣點(diǎn)就好理解了(就變成了求兩點(diǎn)距離??),具體代碼如下:

static squaredEuclideanDistance(points1, points2) {
    let squaredDistance = 0;
    const count = points1.length;
    for (let i = 0; i < count; i++) {
        const p1 = points1[i];
        const p2 = points2[i];
        const dx = p1[0] - p2[0];
        const dy = p1[1] - p2[1];
        squaredDistance += dx * dx + dy * dy;
    }
    return squaredDistance;
}

其實(shí)上面這種方法有個(gè)高大上的名字,叫歐氏距離(好了好了,別裝了??)。

但是對(duì)于我們這種場(chǎng)景有個(gè)更好的相似度算法(算法?溜了溜了?。?,所以接下來(lái)我們來(lái)介紹一個(gè)余弦相似度的概念(不難的,我都畫了圖的,包看包會(huì)):

如果上圖采用的是歐氏距離比較,顯然 AC 距離更近更相似。

如果用余弦值比較,那顯然是 AB 更相似。

這是因?yàn)闅W氏距離得到的是絕對(duì)差異,余弦相似度比較的是相對(duì)差異(仔細(xì)品品??)。

那為什么夾角的大小可以判定兩點(diǎn)的相似度呢?

其實(shí)這個(gè)方法主要判定的是兩點(diǎn)方向的相似度,你可以看到即便向量B很長(zhǎng),但是不影響它的方向朝向,所以B的目標(biāo)朝向和A更相近,這個(gè)用力學(xué)的知識(shí)會(huì)比較好理解一點(diǎn),看下下面這張圖:

夾角越小,發(fā)力的方向才越一致,我們才能拉動(dòng)一個(gè)物體(我們就是有相同目標(biāo)的一類人,也就是相似)。那這么多個(gè)點(diǎn)我們?cè)趺此阌嘞蚁嗨贫饶兀?/p>

回頭看看剛才求夾角的公式,既然只和方向相關(guān),而和向量A、向量B長(zhǎng)度無(wú)關(guān),那么我們一般可以把A、B變成單位向量(就是向量除以它們自身的長(zhǎng)度),這樣A、B的模長(zhǎng)就為1,于是余弦值就可以變成這樣:

是不是突然簡(jiǎn)單了不少,接下來(lái)我們就想辦法把手勢(shì)變成向量就行(就是變成一個(gè)很長(zhǎng)的數(shù)組),這里看圖理解會(huì)方便些:

我們可以把轉(zhuǎn)變后的一維數(shù)組叫做這個(gè)手勢(shì)的特征,并當(dāng)做數(shù)據(jù)保存下來(lái),下次比較的時(shí)候直接把這個(gè)數(shù)組拿出來(lái)算余弦值即可。

// 計(jì)算余弦相似度
static calcCosDistance(vector1: number[], vector2: number[]): number {
    let similarity = 0;
    vector1.forEach((v1, i) => {
        const v2 = vector2[i];
        similarity += v1 * v2;
    });
    return similarity; // 相似度介于 -1~1
}

余弦相似度在很多場(chǎng)合都是有用到的,比如文章相似度中詞向量的應(yīng)用(扯遠(yuǎn)了),所以這里簡(jiǎn)要回顧一下它的具體思路:

  • 想辦法把原始數(shù)據(jù)轉(zhuǎn)換成長(zhǎng)度相同的一維數(shù)組[a, b, c, ..., n],(雖然是一維數(shù)組,但是是 n 維向量,不理解沒(méi)關(guān)系)。
  • 遍歷現(xiàn)有數(shù)據(jù),分別求出對(duì)應(yīng)的余弦值,找出相似度值最高的那一個(gè)。

注意事項(xiàng)

  • 手勢(shì)具有方向性:我們可以識(shí)別|/,因?yàn)樗麄兘?jīng)過(guò)旋轉(zhuǎn)都靠近y軸,但是|就不行了,一個(gè)是 y 軸一個(gè)是 x 軸。所以如果我們要想把|識(shí)別成一個(gè)東西可以這樣子搞,把|多旋轉(zhuǎn)幾個(gè)角度,在每個(gè)角度都判斷一下是否相似。
  • 手勢(shì)的寬高比會(huì)影響結(jié)果:比如你畫一個(gè)正方形和一個(gè)長(zhǎng)長(zhǎng)扁扁的矩形是不相似的。
  • 采樣點(diǎn)的數(shù)量:過(guò)多過(guò)少都不行,過(guò)多效率低,對(duì)圖形一致性要求也高,反之同理。
  • 手勢(shì)的復(fù)雜度:圖形的識(shí)別率和圖形的復(fù)雜性沒(méi)有太大關(guān)系。簡(jiǎn)單的圖形由于特征不明顯,容易出錯(cuò),比如多邊型和圓。復(fù)雜的圖形,采樣點(diǎn)就容易被稀釋,得到的特征比較粗。
  • 應(yīng)用場(chǎng)景:大家可以自己想想這個(gè)東西除了用在手勢(shì)還能用在那里?這里舉個(gè)例子,比如數(shù)學(xué)老師在遠(yuǎn)程上課、寫板書的時(shí)候,經(jīng)常需要徒手畫圓或者畫正方形,這里我們就可以幫其自動(dòng)校正,如果畫的像一個(gè)圓就自動(dòng)重新生成一個(gè)正圓,也許描述的比較蒼白,所以大家可以自行腦補(bǔ)一下畫面??。

比較的基本套路(可跳過(guò))

這里簡(jiǎn)單補(bǔ)充下比較兩個(gè)東西是否相似的一般套路,也就兩大步:

特征提?。ň褪翘幚頂?shù)據(jù)的過(guò)程):

不管是什么東西,都有對(duì)應(yīng)的原始數(shù)據(jù),我們要做的就是將其(經(jīng)過(guò)層層處理)轉(zhuǎn)換成同一個(gè)框架維度下(也就是標(biāo)準(zhǔn)化),通常就是將原始數(shù)據(jù)轉(zhuǎn)換成長(zhǎng)度相同的一維數(shù)組(再次強(qiáng)調(diào)雖然是一維數(shù)組,但其實(shí)是 n 維向量)。

算法識(shí)別(就是比較數(shù)據(jù)的過(guò)程):

  • 通過(guò)某種算法(比如上面提到的歐氏距離和余弦相似度)進(jìn)行逐一對(duì)比。
  • 類似的還有網(wǎng)格識(shí)別(先把圖片馬賽克化,像素粒度就變粗了,然后根據(jù)像素顏色差值進(jìn)行比較,這個(gè)方法是適用于以縮略圖找原圖)、方向識(shí)別(比如只要手勢(shì)順序是先向右再向下再向左再向上就認(rèn)為是矩形)等。
  • 顯然不同的特征和算法就造成了結(jié)果的千差萬(wàn)別(效率啊、準(zhǔn)確率等,還有薪資待遇???),優(yōu)化的手段也是百花齊放,所以也就沒(méi)有通用的算法,只有適合的算法,因地制宜。

我們以一個(gè)極其簡(jiǎn)單的推薦算法為例,推薦算法的問(wèn)題在某種程度上可以轉(zhuǎn)換成兩個(gè)人的喜好相似程度:

喜好干飯摸魚睡覺(jué)就是玩...
甲(咸魚)????...
乙(翻身)????...
????...

這個(gè)和我們的手勢(shì)識(shí)別不能說(shuō)是很像,只能說(shuō)是一模一樣,在已有的手勢(shì)中(甲乙)找一個(gè)和我(喜好)相似度較高的,每一行其實(shí)就是一系列采樣點(diǎn),最終可以簡(jiǎn)單的推斷出我(可能)是條咸魚??,還喜歡摸魚。

又比如你打算買一臺(tái)電腦,那大概率是先看下周圍的人用什么,然后你就買什么,從眾本質(zhì)上也是一種相似(大眾的選擇就是方向),近朱者赤近墨者黑嘛。如果你說(shuō)很獨(dú)立自主,自己想買啥買啥,那也是對(duì)的,畢竟這玩意怎么搞都搞不到 100%。

關(guān)于多筆畫(可跳過(guò))

我們本文學(xué)的是單筆畫,現(xiàn)在你可以稍微想下,如果是多筆畫應(yīng)該怎么搞?這里還是可以短暫的思考幾秒種??。。。

  • 對(duì)于簡(jiǎn)單的一筆畫來(lái)說(shuō)上面的識(shí)別效果是很不錯(cuò)的,不論是效率和準(zhǔn)確率,但如果是多筆畫,那就復(fù)雜起來(lái)了,比如漢字的識(shí)別(想想就頭大??)。
  • 這里就介紹一個(gè)簡(jiǎn)單的識(shí)別方法,就是把多筆畫拆成單筆畫,通過(guò)本文的學(xué)習(xí)你可以求出每個(gè)單筆畫的相似度,然后簡(jiǎn)單求和就可以得到整個(gè)字體的相似度,最后取相似度最高的即可(就這??)。

舉個(gè)具象點(diǎn)的例子,比如這個(gè)字(這里僅僅是例子哈,不完全是這樣):

提取每個(gè)漢字的筆畫特征,一般可以采集起始點(diǎn)、終點(diǎn)和中間的轉(zhuǎn)折點(diǎn)。數(shù)據(jù)大概長(zhǎng)下面這個(gè)樣子:

  • 處理數(shù)據(jù)(標(biāo)準(zhǔn)化的過(guò)程,比如把每個(gè)字移到畫布中心,縮放成一樣的大?。?/li>
  • 比較數(shù)據(jù)(選個(gè)算法,這里就是先判斷下筆畫數(shù),再簡(jiǎn)單的將單筆相似度相加求和) 這就完了?當(dāng)然還差得遠(yuǎn)呢,問(wèn)題一抓一大把。比如:
  • 由于存在連筆的情況,一筆可能寫成兩筆,所以我們應(yīng)該允許筆畫的誤差在 2 左右,但是在最終排序時(shí),筆畫數(shù)越接近的,優(yōu)先級(jí)越高。
  • 每一筆當(dāng)中至少包含起點(diǎn)和終點(diǎn),中間可能有幾個(gè)拐點(diǎn),如果比較的時(shí)候單筆的坐標(biāo)點(diǎn)數(shù)量不同該怎么處理?一種方式是進(jìn)行插值計(jì)算,另一種方式是取最初的采樣點(diǎn)信息。

采用上述的方式如果我寫了個(gè)字是不是好像也能識(shí)別出來(lái),大體都是一橫一豎,有沒(méi)有什么辦法可以避免呢?當(dāng)然是有的,現(xiàn)在我們每一筆保存的不再是點(diǎn)的坐標(biāo),而是該點(diǎn)與前一個(gè)點(diǎn)連線的角度,如果是每一筆的起始點(diǎn),就拿上一筆的終點(diǎn)作為前一個(gè)點(diǎn),說(shuō)起來(lái)比較抽象,所以我又畫了張圖????(很簡(jiǎn)單的一張圖,不要被嚇到??):

大家想想如果是字,在上圖的第二個(gè)角度(綠2)中是不是就可以明顯區(qū)分開了。另外我們只保存了兩兩點(diǎn)之間的角度,還省了不少空間呢。

看起來(lái)好像沒(méi)問(wèn)題了?不,還是差得遠(yuǎn)呢。你想想要是筆畫順序不對(duì)咋整。還是以為例,我先寫豎再寫橫咋整。啊這。。。其實(shí)還有其他識(shí)別方法,比如把文字按坐標(biāo)軸切分成四塊,分四段校驗(yàn),這就不深入了,點(diǎn)到即止(畢竟就懂點(diǎn)皮毛)。

小結(jié)

以上就是手勢(shì)識(shí)別的大致思路,雖然看起來(lái)是挺高大上的一個(gè)東西,但是讀完之后應(yīng)該覺(jué)得。不。。算難吧。。。有些東西不是你不會(huì)只是你不知道也沒(méi)去嘗試下,嘿嘿。最后,再次送上項(xiàng)目地址傳送門,順便附上我 canvas 專欄的另外兩篇實(shí)戰(zhàn)文章:

html2canvas 用著有問(wèn)題?手寫一個(gè)就知道為啥了??

??用 canvas 來(lái)畫個(gè)函數(shù)曲線吧!縱享絲滑

更多關(guān)于JS前端canvas手勢(shì)識(shí)別的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 文字幻燈片

    文字幻燈片

    文字幻燈片...
    2006-06-06
  • 微信小程序 歡迎界面開發(fā)的實(shí)例詳解

    微信小程序 歡迎界面開發(fā)的實(shí)例詳解

    這篇文章主要介紹了微信小程序 歡迎界面開發(fā)的實(shí)例詳解的相關(guān)資料,這里實(shí)現(xiàn)歡迎界面的簡(jiǎn)單實(shí)例和實(shí)現(xiàn)代碼及實(shí)現(xiàn)效果圖,需要的朋友可以參考下
    2016-11-11
  • JavaScript實(shí)例?ODO?List分析

    JavaScript實(shí)例?ODO?List分析

    這篇文章主要介紹了JavaScript實(shí)例?ODO?List分析,主要利用JavaScript、css、HTML等實(shí)例代碼展開起內(nèi)容的解析,需要的小伙伴可以參考一下
    2022-01-01
  • 微信小程序引用公共js里的方法的實(shí)例詳解

    微信小程序引用公共js里的方法的實(shí)例詳解

    這篇文章主要介紹了微信小程序引用公共js里的方法的實(shí)例詳解的相關(guān)資料,這里提供了實(shí)現(xiàn)的方法,希望能幫助到大家,需要的朋友可以參考下
    2017-08-08
  • JavaScript 定時(shí)器詳情

    JavaScript 定時(shí)器詳情

    這篇文章主要介紹了JavaScript 定時(shí)器,在JavaScript中定時(shí)器有兩個(gè) setInterval() 與 setTimeout() 分別還有取消定時(shí)器的方法,下面來(lái)看看文章的詳細(xì)介紹
    2021-11-11
  • 微信小程序 教程之小程序配置

    微信小程序 教程之小程序配置

    這篇文章主要介紹了微信小程序 教程之小程序配置的相關(guān)資料,這里對(duì)app.json,pages,window等做了詳細(xì)介紹,對(duì)于初學(xué)開發(fā)微信小程序的朋友,掌握這些還是比較重要的,需要的朋友可以參考下
    2016-10-10
  • 手把手教你從0搭建前端腳手架詳解

    手把手教你從0搭建前端腳手架詳解

    這篇文章主要介紹了手把手教你從0搭建前端腳手架詳解,腳手架就是在啟動(dòng)的時(shí)候詢問(wèn)一些簡(jiǎn)單的問(wèn)題,并且通過(guò)用戶回答的結(jié)果去渲染對(duì)應(yīng)的模板文件,需要的朋友可以參考下
    2023-03-03
  • 微信小程序使用navigateTo數(shù)據(jù)傳遞的實(shí)例

    微信小程序使用navigateTo數(shù)據(jù)傳遞的實(shí)例

    這篇文章主要介紹了微信小程序使用navigateTo數(shù)據(jù)傳遞的實(shí)例的相關(guān)資料,希望通過(guò)本文能幫助到大家,需要的朋友可以參考下
    2017-09-09
  • 解析Clipboard?API剪貼板操作實(shí)例

    解析Clipboard?API剪貼板操作實(shí)例

    這篇文章主要為大家介紹了解析Clipboard?API剪貼板操作實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • 微信小程序 window_x64環(huán)境搭建

    微信小程序 window_x64環(huán)境搭建

    這篇文章主要介紹了微信小程序 window_x64環(huán)境搭建的相關(guān)資料,需要的朋友可以參考下
    2016-09-09

最新評(píng)論