核心思考
老實說,從現(xiàn)在的角度往回看,最初的時候我真的不知道自己想要做的是什么樣的東西 ……
我只是感覺,強烈地感覺,我能夠做出來點東西 ……
我花了一周多的時間做出來這個小工具:

所以,我得到的是一個什么樣的東西呢?(一本正經(jīng)地下個定義 …… )
從前端工程師的角度,它是一個以相對簡潔直觀的方式,輸入相對少的信息量,又能夠相對靠譜地適配大部分常規(guī)需求場景的網(wǎng)頁流程圖繪畫工具
求索
從使用者的角度,我想,判斷一個東西是否足夠有價值少不了考慮這兩個維度:是否足夠簡潔?是否足夠便利?
又因為當(dāng)前工具的目標(biāo)用戶是前端工程師,所以在便利性方面可以做少許妥協(xié) —— 即,簡化工程師向機器傳達思想的環(huán)節(jié)才是關(guān)鍵 ……
寫這么一個工具,有不同于寫一個常用的 table 組件的地方,因為 table 組件的“最少必要信息量”總是相對容易確定: 它需要一個表頭,以及每個表頭所對應(yīng)的每一行的值以及其它屬性
然而,繪畫一個流程圖的“最少必要信息量”是什么呢?一時間我想不明白。
在網(wǎng)絡(luò)上做檢索的過程中,我吸收了一些別人的思考:
1.chart1 - canvas

(因為中文網(wǎng)頁的抄襲版本太多,我搞不清楚作者是哪一位)
在這個例子中,我們可以直接告訴程序,這個節(jié)點放在哪里(行和列),以及線條應(yīng)該從哪里開始?如何走?又走到哪里?
// one step's info
{
type:'Step',
text:'業(yè)務(wù)名稱2',
name:'step_2_2', // origin
arrowArr:[
{
arrow:'drawBottomToLeft', // how to go
to:'step_3_2' // where to go
}
]
},
chart2 - flowChart.js
官網(wǎng):https://github.com/adrai/flowchart.js

// define every step
// &
// steps chain
const template = `
st=>start: Start|past:>http://www.google.com[blank]
e=>end: End:>http://www.google.com
op1=>operation: My Operation|past:$myFunction
op2=>operation: Stuff|current
st->op1(right)->cond
cond(yes, right)->c2
`
第二個例子看起來會簡便一些,定義節(jié)點和線條所需要的信息量會少很多,大部分設(shè)置是在基本配置里面統(tǒng)一完成的
3.chart3 - css
以上兩個流程圖都是通過 canvas 實現(xiàn)的,當(dāng)然還有 css 實現(xiàn)的:
源碼:https://codepen.io/demonwhite/pen/Gxqpzv

這是一個純 css 繪畫的流程圖,相比前面兩者,實現(xiàn)的難度會大一些,但是一旦實現(xiàn)了,想要進一步給節(jié)點添加效果卻非常方便。
結(jié)果
在本次求索的過程中,我漸漸得到了自己的思考:
首先,我需要一些前提條件:
1.不管節(jié)點(即“步驟”,下同)的大小如何,它所占據(jù)的區(qū)域是固定的
2.不追求適配所有情況,只滿足最基本的繪畫需求
3.允許程序完成大部分繪畫,然后由前端工程師手動微調(diào)
那么,畫一個流程圖的“最少必要信息量”可以是
1.基本參數(shù):節(jié)點默認寬高、節(jié)點之間的間隔
2.節(jié)點的內(nèi)容、類型、自定義屬性(寬高);
3.節(jié)點的位置(處于第幾行、第幾列);
4.線條連接哪兩個節(jié)點,以及線條的走向
落實到代碼層面是這樣的:
- 節(jié)點配置
const stepsOptions = [
[{ content: '步驟 1', width: 100 }],
[{ content: '步驟 2', }],
[{ content: '步驟 3-1', width: 100 }, { content: '步驟 3-2', }, { content: '步驟 3-3', width: 100 }], // 第四行
[{}, { content: '通過', }, { content: ' 不通過', }],
[{ content: '結(jié)束', }],
]
比如,第四行代碼的意思是說:流程圖第三行有三個節(jié)點,以及每個節(jié)點的內(nèi)容
- 線條模板
const linesTemplate = `
1_1-(down)->2_1-(downLeftDown)-z>3_1-(downRightDown)-z>4_2-(right)->4_3-(rightUpLeft)-c>1_1
4_3-(downLeft)->5_1
2_1-(downRightDown)-z>3_3
3_2-(down)->4_1
3_3-(left)->3_2
4_1-(down)->5_1`
比如,第二行代碼的意思是,第四行第三個節(jié)點與第五行第一個節(jié)點,這兩個兩個節(jié)點之間,以‘右-下’的走向繪畫一根線條

