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

JS前端canvas交互實現(xiàn)拖拽旋轉(zhuǎn)及縮放示例

 更新時間:2022年08月03日 09:01:29   作者:尤水就下  
這篇文章主要為大家介紹了JS前端canvas交互實現(xiàn)拖拽旋轉(zhuǎn)及縮放示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

正文

到目前為止,我們已經(jīng)能夠?qū)ξ矬w進行點選和框選的操作了,但是這還不夠,因為并沒有什么實際性的改變,并且畫布看起來也有點呆板,所以這個章節(jié)的主要目的就是讓畫布中的物體活起來,其實就是增加一些常見的交互而已啦??,比如拖拽、旋轉(zhuǎn)和縮放。這是這個系列最重要的章節(jié)之一,希望能夠?qū)δ阌兴鶐椭?/p>

拖拽

先來說說拖拽平移的實現(xiàn)吧,因為它最為簡單??。我們知道每個物體都是有 top 和 left 值來表示物體位置的,所以平移的時候只需要簡單的更新下物體的 top 和 left 值即可,然后每次移動都會觸發(fā) renderAll 方法進行重新渲染,于是就自然而然的在新的位置繪制物體了。

這個就是典型的數(shù)據(jù)與視圖分離,這個章節(jié)包括接下來的章節(jié)我們一般都不需要去修改物體的 render 方法了,但凡畫布上有物體在動(物體狀態(tài)改變了),我們都只需要更新物體的數(shù)據(jù)就行,而不用去關心如何繪制,反正值改了會自然而然的反應到畫布上,這點很重要。

然后簡單看下平移的代碼????:

/** 平移當前選中物體 */
_translateObject(x: number, y: number) {
    const target = this._currentTransform.target;
    target.set('left', x - this._currentTransform.offsetX); // offsetX 是畫布整體偏移
    target.set('top', y - this._currentTransform.offsetY); // offsetY 是畫布整體偏移
}

是的,代碼就那么點,也不難理解,因為物體的繪制方法是固定的,我們所做的任何變換操作都僅僅是單純的修改數(shù)據(jù)而已。不過要提下上面代碼中的 _currentTransform 是什么東西,它就是一開始我們按下鼠標時記錄的一些初始信息,大概長下面這個樣子,看看就行,有個印象即可????:

em...,沒錯,拖拽平移的部分就那么短,畢竟確實簡單。

旋轉(zhuǎn)

再來說下旋轉(zhuǎn)吧,旋轉(zhuǎn)也比較簡單。我們知道每個物體都是有一個 angle 變量來表示物體旋轉(zhuǎn)角度的,當對物體進行旋轉(zhuǎn)操作的時候,我們可以先計算出拖拽旋轉(zhuǎn)的角度 deltaAngle,于是新的 angle = 舊的 angle + deltaAngle,然后重新賦值 angle 變量即可,同樣的這個過程中也不會涉及修改物體的 _render 方法,只不過比平移稍微麻煩點的就是這個變換的角度該怎么計算呢?

其實旋轉(zhuǎn)的過程本質(zhì)就是鼠標點的旋轉(zhuǎn),也就是說我們只要計算出當前鼠標點和初始鼠標點之間的角度就行。就像下面這張圖一樣:

我們先來看看一個點的情況下,怎么算這個點的朝向,一般我們算的是該點與原點的連線和 x 軸正方向之間的逆時針方向的夾角,如下圖所示:

通常我們會用 radian = Math.atan2(y, x) 來計算弧度,注意是弧度(radian)不是角度(angle),所以再提醒下,canvas 中用的都是弧度,但是角度方便我們理解,所以時不時需要轉(zhuǎn)換;

另外要注意我們用的是 Math.atan2 而不是 Math.atan,雖然它們大同小異,但是我們不能根據(jù) atan 的值來確定唯一的方向,比如點(1, 1)和點(-1, -1),它們的 atan 值都一樣,但是方向確相反,所以有了 atan2,atan2 的取值范圍在 [-Math.PI, Math.PI] 之間,并且四個象限的取值各不相同,所以一般都是用它來計算。

知道了這些計算就簡單了,原點就是物體的中心點,鼠標按下的點可以與物體中心點相連形成一個起始角度,鼠標拖拽時的點也可以與物體中心點相連形成一個最終角度,用最終角度-起始角度就能得到要變換的角度了。

切記,通常情況下我們對什么物體進行旋轉(zhuǎn),原點就是物體的中心點。下面是核心的代碼示例,代碼不多也好消化????:

/** 旋轉(zhuǎn)當前選中物體 */
_rotateObject(x: number, y: number) {
    const t = this._currentTransform;
    const o = this._offset;
    // 鼠標按下的點與物體中心點連線和 x 軸正方向形成的弧度
    const lastRadian = Math.atan2(t.ey - o.top - t.top, t.ex - o.left - t.left);
    // 鼠標拖拽的終點與物體中心點連線和 x 軸正方向形成的弧度
    const curRadian = Math.atan2(y - o.top - t.top, x - o.left - t.left);
    const deltaRadian = curRadian - lastRadian;
    let angle = Util.radiansToDegrees(t.theta + deltaRadian); // 新的角度 = 原來的角度 + 變換的角度
    if (angle < 0) angle = 360 + angle;
    angle = angle % 360;
    t.target.angle = angle;
}

