大前端時代下寨辩,面向組件,面向數(shù)據(jù)編程已家常便飯歼冰,但是作為一個合格的程序員靡狞,優(yōu)秀的邏輯思維和抽象能力是其最基本的素質(zhì)。代碼只是實現(xiàn)過程和編程功底的體現(xiàn)隔嫡,更重要的是分析問題甸怕,建模實力甘穿,解決問題,才能實現(xiàn)技術(shù)創(chuàng)新梢杭,引領(lǐng)業(yè)務(wù)温兼,改變世界
點擊查看本文的第一個案例
一個優(yōu)秀的程序員應(yīng)該做到:
1. 觀察歸納需求,抽象需求
2. 設(shè)計算法將需求數(shù)學(xué)化武契,建立合理的數(shù)學(xué)模型
3. 扎實的編程功底實現(xiàn)模型
我們首先歸納的應(yīng)該有以下的細節(jié):
1. 整個頁面的動效交互只有滾動條觸發(fā)
2. 兩點之間是任意曲線募判,曲線的運動學(xué)是整個曲線方程的子集軌跡,也就是局部曲線
3. 由于滾動條有上下滾動兩個方向吝羞,所以曲線的運動軌跡的長度隨滾動條的高度成正比兰伤,即向下滾動,軌跡變長钧排,向上滾動敦腔,軌跡變短,注意一點恨溜,軌跡長度的變化方向符衔,和原曲線的方向一致
4. 飛機沿著曲線的運動軌跡而做曲線運動,注意一點糟袁,由于飛機是svg圖片判族,而其運動軌跡是任意曲線,那么飛機的方向也必須跟隨軌跡運動而實時變化项戴,這一點非常重要形帮!
5. 飛機在兩點之間運動時,在該曲線的起點到1/2長度之間周叮,飛機逐漸變大辩撑,在運動到超過1/2長度和自身長度之間,飛機又逐漸變小
6. 在曲線運動過程中仿耽,整個地圖也發(fā)生運動合冀,且地圖的運動軌跡和當前時刻曲線的運動方向一致
7. 在滾動過程中,當左側(cè)圖文面板里出現(xiàn)圖片项贺,那么以飛機為三角形定點君躺,左側(cè)圖片的上下兩個點為三角形底邊,作一個三角形光束照射在圖片上开缎,并且隨著底邊點的位置改變定點角度也隨著變化
以上細節(jié)棕叫,是一個程序員最基本的觀察力和對需求的歸納能力,那么接下來奕删,開始動手編程谍珊?那是新手,接下來要做的應(yīng)該是逐一分析技術(shù)算法細節(jié),也是培養(yǎng)我們的建模能力
下面開始算法原理的分析:
1. 前端技術(shù)棧很明顯砌滞,使用原生js和canvas技術(shù)就可以實現(xiàn)侮邀,這一點沒什么可以說的
2. 曲線的制作可以利用三次貝塞爾方程(一切的計算都源于此方程)
通過此方程,我們可以方便的求其偏導(dǎo)數(shù)以及任意時刻t對應(yīng)的x和y值
3. 兩點間一曲線贝润,把一條曲線和4個點(繪制三次貝塞爾)抽象成一個類绊茧,這非常容易想到,而這個類里應(yīng)封裝基本的繪制方法打掘,包含曲線华畏、圓形點、飛機圖形尊蚁、三角形等亡笑,還應(yīng)封裝計算方法,包含根據(jù)任意時刻t計算相應(yīng)的x和y值横朋、根據(jù)任意x或y值反解出曲線對應(yīng)的t值仑乌、求x和y的偏導(dǎo)數(shù)、求曲線上任意一點的斜率琴锭、求曲線的總長度
以上的分析依然很簡單晰甚,只不過是oop和canvas的api的使用,接下來實際說明為什么需要封裝這些計算方法
4. 繪制局部貝塞爾曲線:
我們知道使用canvas的api里bezierCurveTo(p0,p1,p2,p3)很容易畫出一條完整的貝塞爾曲線决帖,但是我們需要模擬的運動學(xué)厕九,是沿著曲線做曲線運動,也就是需要繪制局部曲線地回,而canvas是沒有給我們提供繪制任意局部曲線的api的扁远,所以單純依賴api是走不通的,我們需要從本質(zhì)原理出發(fā)推理
我們知道兩點可以組成一條直線刻像,那么曲線呢穿香?而任意一條線上有多少個點?不妨假設(shè)有n個點绎速,那么如果這n個點的坐標不是線性的,那么將這n個點連接起來焙蚓,就是一條完整的曲線纹冤,如果這n個點的坐標滿足貝塞爾方程,那么組合起來就是一條完整的貝塞爾曲線
分析到這里购公,我們可以抽象出我們只要選擇m個點(0=<m<=n)萌京,將這m個點連接起來,就構(gòu)成了局部貝塞爾曲線(請思考為什么不只選取運動起點和運動終點來連線)
而新問題緊接著產(chǎn)生宏浩,我們設(shè)滾動條滾動的距離為a知残,怎么去計算出a所對應(yīng)的方程的x和y?
因為我們?yōu)g覽器的屏幕是笛卡爾坐標系比庄,我們繪制任意一個點需要知道點的x坐標和y坐標求妹,所以我們很容易想到不妨用a去映射方程終點p3對應(yīng)的x乏盐,那么我們只需要求出p3的y值即可完成終點的繪制。觀察方程制恍,我們不能直接利用x去求y父能,需要先求出p3.x對應(yīng)的t值,才能用t去計算p3.y
已知任意時刻t的方程净神,P0何吝,P1,P2鹃唯,P3為四個點爱榕,其中P0和P3是起點和終點,P1和P2是控制點
我們已知曲線終點的Px坡慌,求Py黔酥,那么兩邊除以t的三次方,整理得:
設(shè) m = (1 - t) / t八匠,即可以整理出:
我們知道這是一個標準的三次方程絮爷,利用三次求根公式可以解出m進而求出t,那么帶入t和另外三個點的y值就可以求出p3的y值
但是這并不是一個非常好的做法梨树,因為考慮我們繪制的是任意曲線坑夯,方程圖像如果存在以下這種情況:
那么我們很難控制曲線到底應(yīng)該畫到第一個y值,還是第二個y值抡四,所以綜上分析柜蜈,a去映射x求t來解y,會存在多解影響笛卡爾坐標繪制指巡,故而正確的做法應(yīng)該是用a去映射t淑履,用t來解出x和y
那么問題繼而轉(zhuǎn)化為,a怎么去映射t藻雪?
我們可以容易的想到如果已知曲線的總長度L秘噪,用a和L的比率去計算局部dl,我們假設(shè)長度為L的曲線對應(yīng)可視高度為H的值(當盒子完全被滾動條卷入勉耀,也就證明線走完了)指煎,可以得到dl = L / H * a,則此時此刻的t = dl / L (t 屬于[0便斥,1])
那么問題就轉(zhuǎn)化為需要先求出任意曲線的長度L至壤?
我們知道路程 = 速度 * 時間,基于微積分的思想枢纠,我們假設(shè)某個很小的時刻dt像街,存在速度dv,那么ds = dv * dt,最后我們對所有dt對應(yīng)的ds進行求和镰绎,可以逼近曲線的總長度L(我們假設(shè)將曲線S劃分為N段)
問題即又轉(zhuǎn)化為怎么求速度脓斩?(這里指的是合速度)
很明顯,我們需要對曲線方程對t進行求導(dǎo)跟狱,即得到其速度的方程式
求導(dǎo)過程非常簡單(請復(fù)習(xí)高中復(fù)合函數(shù)求導(dǎo))
我們帶入t求出兩個方向的速度俭厚,很顯然,一個是沿著x方向的速度vx驶臊,另一個是沿著y方向的速度vy挪挤,我們需要計算出二者產(chǎn)生的合力的速度v即可
根據(jù)平行四邊形法則,我們知道v = (vx * vx + vy * vy) ^ 1/2关翎,現(xiàn)在我們終于可以計算出曲線的長度L = ∑ v * dt扛门,而a和H已知(單純的網(wǎng)頁dom計算,不題)纵寝,我們就可以得到dl = L / H * a论寨,最后得到t = dl / L,完成了a和t的映射關(guān)系
現(xiàn)在我們用a映射出t爽茴,并且可以保證t在0到1之間葬凳,根據(jù)t解出真正的x和y值,至此室奏,我們終于可以畫出局部曲線了
5. 局部曲線長度的動效變化:
當我們建立如何畫局部曲線后火焰,將要解決隨著滾動條向上或向下滾動時候,局部曲線的長度變化過程‰誓現(xiàn)在我們已經(jīng)得到任一點的dt和該dt對應(yīng)的x和y值昌简,很顯然想到利用canvas的LineTo(x, y)進行繪制連線即可
但是問題出現(xiàn)了,如果我們直接一步LineTo(x绒怨,y)到終點纯赎,會出現(xiàn)以下情況:
顯然,我們不能一步就繪制起點和終點
那么問題又回到了繪制局部曲線的理論南蹂,我們將起點(dt = 0)和終點( dt = t)之間構(gòu)造出N個dt犬金,利用微分的思想繪制可以很方便的繪制出這段曲線
這段代碼請自行感悟
至此我們只要知道a對應(yīng)的dt,利用微分思想六剥,就可以繪制任意方向的局部曲線動效
6. 飛機做曲線運動晚顷,且隨著曲線方向?qū)崟r旋轉(zhuǎn)方向:
我們飛機是svg矢量圖,利用canvas的drawImage非常容易繪制出飛機仗考,但是問題是計算機是不知道飛機的機頭要跟隨其運動的方向旋繞,這是我們的所常識決定的词爬,所以我們要利用計算機仿真這個常識
我們觀察很容易發(fā)現(xiàn)飛機的旋轉(zhuǎn)是沿著運動軌跡的切線方向秃嗜,其正負值由運動方向決定,如果我們知道任意時刻dt的曲線切線與水平軸的夾角d,那么利用canvas的rotate(d)就可以改變飛機的旋轉(zhuǎn)角度锅锨,并且保證其旋轉(zhuǎn)的角度和曲線相切叽赊,就可以模仿這個常識
問題就抽象出計算曲線任意時刻dt的切線與水平軸的夾角?
計算切線必搞,其實就是求導(dǎo)必指,而我們之前就已經(jīng)計算好dt對應(yīng)的兩個方向的偏導(dǎo)數(shù)方程式,這里只需要計算兩個運動方向的夾角即可恕洲,我們利用arctan vy / vx 就可以計算出該時刻切線與水平軸的夾角
7. 飛機在運動過程中的大小變化
這一步就非常簡單了塔橡,我們已知飛機的運動過程由t決定,而t在[0霜第,1]之間葛家,我們利用canvas的scale讓飛機運動在[0,1/2 * L]的時候從0到1放大泌类,在運動的[1/2 * L癞谒,L]的時候從1到0縮小即可
那么問題就抽象成構(gòu)造一個分段函數(shù)去計算scale的值
利用初中數(shù)學(xué)很容易構(gòu)造出該分段函數(shù)
至此我們就可以用[0,1]去映射[0,1]變化到[1,0],完成飛機運動的縮放
8. 剩下基本是canvas的知識運用了刃榨,比如save和restore的結(jié)合弹砚,lineTo和moveTo的結(jié)合,漸變填充createLinearGradient等枢希,都非常簡單的純api調(diào)用桌吃,這里就不題了
現(xiàn)在,需求歸納和技術(shù)原理晴玖,實現(xiàn)難點读存,數(shù)學(xué)模型都已經(jīng)推理完,接下來的才到最后一步呕屎,編程實現(xiàn)让簿,而這一步單純考驗讀者原生JS,閉包秀睛,異步編程的功底尔当,相比之前的算法分析,顯得簡單的太多了
最后我們可以從這個案例總結(jié)出幾點:
1. 一個優(yōu)秀的程序員可以抽象出整個世界
2. 優(yōu)秀的數(shù)學(xué)功底蹂安,很多本質(zhì)上的原理椭迎,都是數(shù)學(xué)原理的實際運用
3. 算法永遠是編程的靈魂,編程只是實現(xiàn)田盈,算法是先驅(qū)畜号,也是核心大腦