你可能會發(fā)現(xiàn),利用這樣的寫法,實際上需要傳入的信息量比上面提到的 flowChart.js 第三方代碼更多一些 ……
我是這么考慮的:
不應(yīng)該在實用性維度上做過分的讓步,在我使用 flowCart.js 的過程中,我發(fā)現(xiàn)它極度便利,但是與此同時,它在樣式自定義方面上又薄弱到了極點 —— 它可以非常自動地計算出線條的合適走向,與之相應(yīng)的不得不做出的犧牲就是,我們很難去干預(yù)線條的走向;甚至我們只能在非常小的程度上改動它的節(jié)點樣式 ……
以上,
確定了傳入的信息量,接下來就是給計算器編碼一套“規(guī)則”,讓它生產(chǎn)出來我們想要的東西。
實現(xiàn)的思路
關(guān)于節(jié)點與線條的定位
在這里,定位的核心是采用 css 的 position ,首先寫一個 div,定一下寬高,設(shè)置 position: relative;。然后給所有 steps, lines 設(shè)置 position: absolute;,丟進去。
結(jié)果就是,所謂的“定位”問題,實際上就變成了數(shù)學(xué)計算問題,計算每一個 step 或 line 出現(xiàn)的位置、寬高(關(guān)鍵是起點坐標(biāo)(x, y)的計算)
畫節(jié)點
1.通過每一行的步驟數(shù),以及每個步驟對應(yīng)的 index,結(jié)合 step 的 width, 以 x = 0 未中心做定位

2.利用黃金分割比,優(yōu)雅地計算出每一行距離頂端的高度 ……

畫線條
css 中的線條沒有我們想想中的那么好繪畫,借鑒了上文提到的 chart3,我采用 border 來繪畫線條
普通線條的或者有一個拐點的線條沒有特別的地方,主要是根據(jù)線條的走向然后利用 border-radius 讓拐點變得圓滑
.line {
&.downRight, &.leftUp {
border-radius: 0 0 0 5px;
border-left: $borderStyle;
border-bottom: $borderStyle;
}
}
然而需要拐彎兩次的 z 形線和 c 形線就需要一點小技巧
在 z-形線條的實現(xiàn)上,我直接取用中間黃金分割點,拆分成兩個 div:
z-線條(1)

z-線條(2)

c-形線條的難點在于,在確定了起止點之后,它還需要繞一個大彎,關(guān)鍵點就在于,線條既不能橫穿其它節(jié)點,又能夠自動在合適的地方轉(zhuǎn)彎。
這里的實現(xiàn)是這樣的,拿左邊部分的拐彎來說,先找出起止節(jié)點之間的所有行中最寬的一行,以這一行的最左作為起點,找出這一點與圖形左邊緣之間靠近節(jié)點的黃金分割點,作為我們 C 形線條的中轉(zhuǎn)站 ……
黃金分割點的好處是 —— “效果總不會差”
c-線條(1)

c-線條(2)

畫箭頭
最初的思路是,讓箭頭也獨立出來,計算它在頁面上的定位,然后把它放進去,結(jié)果發(fā)現(xiàn),想要讓它剛剛好出現(xiàn)在線條與邊框交錯的位置,理論上可行,可現(xiàn)實上會比較麻煩,而且效果非常脆弱,容易出現(xiàn)樣式 bug。
于是我把它做成了線條 div 的一個偽類,根據(jù)線條的方向可以確定它出現(xiàn)的位置和方位:
&.downRight {
// .arrow 用來判斷需不需要畫箭頭
// ::after 實現(xiàn)箭頭效果
&.arrow::after {
right: -$lineWidth;
@include arrowBottomOffset;
@include arrowRight;
}
}
一些思考
優(yōu)點
- 簡潔直觀的模板語法
1)節(jié)點屬性
根據(jù)節(jié)點在數(shù)據(jù)中的位置信息(第 i 個數(shù)組中的第 j 項),把節(jié)點映射到頁面上的位置(第 i 行第 j 排)
2)線條語法:
1_1-(downLeft)->2_1
相對簡潔直觀地表示起止節(jié)點以及線條的走向,開發(fā)者不用做任何數(shù)值計算就可以自動畫出一條符合大致預(yù)期的線條
- 樣式相對靈活,使用門檻相對低
與常規(guī)的流程圖繪畫(普遍 canvas 用得多)不同,這個工具采用的是 css 實現(xiàn),所以想要對流程圖結(jié)構(gòu)進行調(diào)整(比如修改樣式、添加樣式效果),實操起來會更為友好一些
從另外一個角度上考慮,大部分前端實際上是相對 canvas 更熟悉 css 的 ……
這個工具還提供了一個不錯的定位功能,可以快速定位到某一個步驟或線條所對應(yīng)的 div 數(shù)據(jù)

不足
- 報錯機制
目前 line / cline / zline 容易寫錯,還沒提供有效的錯誤識別
- 半自動
目前這個小工具只能做到半自動,比如需要手動補充 vue 文件結(jié)構(gòu),引入 css, 并對 css 基本數(shù)據(jù)進行調(diào)整
一些思緒
復(fù)雜并不意味著更優(yōu),最初在構(gòu)思 Z, C 形線條的實現(xiàn)的時候,考慮到許多特殊情況,比如,如何讓 Z 線條不穿過別人呢?如果我的 C 線條不想在外面拐彎,我想在某兩個步驟之間的縫隙穿過呢?
也就是說,最初我想要做的是兼容各種奇奇怪怪的畫線兼容情況,考慮得太復(fù)雜,實現(xiàn)太難,以至于,思考老是進入死胡同。
直到,某一刻恍然大悟:我干嘛要追求面面俱到,我只要處理好最常規(guī)最簡單的情況就行了吧,一時間,前途似乎敞開了 ……
謝嘉鋒 ---- 2020-06-20 19:33:58