縮放

再來就是縮放啦,這個又比上面的旋轉(zhuǎn)稍微麻煩些,這里我們以右邊中間的縮放控制點為例子,其他控制點是一個意思(復制改改就行),先看看效果????:

大家仔細看上圖中右邊中間紅色的那個控制點,它的縮放結(jié)果其實是就沿著 x 軸拉伸,本能的想法是什么呢?就是計算出水平方向的拖拽距離 dx,然后去改變物體的寬度,就像這樣 object.width += dx,但是如果 width 變成了負數(shù)怎么辦,是不是也要處理一下,簡單點的做法就是我們可以限制個最小值,如果是右邊的控制點拉到最左邊了,就不允許再拉了。

不過,不知道你還記得我們早前說過的一個知識點么???就是我們一般不會去改變物體自身的大小,而是去修改物體的變換值,所以縮放的本質(zhì)也僅僅是改變物體的 scaleX 和 scaleY 值。還是以拖拽右邊中間控制點的拉伸為例子,這次我們算的是 scaleX,怎么算這個值會方便點呢?可以將拉伸的變換基點暫時變?yōu)樽筮呏虚g的控制點,也就是左邊的藍點(這個很重要),這樣計算當前寬度的時候就會比較方便了:

  • 當前寬度 = 鼠標位置的 x - 左邊中間控制點的位置的 x
  • scaleX = 當前寬度 / 自身寬度 記住,我們自身 width 的值并沒有改變,只是改變了 scaleX 值。同樣的它也有反向拉伸的問題,但我們可以變通處理一下,臨時變換下拉伸基點。什么意思呢???就是一旦變成反向拉伸,我們就立馬切換成按左邊中間控制點拖拽的邏輯執(zhí)行,也就是變成拖拽藍點,而紅點變成了參考基點,大家可以再好好看看上面那個動圖體會下。
  • 當然這樣還不夠,拖拽縮放的時候還有個問題,就是 top 和 left 值也會隨之改變,所以算完 scaleX 之后還需要對這兩個值進行更新,大家注意看上面那個動圖中的黑點就能體會到了。然后再提醒兩個點:
  • 就是縮放的時候中心點并不是在物體的中心,所以我們可以簡單的理解成單邊縮放;當然其實也可以沿著中心點縮放,只不過我們講解的是默認的形式;
  • 如果是豎直拉伸,只要把 x 換成 y,把寬度換成高度即可,如果是右下角那個控制點就把 xy 的代碼都加上即可;

這里也簡單貼下核心代碼????:

/**
 * 縮放當前選中物體
 * @param x 鼠標點 x
 * @param y 鼠標點 y
 * @param by 是否等比縮放,x | y | equally
 */
_scaleObject(x: number, y: number, by = 'equally') {
    let t = this._currentTransform, // 在鼠標按下的時候會記錄物體的狀態(tài)
        offset = this._offset, // 畫布偏移
        target: FabricObject = t.target;
    // 縮放基點:比如拖拽右邊中間的控制點,其實我們參考的變換基點是左邊中間的控制點
    let constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY);
    // 以物體變換中心為原點的鼠標點坐標值
    let localMouse = target.toLocalPoint(new Point(x - offset.left, y - offset.top), t.originX, t.originY);
    if (t.originX === 'right') {
        localMouse.x *= -1;
    }
    // 計算新的縮放值,以變換中心為原點,根據(jù)本地鼠標坐標點/原始寬度進行計算,重新設定物體縮放值
    let newScaleX = target.scaleX;
    if (by === 'x') {
        newScaleX = localMouse.x / (target.width + target.padding);
        target.set('scaleX', newScaleX);
    }
    // 如果是反向拉伸 x
    if (newScaleX < 0) {
        if (t.originX === 'left') t.originX = 'right';
        else if (t.originX === 'right') t.originX = 'left';
    }
    // 縮放會改變物體位置,所以要重新設置
    target.setPositionByOrigin(constraintPosition, t.originX, t.originY);
}

這個變換看起來麻煩點,所以我單獨寫了個小 demo,有興趣的可以點擊這個鏈接單獨查看。建議大家多動手試試,記住,最核心的要點就是:

我們不改變物體自身的寬高大小,也不改變物體的渲染方法,而只是改變?nèi)N變換的值。

可能有的同學還會問到上面的變換操作在鼠標移動時會不停的調(diào)用 renderAll 這個渲染函數(shù),性能是不是一般啊,尤其是當物體一多就更不咋地了?

那肯定是這樣的,在前端,不管啥東西,只要東西多了就會垮掉,比如數(shù)據(jù)多了就得分頁,虛擬滾動;元素多了能不繪制就不繪制。

