搜狗地圖發佈了新版的移動端地鐵圖,改版初衷是爲了用戶交互體驗的提高以及性能的改善。原版地鐵圖被用戶吐槽最多的是pinch縮放不流暢、無過渡動畫、拖拽邊界不合理等等,大致上都是交互體驗上的問題。實際上原版的問題不只僅存在於交互體驗上,源代碼也是一團糟:css
以上問題其實跟業務以及技術選型無關,能夠說是任何一個「歷史悠久」的項目都難以免的問題。針對以上問題的重構方案不是本文要闡述的核心,因此就一筆帶過。以下:html
本文重點討論搜狗地鐵圖對SVG的使用和優化方案。在討論技術細節以前,咱們先說明一下爲何要使用SVG。前端
不管是從業務類型仍是操做方式的角度考慮,地鐵圖均可以被視爲一種微型或者簡易的地圖。咱們能夠先回想一下手機地圖的一些基本操做,舉幾個簡單的例子:html5
以上幾種操做的技術實現須要遵循如下幾個基本原則:git
注:之因此將「矢量」加引號是由於地圖的實現包括柵格瓦片和矢量瓦片兩種不一樣的技術方案。顧名思義,矢量瓦片是真正意義上的矢量地圖,由OpenGL或者WebGL實現;而由柵格瓦片實現的地圖並非矢量的,縮放時會看到明顯的模糊效果,可是縮放動做完成後會展現對應等級的柵格圖片,也就是說縮放後的內容是清晰的,只是縮放過程當中存在模糊效果。隨着WebGL的普及,柵格瓦片技術逐漸退出了歷史舞臺。github
簡單歸納,地圖必須是:web
即便是柵格瓦片地圖,POI點也是動態繪製的,感興趣的讀者能夠自行查閱相關信息。瀏覽器
地鐵圖一樣如此,而Web展現矢量內容只有兩種方案:WebGL和SVG。雖然WebGL更富有視覺表現力,可是地鐵圖業務的體量較小,並無達到值得用WebGL實現的程度,因此SVG便成了惟一的選擇。前端工程師
舊版的搜狗地鐵圖雖然也是使用SVG繪製UI,可是並無將SVG的動態優點發揮出來,而是將其視爲靜態的圖片。圖1是舊版地鐵的DOM結構:架構
藍色框的svg是地鐵圖的UI內容,除了尺寸之外沒有任何其餘的屬性。紅色框是地鐵圖外層容器,能夠看到全部的偏移、縮放等交互都是藉由外層容器的transform實現。黑色框的各個DOM節點包括了定位、求路、信息氣泡等內容,這些DOM每每須要跟隨用戶操做被改動,並且某些操做可能須要同時操做多個DOM。
接下來咱們看看這樣的DOM結構存在什麼問題。
定位、求路、信息氣泡等內容是與地鐵圖強耦合的,假設我選中了某個地鐵站,如圖2:
紅色框內的信息氣泡對應到上圖的container3
節點,地鐵底圖對應container1
節點。若是此時咱們拖拽地鐵圖,底圖和信息氣泡都會隨着手勢而改變位置,那麼就須要同時改變container1
和container3
的位置。
咱們把一樣的問題帶入到求路,如圖3:
我並無畫出每一個UI對應的節點,由於實在是太多了。上圖中包括了2個轉乘節點、2個起終節點和3個氣泡節點,拖拽過程當中這7個DOM節點所有須要被操做。而且不只僅是改寫DOM屬性那麼簡單,而是須要先獲取每一個節點的座標而後再進行計算,而咱們都知道,獲取DOM的offset是很是消耗性能的。此外,求路狀態下的地鐵圖必須縮放到完整展現求路路線的等級,那麼就須要計算求路路線的輪廓尺寸,其中也會涉及到大量的計算和DOM操做。
其實拖拽是很是基本的操做,若是是縮放呢?拋開大量的計算和DOM操做不談,從視覺上表現如圖4所示:
爲何氣泡和起終點等節點沒有同比例縮放?由於這些節點不是矢量的SVG,縮放會失真。若是想獲得「矢量」的縮放效果只能從新計算這些節點的尺寸,這樣的代價太大了。因此咱們不得不忍受這些問題。
總結以上的問題能夠歸納出兩點:
以上問題的癥結能夠概括爲:
container1
實現,座標的獲取只能藉助於常規的DOM API;簡單來說,舊版地鐵圖的核心問題是DOM結構不合理,而且沒有把SVG的動態特性發揮出來。
重構後的DOM結構如圖5所示:
handler
節點負責直接響應手勢操做,拖拽、縮放等操做首先會改變handler
的transform
樣式;container
節點是svg容器,負責以瀏覽器窗口爲參考將地鐵圖居中;view
節點是全部與地鐵圖展現相關內容的容器,包括底圖、定位、氣泡、求路等等等等。同時,手勢操做最終會修改view
的transform屬性,以實現地鐵圖自己的縮放。以上說明可能有些難以理解,咱們用具象的圖形加以說明。分層的結構大體如圖6所示,從外到裏分別是handler/container/view:
此時若是用戶進行了手勢操做,以pan-拖動爲例:
panstart
事件觸發後記錄拖動的初始座標,不影響分層結構中的任何一層,也就是說不改變任何一層的任何屬性或樣式;panmove
事件頻繁觸發,即拖動過程當中,映射爲handler層transform
的改動,container和View無任何變化。以下圖7: 3. pancancel/panend
事件觸發後修正handler合理的偏移量(詳情請閱讀下文的邊界控制),同時將修正後的transform
屬性值換算爲view的transform
,最後將handler的transform
歸零。如圖8:
代碼以下:
1 /** 2 * @constant PrevOffset 前一次拖拽的座標偏移量 3 * @type {Object} 4 */ 5 const PrevOffset = { 6 x: undefined, 7 y: undefined 8 }; 9 10 EventRuntime.on('panstart panmove pancancel panend', e => { 11 e.preventDefault(); 12 e.srcEvent.stopPropagation(); 13 // panstart事件記錄初始座標 14 if (e.type === 'panstart') { 15 PrevOffset.x = e.deltaX; 16 PrevOffset.y = e.deltaY; 17 } else if (e.type === 'panmove') { 18 // handler位移設置增量 19 subway.setTranslate(e.deltaX - PrevOffset.x, e.deltaY - PrevOffset.y); 20 PrevOffset.x = e.deltaX; 21 PrevOffset.y = e.deltaY; 22 } else { 23 // 拖拽結束後換算hander和view的transform,同時修正合理偏移量 24 subway.adjustTransform('translate'); 25 } 26 });
分層結構中三者的做用能夠簡單歸納爲:
可能你會疑問爲何不直接改變view的transform
?額外加一層handler的做用是什麼?在回答這個問題以前咱們不妨先思考一下若是直接改變view的transform
來響應拖動和縮放會有哪些不足。
動畫是前端交互中的重點,爲了提供順暢的操做體驗,最典型的優化動畫方向是:
搜狗地鐵圖有三種基本的操做:
1) 點擊某個站點,將此站點居中,期間有緩動動畫以下圖9:
2) 拖動到地鐵圖邊界後,拖動結束(即手指離開屏幕)後須要修正拖動邊界,不然會停留在拖動結束的狀態可能形成大面積空白。這種修正相似Safari IOS的橡皮筋效果。修正過程當中有緩動動畫以下圖10:
3) 與拖動相似,縮放一樣有邊界限制,不然會無限制的放大/縮小。修正縮放邊界期間有緩動動畫以下圖11:
GIF圖片表現力有限,不能表現完美的效果。體驗真實的效果請下載搜狗地圖APP進入到地鐵圖查看。
回到最初的問題:若是直接改變view的transform
如何實現緩動效果?
這裏須要註明兩個前提知識點:
transform
是一個屬性,與CSS的transform
是兩個不一樣的概念,二者使用的座標體系有必定差別;transition
的屬性,也就是說SVG沒有原生支持過渡動畫的功能。關於SVG transform的詳細知識能夠參考理解SVG transform座標變換。
因此若是咱們在view的transform
上下功夫實現緩動動畫的話,只能經過JS結合緩動公式和requestAnimationFrame
計算每一幀的SVGtransform
值,或者使用第三方現有的動畫工具庫,好比TweenJS。transform
的計算很是複雜,尤爲是同時存在scale
和transiton
的場景下。既然CSS的transiton
可使用瀏覽器提供的緩動動畫,那咱們爲何不把複雜的工做交給瀏覽器呢?transiton
做爲偏移、縮放的緩動動畫媒介必須搭配CSS的transform
,可是咱們不能直接經過view的style修改transform
。緣由有二:
transform
和SVG的transform
不能等同;transform
進行邊界控制(下文詳述),也就是說偏移和縮放的效果最終須要換算爲SVG的transform
但在動畫執行期間不能修改。那麼咱們便得出了handler存在必要性的證實之一,也就是優化動畫的第一條:緩動。接下來咱們嘗試進一步優化動畫的性能。
咱們都知道CSS的3Dtransform
能夠強制啓用GPU加速以優化動畫的表現,天然會想到SVG可不可使用GPU加速呢?很惋惜,答案是否認的。SVG是一種表現2D矢量圖形的技術,它在設計之初便沒有考慮3D的場景,因此SVG並無3Dtransform
,也沒法藉助GPU對動畫進行加速。
那麼咱們便得出了handler存在必要性的第二個證實:GPU加速。
其實業內對於藉助GPU加速動畫的方案褒貶不一,即使是啓用GPU加速也有方案的優劣。咱們這次重構只是第一步,後續仍舊會不斷探索進一步的優化方案。
SVG沒有transform-origin
概念,transform
的原點永遠都是自身的左上角,即(0,0)
。
你們能夠想象一下在手機上用兩根手指縮放地鐵圖的場景,咱們須要知道地鐵圖應該以屏幕上的哪一點做爲中心進行縮放。從技術角度來說,咱們須要知道兩個觸控點的中心位置座標。不管是IOS系統原生的gesture
事件,仍是經過touch事件模擬的pinch
事件(如HammerJS)使用的都是瀏覽器座標系,也就是CSS座標系。
若是必定要把中心點座標映射到SVG座標系,則須要必定的計算量(下文詳述)。在縮放操做過程當中須要頻繁地改變被縮放DOM的transform
從而引發重繪(re-render),這期間瀏覽器自己就進行着大量計算,因此在應用程序層面應該儘量減小計算量。
關於重繪和重排,能夠參考瀏覽器的重繪與重排。
這也是handler節點存在必要性的第三個證實:減輕計算量。
有了handler節點的輔助,縮放操做進行中(請注意是進行中,不包括起始和結束時刻)惟一的計算即是handler的transform
,無需將其轉換爲SVG的transform
。固然,換算仍然是必須的,可是咱們將其推遲到縮放操做結束以後進行,這樣即可以在一次完整的操做流程中只進行一次換算工做,大大減小了整體的計算量。具體的換算公式下文詳述。
上文並無過多的描述container節點,由於它的做用很是簡單。container做爲svg的容器,同時在初始化時以瀏覽器窗口爲參考將地鐵圖居中。以下圖12所示:
container節點的高寬均爲2000,決定這個數字的惟一原則是:只要比view節點的尺寸大便可。因此咱們設置了一個比較大的值。container節點的尺寸會影響它自身的left
和top
,上圖中紅色標註是container節點居中的偏移量:
1 Offset.x = (container.width - window.innerWidth)/2; 2 Offset.y = (container.height - window.innerHeight)/2;
那麼container節點的CSS即是:
1 container.style.cssText = [ 2 'postion: absolute;', 3 `left: -${Offset.x};`, 4 `top: -${Offset.y};` 5 ].join('');
transform是應用到view節點,邊界控制一樣是以view節點的尺寸爲計算因子。因此,在初始化以後container再也不進行任何改動,它的做用至此便徹底體現了。
transform是應用到view節點,邊界控制一樣是以view節點的尺寸爲計算因子。因此,在初始化以後container再也不進行任何改動,它的做用至此便徹底體現了。
可能你會冒出這樣一個疑問:handler使用的是CSS的座標體系,那麼它的transform
要換算成SVG座標的計算必定很複雜吧?這個問題的有兩個難點:
transform-origin
的概念和功能,可是咱們須要藉助CSS的transform-origin
計算縮放中心,這進一步複雜化了換算邏輯。若是SVG設置了viewBox
屬性,那麼它所使用的座標系便不一樣於CSS座標系。此外,SVG的preserveAspectRatio
也會影響座標系的細節。這兩個屬性在實現SVG縮放時很是關鍵,但搜狗地鐵圖並無藉助viewBox
實現縮放,而是將所有的展現交給了view節點的transform
,必定程度上減輕了CSS和SVG座標差別性形成的計算複雜度。同時,咱們將preserveAspectRatio
屬性值設置爲"xMinYMin meet"
,即強制寬高等比例縮放。
遠於SVG座標系的更多細節能夠參考理解SVG座標系和變換:視窗,viewBox和preserveAspectRatio
剩下的問題就是如何將CSS的transform-origin
換算成SVG的transform
了。
transform-origin
」SVG與CSStransform
的相同點是:二者都是以自身爲變換座標系。但SVG的transform
原點不能改變,永遠都是自身的左上角,即(0,0)
。
那麼SVG如何實現相似CSStransform-origin
效果呢?
假設我想讓SVG以點(50,30)
爲原點放大1.5倍,我須要按照下述順序依次對SVG進行變換:translate(50 30)
->scale(1.5 1.5)
-> translate(-50 -30)
。先將SVG偏移到點(50,30)
;而後再將SVG放大1.5倍(請謹記SVGtransform
的原點是自身的左上角);最後再將SVG反向偏移(50,30)
。具體變換過程能夠參考圖13:
更多技術細節請參考這篇文章。
SVG的transform
屬性值爲translate(50 30) scale(1.5 1.5) translate(-50 -30)
。因爲地鐵圖的操做頻繁是,涉及到大量變換,因此咱們用matrix表示。以上的transform
屬性值換算爲matrix表示爲matrix(1.5 0 0 1.5 ${(1-1.5)*50} ${(1-1.5)*30})
。
至此咱們便總結出SVG以點(ox,oy)
爲原點進行縮放的transform
計算公式:
transform = matrix(sx 0 0 sy (1-sx)*ox (1-sy)*oy)
接下來咱們根據以上的前提知識點推導出具體的換算公式。
爲了更清晰地推算換算公式,咱們假設在縮放地鐵圖以前已經有了必定的偏移量和縮放比例,以下圖14:
假設此時View節點的transform
屬性值爲matrix(scale 0 0 scale dx dy)
,簡化爲:
View.scale
- view節點的初始縮放值;View.dx
&View.dy
- View節點的初始偏移量。由於咱們爲SVG設置了
preserveAspectRatio="xMinYMin meet"
,即強制寬高等比例縮放,因此scaleX = scaleY,咱們統一使用scale表示。
同時咱們將handler的樣式設置爲:
1 `transform: translate3d(${dx}, ${dy}, 0px) scale(${scale});` 2 `transform-origin: ${ox} ${oy} 0px;`
即:
Handler.dx
&Handler.dy
- handler節點的偏移量;Handler.scale
- handler節點的縮放值;Handler.
ox&Handler.oy
- handler節點的transform-origin
座標。須要特別注意的一點是,handler節點的transform
咱們並未使用matrix表示,而是直接用translate3d
和scale
。非matrix表示transform
時的變換順序很是重要,按照從左往右的順序後面的變換是之前面的變換爲基礎。也就是說,handler節點的transform
是先進行translate3d
-偏移變換,而後在偏移以後的狀態基礎上再進行scale
-縮放變換。
另外還有一個重要前提:目前版本咱們將縮放和拖動操做割裂開,同一時間只能進行縮放或者拖動操做。也就是說,縮放操做只改變Handler.scale和Handler.ox&Handler.oy,拖動操做只改變Handler.dx&Handler.dy。後續版本會探索將兩種操做耦合的可行性方案。
接下來咱們詳細講解一下scale的換算公式,你們請先仔細研究下圖15所示的縮放狀態
transform
屬性值爲matrix(scale 0 0 scale dx dy)
;此時對應的DOM狀態以下圖16所示
(50px,40px)
爲原點縮放了1.2倍;transform="matrix(1.1 0 0 1.1 194 75)"
,即縮放了1.1倍,X軸偏移194,Y軸偏移75。接下來要作的事情是吧Handler的transform
以及transform-origin
換算爲SVG的transform
,而後將Handler節點transform
和transform-origin
歸零。換算公式以下:
1 View.scale = View.scale * Handler.scale; 2 View.dx = View.dx + (1 - Handler.scale)*(Handler.ox + Offset.x - View.dx); 3 View.dy = View.dy + (1 - Handler.scale)*(Handler.oy + Offset.y - View.dy);
公式的推導過程並不複雜,由於咱們並無改變SVG的Viewbox,因此其座標系與CSS座標系並沒有二致。因此只須要將場景代入CSS座標系,同時將transform-origin
設置爲(0,0)
,在此前提下進行推導公式便很是簡單了。
將CSS的transform-origin設置爲’0,0’後,transform的規則與SVG的transform便徹底同樣了。若是你熟悉CSS的transform,SVG的transform便不會有任何問題。由於CSS的transform屬性自己就是從SVG的transform借鑑而來,只是加入了transform-origin這個語法糖。
顧名思義,邊界控制的做用是限制地鐵圖的可操做邊界,包括拖拽邊界和縮放邊界。拖拽邊界指的是地鐵圖上下左右四個方向上的可拖動的最大距離。縮放邊界指的是地鐵圖可被縮放的最大和最小比例。兩種邊界控制的具體的交互表現可參考上文「緩動動畫」一節的圖10和圖11。
從圖12很容易得出初始的拖拽邊界,請參考如下僞代碼:
ViewBox <- 計算View的座標和尺寸 Viewport <- 獲取瀏覽器的尺寸 Offset <- 計算Container相對瀏覽器的偏移量 THEN 往右拖動的最大距離MaxX = Offset.x - BBox.x 往左拖動的最大距離MinX = ViewBox.width-(Offset.x - BBox.x + Viewport.width) 往下拖動的最大距離MaxY = Offset.y - BBox.y 往上拖動的最大距離MinY = ViewBox.height-(Offset.y - BBox.y + Viewport.height)
注意,由於拖拽的邊界最終映射到translate
上,因此左拖動邊界和上拖動邊界的值是上述僞代碼所計算出來結果的相反數,即始終爲負數或者0。
隨後用戶進行拖拽和縮放操做後,拖拽邊界便隨之動態變化。計算動態拖拽邊界的時候須要考慮兩點:
transform-origin
,是重要的計算因子;將以上規則帶入計算,僞代碼以下:
Viewport <- 獲取瀏覽器的尺寸 TransformOrigin <- transform-origin的值 Scale <- 縮放比例 Translate <- 偏移量 THEN 往右拖動的最大距離MaxX = Prev_MaxX*Scale + TransformOrigin.x*(Scale-1) - Translate.dx; 往左拖動的最大距離MinX = Prev_MinX*Scale - (Viewport.width-TransformOrigin.x)*(Scale-1) - Translate.dx; 往下拖動的最大距離MaxY = Prev_MaxY*Scale + TransformOrigin.y*(Scale-1) - Translate.dy; 往上拖動的最大距離MinY = Prev_MinY*Scale - (Viewport.height-TransformOrigin.y)*(Scale-1) - Translate.dy; THEN 修正 MinX: MinX<MaxX?MinX:Math.min(0,MinX) MaxX: MaxX>MinX?MaxX:Math.max(1,MaxX) MinY: MinY<MaxY?MinY:Math.min(0,MinY) MaxY: MaxY>MinY?MaxY:Math.max(1,MaxY)
這些公式的推導過程說複雜也複雜,說簡單其實也很簡單。道理與上文的scale換算同樣,由於SVG的viewBox沒有改變,因此只需將SVG帶入CSS座標系便可迎刃而解。篇幅所限,具體的推導過程便再也不贅述。
與拖拽邊界不一樣的是,縮放邊界是固定的,一經初始化便不會再改動。具體如何控制縮放的邊界其實並無統一的方案,不一樣的團隊可能有不一樣的看法,好比高德和百度的地鐵圖最小縮放比例小仍然沒法展現底圖的全貌。搜狗地鐵圖在評審和開發過程當中有過幾回商討,最終定下的方案是:
也就是說,不一樣城市地鐵圖的最小縮放比例是不一樣的,由於每一個城市的地鐵線路個數、長度均有所差別,須要動態計算。計算的方法很簡單,惟一須要注意的是必定要將瀏覽器的寬高比做爲計算的因子。請參考如下僞代碼:
ViewBox <- 計算View的座標和尺寸 Viewport <- 獲取瀏覽器的尺寸 AspectRatioOfWindow <- 瀏覽器的寬高比 THEN 最大縮放比例 = 1.5 最小縮放比例 = ViewBox.width/ViewBox.height < AspectRatioOfWindow ? Viewport.height/ViewBox.height : Viewport.width/ViewBox.width;
其實我我的以爲高德和百度的方案更佳,由於手機屏幕尺寸比較小,即便展現地鐵全貌也看不清楚細節,索性不如將最小比例寫死爲一個可以看清楚細節的臨界值。這樣不只能減小計算量,並且從總體交互上也比較人性化。可是胳膊擰不過大腿,最終仍是信了PM的邪。。。
爲何要把這一條單拎出來說,是想提醒一下你們千萬不要一味的追求所謂的流行技術和框架。我曾經見過不少前端工程師在介紹React/Vue的優勢時必定要唾棄直接操做DOM和jQuery/PrototypeJS等「老傢伙們」。不能否認React/Vue確實很大程度上解放了生產力,可是並不是全部的場景均適合使用它們,好比地鐵圖的手勢操做。地鐵圖響應手勢操做的過程當中須要頻繁的改變底圖的transform
,那麼請你們思考如下兩種方式哪一個性能更好:
v-bind:transform="transform"
;this.$refs.handler.cssText=transform
第二種實現是否是Vue的「反模式」?仁者見仁。可是從實際效果來看第二種具備絕對的性能優點,其背後的道理很簡單。對於手勢操做這種幾乎每一幀都須要響應的場景來講,邏輯越少越好,而Vue在改變DOM以前須要處理一系列複雜的邏輯,與直接操做DOM相比,性能孰好孰壞顯而易見。
Vue的動態綁定把DOM操做封裝在框架內部,高內聚的框架讓開發者無需關心具體實現,可是基本的原理仍然未脫離DOM這一核心因素。
首先加載主邏輯文件index.js,而後index.js中的邏輯獲取url的城市參數名稱,隨後異步加載對應城市的數據文件,加載完成後進行解析和渲染。以下圖:
這種流程對於常規的web站點沒有任何問題,由於常規的web網站全部城市共用一套代碼,只能從參數區分城市名稱。可是Hybrid地鐵圖使用的是離線包而不是web站點,每一個城市均打包爲對應名稱的離線包,好比北京的源碼被打包爲beijing.zip。也就是說,每一個城市的代碼是互不影響的,這是優化的重要前提。
針對離線包的構建流程中加入額外的功能,即把每一個城市的數據js引用在構建階段注入到index.html中。以下:
這樣能夠實現數據文件的同步加載,與舊版的對比節省了如下時間:
須要說明的是,雖然單純加載數據文件,不管是同步仍是異步方式,二者的時間徹底一致。可是若是按照本來的異步加載流程,數據文件便沒法利用瀏覽器http並行加載的優點,即便這個時間可能微乎其微。
歷史緣由,地鐵數據被製備爲XML格式的字符串,解析數據須要先將其轉換爲XML對象,而後再轉換爲JSON格式。且全部的解析工做均在客戶端瀏覽器執行,以下:
將數據的解析工做提早到源碼構建階段,客戶端直接接觸的是解析後的JSON格式數據,減小客戶端負載和用戶的等待時間。以下:
此外,舊版的解析數據中存在大量冗餘的字段,本次重構將這些冗餘字段刪除,進一步減少了文件體積。
以北京的地鐵數據爲例,分別對比優化先後的數據文件的體積以及解析所消耗的時間。
1> 文件體積
- | XML | JSON-未優化 | JSON-優化 |
未壓縮 | 145KB | 288KB | 149KB |
壓縮 | 30KB | 58KB | 31KB |
結論:單純從文件體積衡量,優化先後的差距幾乎能夠忽略。
2> 解析時間
設備信息:
模擬環境:Chrome
測試結果(取十次平均值):
設備性能 | 原始 | 慢4倍 | 慢6倍 |
解析時間-優化前 | 45.6ms | 281.2ms | 294.3ms |
解析時間-優化後 | 0 | 0 | 0 |
結論:優化後無需解析,直接進行底圖渲染。設備性能越差,優化先後的對比越明顯。
技術棧自己並沒有好壞之分,優劣體如今與業務的契合度上。老版本搜狗地鐵圖的問題核心並不是在於技術棧的不合理,甚至以當時開發初版地鐵圖的時間節點來看,其技術棧算得上優秀。技術架構和實現方式上的混亂是形成老版本地鐵圖性能和交互問題的根本。
優化技術架構是重構的第一步,但完成架構的升級只算完成了一半。特殊的運行方式(離線包)決定了不能將地鐵圖等同爲常規的Web站點,這種特殊性也提供了進一步優化的空間,這是重構工做的第二步。因此在本次地鐵圖重構項目過程當中能夠提煉出重構的兩個基本點: