摘要:對于能畫出貝塞爾曲線的,對已經求出的實例,執行,否則執行畫點的方法獲取配置中的,執行畫點。總結閱讀一遍后,這個庫說白就是基礎的事件操作貝塞爾曲線算法,但是,它內部的代碼格式非常清晰,細粒度代碼復用使得維護起來非常方便。
signature_pad一個基于Canvas的平滑手寫畫板工具介紹
實現手寫有多種方式。
一種比較容易做出的是對鼠標移動軌跡畫點,再將兩點之間以直線相連,最后再進行平滑處理,這種方案不需要什么算法支持,但同樣,它面對一個性能和美觀的抉擇,打的點多,密集,性能相對較低,但更加美觀,視覺上更平滑;
此處用的另一種方案,畫貝塞爾曲線。
由于canvas沒有默認的畫出貝塞爾曲線方法(感謝@madRain評論中更正)由于canvas并沒有提供根據初始和結束點計算出貝塞爾曲線控制點的API,因此這里使用了貝塞爾曲線的一系列算法,包括求控制點,求長度,計算當前點的大小,最后用canvas畫出每一個確定位置的點。
補充:個人認為,之所以不使用canvas提供的貝塞爾曲線API,是因為可以實時控制線條粗細(點的大小),在斜街的時候達到平滑的效果。參數及配置介紹
提供的可配置參數如下
export interface IOptions { // 點的大小(不是線條) dotSize?: number | (() => number); // 最粗的線條寬度 minWidth?: number; // 最細的線條寬度 maxWidth?: number; // 最小間隔距離(這個距離用貝塞爾曲線填充) minDistance?: number; // 背景色 backgroundColor?: string; // 筆顏色 penColor?: string; // 節流的間隔 throttle?: number; // 當前畫筆速度的計算率,默認0.7,意思就是 當前速度=當前實際速度*0.7+上一次速度*0.3 velocityFilterWeight?: number; // 初始回調 onBegin?: (event: MouseEvent | Touch) => void; // 結束回調 onEnd?: (event: MouseEvent | Touch) => void; }
這里要注意的是并沒有線條粗細這個選項,因為這里面的粗細不等線條都是通過一個個大小不同的點構造而成;
throttle這個配置可以參考loadsh或者underscore的_.throttle,功能一致,就是為了提高性能。
注冊事件在constructor內部,除了配置傳入的參數外,就是注冊事件。
這里優先使用了PointerEvent觸點事件,PointerEvent可以說是觸摸以及點擊事件的統一,如果設備支持,不需要再分別為mouse和touch寫兩套事件了。
狀態數據儲存狀態開關:
this._mouseButtonDown
當執行move事件時,會檢查此狀態,只有在true的情況下才會執行。
數據儲存分為2種格式:
pointGroup
這是當前筆畫的點的一個集合,內部儲存了當前筆畫的顏色color和所有的點points
this._data
這是一個儲存所有筆畫的棧,格式為[pointGroup, pointGroup, ..., pointGroup],當需要執行undo的時候,只需要刪除this._data中的最后一條數據。
事件流程及方法 mouseDown事件當鼠標(觸點)按下時,改變狀態this._mouseButtonDown = true,調用onBegin回調,創建當前筆畫的一個新的集合,然后對當前點執行更新。
mouseMove事件首先檢查this._mouseButtonDown狀態,對當前點執行更新。
mouseUp事件改變狀態this._mouseButtonDown = false;,調用onEnd回調,對當前點執行更新。
可以看到,上面的每一個事件內部都調用對當前點執行更新的方法。
_strokeUpdate——點的更新方法private _strokeUpdate(event: MouseEvent | Touch): void { // 獲取當前觸點的位置 const x = event.clientX; const y = event.clientY; // 創建點 const point = this._createPoint(x, y); // 調出最后一個點集 const lastPointGroup = this._data[this._data.length - 1]; // 獲取最后一個點集的點的數組 const lastPoints = lastPointGroup.points; // 如果存在上一個點,獲取上一個點 const lastPoint = lastPoints.length > 0 && lastPoints[lastPoints.length - 1]; // 判斷上一個點到當前點是否太近(也就是小于配置的最小間隔距離) const isLastPointTooClose = lastPoint ? point.distanceTo(lastPoint) <= this.minDistance : false; // 調出點集的顏色 const color = lastPointGroup.color; // Skip this point if it"s too close to the previous one // 存在上一個點但是太近,跳過,其余的執行 if (!lastPoint || !(lastPoint && isLastPointTooClose)) { // 向上一次的點數組中添加當前點,并且生成一個新的貝塞爾曲線實例 // 包括4個點 (初始點,2個控制點,結束點) // 初始寬度,最終寬度 const curve = this._addPoint(point); // 如果不存在lastPoint,即當前點是第一個點 if (!lastPoint) { // 畫一個點 this._drawDot({ color, point }); // 如果存在lastPoint 并且能形成一個貝塞爾曲線實例(3個點以上) } else if (curve) { // 畫出參數中curve實例中兩點之間的曲線 this._drawCurve({ color, curve }); } // 添加到當前筆畫的點數組 lastPoints.push({ time: point.time, x: point.x, y: point.y, }); } }
這個方法前面就是一系列判斷
判斷是否是第一個點
判斷是否能加入點的集合(滿足點的最小間隔)
判斷是否能畫出貝塞爾曲線(滿足至少3個點)
對于能畫出貝塞爾曲線的點,執行算法,求出Besier實例,包括4個點初始點,結束點,控制點1,控制點2以及當前曲線中線條的的初始寬度和結束寬度。
具體如何算的,請參考源碼src/bezier.ts和這篇文章。
對于能畫出貝塞爾曲線的,對已經求出的Bezier實例,執行this._drawCurve,否則執行this._drawDot
this._drawDot——畫點的方法獲取配置中的dotSize,執行canvas畫點。
this.__drawCurve——畫線的方法求出當前Bezier實例初始點和結束點之間的距離,這個距離不是直線距離,而是貝塞爾曲線距離。
對這個距離進行擴展,例如,計算得到距離為50,那就擴展為100個點,即我需要在50這個距離內畫出100個點;
這么做可以保證在正常或者稍微快速的書寫中,不出現斷層。
接著又是算法,目的是求出這個距離內的每一個點的大小,這是一個變化值,是的粗細變化更加平滑。
最后同樣是canvas畫點。
以上就是整個基本流程。
總結閱讀一遍后,這個庫說白就是基礎的事件操作+貝塞爾曲線算法,但是,它內部的代碼格式非常清晰,細粒度+代碼復用使得維護起來非常方便。
同時可以對貝塞爾曲線有一個更深層的了解(算法還是沒法手撕囧),但起碼有一個比較完整的思路;
一些可以借鑒的東西:
PointerEvent的優勢
canvas+貝塞爾曲線
節流throttle的寫法(參考源碼src/throttle.ts)
數據結構及實現undo的方案
導圖貝塞爾曲線算法資料:
https://medium.com/square-cor...
https://www.lemoda.net/maths/...
源碼閱讀專欄對一些中小型熱門項目進行源碼閱讀和分析,對其整體做出導圖,以便快速了解內部關系及執行順序。
當前源碼(帶注釋),以及更多源碼閱讀內容:https://github.com/stonehank/sourcecode-analysis,歡迎fork,求
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/104818.html
摘要:,算法就是這樣,那我們基于該算法再對現有代碼進行一次升級改造設置線條顏色在原有的基礎上,我們創建了一個變量用于保存之前事件中鼠標經過的點,根據該算法可知要繪制二次貝塞爾曲線起碼需要個點以上,因此我們只有在中的點數大于時才開始繪制。 背景概要 相信大家平時在學習canvas 或 項目開發中使用canvas的時候應該都遇到過這樣的需求:實現一個可以書寫的畫板小工具。 嗯,相信這對canva...
摘要:,算法就是這樣,那我們基于該算法再對現有代碼進行一次升級改造設置線條顏色在原有的基礎上,我們創建了一個變量用于保存之前事件中鼠標經過的點,根據該算法可知要繪制二次貝塞爾曲線起碼需要個點以上,因此我們只有在中的點數大于時才開始繪制。 背景概要 相信大家平時在學習canvas 或 項目開發中使用canvas的時候應該都遇到過這樣的需求:實現一個可以書寫的畫板小工具。 嗯,相信這對canva...
摘要:寫在最前本次分享一下在作者上一次失利即拿到畢業證第二天突然收到阿里社招面試通知失敗之后,通過分析自己的定位與實際情況,做出的未來一到兩年的規劃。在博客有一定曝光度的積累中,陸續收到了一些面試邀請,基本上是阿里的但是我知道我菜。。 寫在最前 本次分享一下在作者上一次失利即拿到畢業證第二天突然收到阿里社招面試通知失敗之后,通過分析自己的定位與實際情況,做出的未來一到兩年的規劃。以及本次社招...
閱讀 2947·2021-09-23 11:32
閱讀 2918·2021-09-22 15:12
閱讀 1708·2019-08-30 14:07
閱讀 3448·2019-08-29 16:59
閱讀 1640·2019-08-29 11:11
閱讀 2307·2019-08-26 13:50
閱讀 2426·2019-08-26 13:49
閱讀 2621·2019-08-26 11:49