當然在 canvas 中也有它的解法,比如緩存、分層、上 webgl 等等,這個在后續(xù)的優(yōu)化章節(jié)中會專門講到,所以敬請期待吧。不過還是要說一下,性能這東西,我覺得吧,一個普通頁面一般是很少會遇到的,所以等遇到了再去考慮解決和優(yōu)化也不遲,不然就屬于過度優(yōu)化了(沒必要),不過在 canvas 中性能是個比較普遍的問題,你很容易寫出卡卡的 canvas,所以我們還是有必要講一講的??。

小結(jié)

本個章節(jié)我們主要講的是物體的一些變換操作,本來感覺應該是件很難的事情,但是歸功于我們之前做了很好的結(jié)構(gòu)劃分,也就是將數(shù)據(jù)和渲染層分離,所以這一趴其實我們最核心的就是只改變了數(shù)據(jù),其它什么都沒變,這種感覺就像什么。。。那是數(shù)據(jù)驅(qū)動視圖的味道,哈哈??。扯犢子了,這里就簡單總結(jié)下三種基本的操作吧:

  • 拖拽,計算新的 top、left
  • 旋轉(zhuǎn),計算新的 angle
  • 縮放,計算新的 scaleX、scaleY

其實三種變換操作的本質(zhì)就是依托于鼠標坐標點的計算,啪??,沒了。

然后這里是簡版 fabric.js 的代碼鏈接,有興趣的可以看看。好啦,本次分享就到這里

canvas 中如何實現(xiàn)物體的框選(六)??

canvas 中如何實現(xiàn)物體的點選(五)??

canvas 中物體邊框和控制點的實現(xiàn)(四)??

實現(xiàn)一個輕量 fabric.js 系列三(物體基類)??

實現(xiàn)一個輕量 fabric.js 系列二(畫布初始化)??

實現(xiàn)一個輕量 fabric.js 系列一(摸透 canvas)??

更多關于JS前端canvas交互的資料請關注腳本之家其它相關文章!

相關文章

  • 微信小程序 轉(zhuǎn)發(fā)功能的實現(xiàn)

    微信小程序 轉(zhuǎn)發(fā)功能的實現(xiàn)

    這篇文章主要介紹了微信小程序 轉(zhuǎn)發(fā)功能的實現(xiàn)的相關資料,這里提供實現(xiàn)方法及實例幫助大家學習理解,需要的朋友可以參考下
    2017-08-08
  • 用JS創(chuàng)建一個錄屏功能

    用JS創(chuàng)建一個錄屏功能

    這篇文章主要介紹了利用JS創(chuàng)建一個錄屏功能,創(chuàng)建這個功能錢我們首先創(chuàng)建一個HTML文件,包含記錄按鈕和一個播放標簽,下面來看看創(chuàng)建的詳細過程
    2021-11-11
  • 微信小程序 <swiper-item>標簽傳入數(shù)據(jù)

    微信小程序 <swiper-item>標簽傳入數(shù)據(jù)

    這篇文章主要介紹了微信小程序 <swiper-item>標簽傳入數(shù)據(jù)的相關資料,需要的朋友可以參考下
    2017-05-05
  • JavaScript面試數(shù)組index和對象key問題詳解

    JavaScript面試數(shù)組index和對象key問題詳解

    這篇文章主要為大家介紹了JavaScript面試數(shù)組index和對象key問題詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • JavaScript數(shù)組去重方案

    JavaScript數(shù)組去重方案

    這篇文章主要介紹了JS數(shù)組方案,先總結(jié)一下我們數(shù)組的方法:pop、push、shift、unshift、slice、splice、sort、reverse、concat、join、indexOf、lastIndexOf、map、forEach,下面文章將詳細對他們做個詳細介紹,需要的朋友可以參考一下
    2021-09-09
  • apply?call?bind方法原理及使用場景示例詳解

    apply?call?bind方法原理及使用場景示例詳解

    這篇文章主要為大家介紹了apply&call&bind方法原理及使用場景示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-08-08
  • js中ES6繼承和ES5繼承之間的差別

    js中ES6繼承和ES5繼承之間的差別

    這篇文章主要介紹了ES6繼承和ES5繼承,以及兩個繼承之間的差別,文中運用示例以及代碼講解的非常清晰,感興趣的小伙伴可以參考一下
    2021-08-08
  • Tree Shaking實現(xiàn)方法指南

    Tree Shaking實現(xiàn)方法指南

    這篇文章主要為大家介紹了Tree Shaking實現(xiàn)方法指南,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-03-03
  • 微信小程序 LOL 英雄介紹開發(fā)實例

    微信小程序 LOL 英雄介紹開發(fā)實例

    這篇文章主要介紹了微信小程序 LOL 英雄介紹開發(fā)的相關資料,需要的朋友可以參考下
    2016-09-09
  • JavaScript阻止事件冒泡的方法

    JavaScript阻止事件冒泡的方法

    這篇文章主要介紹了基于JavaScript阻止事件冒泡,事件冒泡?開始時由最具體的元素接收,然后逐級向上傳播到到?DOM?最頂層節(jié)點。更多詳細內(nèi)容請需要的小伙伴參考下面文章的具體內(nèi)容希望對你有所幫助
    2021-12-12

最新評論