據(jù)驅(qū)動動態(tài)渲染)
我最早接觸 Mapbox 的線樣式時第一反應是這玩意兒不就調(diào)個顏色和粗細嗎真正上手才知道線條樣式在 Mapbox GL 里對應的是一整套以line-開頭的 paint 屬性體系從基礎的線寬、顏色到虛線、漸變、描邊、偏移再到數(shù)據(jù)驅(qū)動樣式組合起來能玩的花樣非常多。如果你也在用 Mapbox 畫路線、邊界、等值線或者軌跡這篇就把線條樣式從配置到原理、從靜態(tài)到動態(tài)完整拆開講清楚順便把我踩過的坑也一并交代了。先說下這篇內(nèi)容的范圍以 Mapbox GL JS v2/v3 為主涉及addLayer添加line類型圖層、核心 paint 屬性的作用機制、GeoJSON 數(shù)據(jù)格式要求、樣式動態(tài)更新最后是幾個高頻問題的排查過程。適合剛接觸 Mapbox 的初學者也適合已經(jīng)能跑通基礎地圖、想在樣式細節(jié)上做深的朋友。1. 動手前的裝備Token 獲取與首次初始化很多人在 Mapbox 官網(wǎng)注冊賬號之后卡在了第一步到底去哪拿 token拿到之后又該放哪。這個環(huán)節(jié)看似基礎實際上后面所有圖層能否正常顯示都跟它直接相關所以先把這塊踩實。1.1 注冊與 Token 獲取的正確姿勢注冊流程沒什么特別的去 Mapbox 官網(wǎng)用郵箱注冊完成郵箱驗證后進入賬戶后臺。注意一個細節(jié)Mapbox 的 Access Token 分幾種包括默認的 public tokenpk.開頭和 secret tokensk.開頭。前端地圖渲染用的是 public token 就夠了secret token 千萬別寫進前端代碼里它泄露了相當于你的賬戶額度被人白嫖。在后臺的Account Tokens頁面可以創(chuàng)建新 token。創(chuàng)建時建議手動選擇一個 scope 集合至少要包含styles:read、fonts:read、tiles:read這幾個權限對應了加載樣式、字體和瓦片的基礎需求。Token 創(chuàng)建完成后有一串很長的字符串就是類似pk.eyJ1Ijoi...的長文本復制保存好。1.2 初始化地圖Token 放哪才不會被坑初始化地圖時 token 的注入方式有兩種一種是在引入 Mapbox GL JS 的 script 標簽之前直接給mapboxgl.accessToken賦值另一種是在new mapboxgl.Map()的構(gòu)造參數(shù)里通過accessToken字段傳入。推薦第一種代碼更清晰mapboxgl.accessToken pk.eyJ1Ijoi...; const map new mapboxgl.Map({ container: map, style: mapbox://styles/mapbox/streets-v12, center: [116.4, 39.9], zoom: 10 });這里有個常見的坑token 放到了 DOM 元素加載之前執(zhí)行。如果你把mapboxgl.accessToken ...寫在了#map這個 div 的前面而且用了document.getElementById(map)去拿容器大概率會報Container not found。反正我建議把地圖初始化代碼整體放在頁面底部或者用window.onload/DOMContentLoaded包起來順序問題就徹底沒了。初始化完成后如果你能看到 Mapbox 默認的街道底圖說明環(huán)境已經(jīng)通了。接下來所有線條相關操作都建立在這張底圖之上。2. line 圖層的骨架Source 數(shù)據(jù)格式與 addLayer 配置線條不是憑空畫出來的它必須先有數(shù)據(jù)源source再通過圖層layer定義怎么畫。Mapbox 的這套數(shù)據(jù)與樣式分離設計一開始可能會覺得繞但理解了之后會發(fā)現(xiàn)它特別適合做動態(tài)換膚和交互高亮。2.1 GeoJSON 里 LineString 的格式約束Mapbox 中最常用的 source 類型是 GeoJSON線條對應的幾何類型是LineString或MultiLineString。一個最簡單的線條數(shù)據(jù)長這樣{ type: FeatureCollection, features: [ { type: Feature, properties: { name: 路段A, speed: 80 }, geometry: { type: LineString, coordinates: [ [116.4, 39.9], [116.5, 39.92], [116.6, 39.88] ] } } ] }有幾點要提醒坐標順序是經(jīng)度在前、緯度在后寫反了線條會跑到地圖另一頭去我第一次就把這個順序搞反了折騰了半天才發(fā)現(xiàn)是經(jīng)緯度反了。另外LineString至少需要兩個坐標點MultiLineString則是多個LineString的數(shù)組適合表達一條整體邏輯線路的多段不連續(xù)幾何。properties 字段是自由擴展的它不會直接決定樣式但它是后面做數(shù)據(jù)驅(qū)動樣式的關鍵。比如你想根據(jù)道路的speed屬性給線條上不同顏色靠的就是 properties 里的字段。2.2 addLayer 的兩個核心參數(shù)source 與 paint給地圖添加線條圖層的完整代碼如下map.addLayer({ id: route-line, type: line, source: { type: geojson, data: geojsonData }, layout: { line-cap: round, line-join: round }, paint: { line-color: #ff6b6b, line-width: 4, line-opacity: 0.8 } });這里我會特別強調(diào)type: line這個字段。Mapbox GL 的圖層類型不止一種如果你把type寫成了fill那面的填充邏輯會覆蓋掉線的繪制邏輯線條直接消失。實際開發(fā)里我遇到過幾次圖層添加了但地圖上啥也沒有的情況排查到最后都是這種低級錯誤。layout和paint是兩個不同的屬性組layout 控制的是幾何層面的渲染方式比如線帽長什么樣、拐角怎么連接paint 控制的是視覺樣式比如顏色、寬度、透明度。在 Mapbox 文檔里這兩類屬性用不同的前綴標識line-開頭的屬性一部分歸 layout一部分歸 paint需要具體區(qū)分。我習慣記憶的方式是改顏色、寬度、透明度、模糊、偏移這類給人看的效果是 paint改端點形狀、連接形狀這些幾何構(gòu)造方式是 layout。2.3 直接內(nèi)聯(lián) source 與事先定義 source 的區(qū)別上面代碼里把 source 直接寫在了addLayer的source字段里這是 Mapbox GL JS 支持的一種快捷寫法數(shù)據(jù)會被隱式創(chuàng)建一個 source。但如果你需要多個圖層共用同一份數(shù)據(jù)——比如一套線數(shù)據(jù)既用來畫道路又用來畫道路兩側(cè)的描邊那最好顯式先添加 sourcemap.addSource(road-data, { type: geojson, data: geojsonData }); map.addLayer({ id: road-line, type: line, source: road-data, paint: { line-color: #333, line-width: 6 } }); map.addLayer({ id: road-outline, type: line, source: road-data, paint: { line-color: #fff, line-width: 2, line-offset: 3 } });這種一個 source 多個 layer的模式在實際項目里非常常見做道路網(wǎng)、地鐵線路圖時幾乎必用。原因是 Mapbox 允許同一條幾何數(shù)據(jù)用不同的 paint 參數(shù)渲染多遍疊加在一起就是描邊、發(fā)光、陰影等效果而不用復制數(shù)據(jù)。3. 線條樣式核心屬性從基礎到進階逐步拆解這節(jié)是全文的重頭戲。我把line-開頭的常用屬性按基礎三件套幾何形態(tài)控制進階視覺特效數(shù)據(jù)驅(qū)動四個層次來講每一層都給出可以直接抄的配置。3.1 基礎三件套顏色、寬度、透明度line-color是最直觀的屬性支持十六進制、rgb()、rgba()、hsl()等格式。特別注意如果使用了line-gradient線性漸變line-color會被忽略兩者不能同時生效。line-width的單位是像素但這里的像素是屏幕像素而非地理單位。當?shù)貓D縮放級別變化時默認為line-width不會隨縮放等比變化除非你手動做了interpolate表達式按 zoom 插值。這是一個地圖項目里非常常規(guī)的需求小比例尺時線細一點大比例尺時線粗一點如果直接固定一個值放大后線條會顯得過細縮小后又會糊成一團。line-opacity控制整體不透明度取值0到1。如果線條下面還有別的圖層需要露出下面的內(nèi)容就用 opacity 做半透明處理。我一般把 opacity 用在背景輔助線上比如行政區(qū)邊界疊加到底圖上半透明效果會讓主圖層更突出?;A三件套組合的最小配置paint: { line-color: #2b6cb0, line-width: 3, line-opacity: 0.9 }3.2 幾何形態(tài)線帽與拐角line-cap控制線頭形狀可選值有butt平頭、round圓頭、square方頭。默認是butt線條在端點處平直切斷。round是最常用的尤其在繪制道路、軌跡時圓頭看起來自然很多不會有被刀切了一刀的生硬感。line-join控制折線拐角處的連接方式可選值bevel斜切、round圓角、miter尖角。道路、河流這種自然元素首選round拐彎平滑如果是建筑輪廓、規(guī)劃紅線這類偏硬朗的圖形可以選miter或bevel。有個細節(jié)miter模式下如果拐角角度太小會出現(xiàn)尖角過長甚至超出預期繪制范圍的問題。Mapbox 為此提供了line-miter-limit屬性默認值2意思是當尖角長度超過線寬乘以該值的倍數(shù)時會自動切換為bevel渲染。你不需要經(jīng)常調(diào)它但知道這個機制后遇到尖角變形就不會慌了。3.3 虛線、點線與其他 patternline-dasharray是畫虛線用的屬性值為數(shù)字數(shù)組表示實線段和空白段的長度比例單位是線寬的倍數(shù)。例如line-dasharray: [4, 2]表示畫 4 倍線寬長度的實線、空 2 倍線寬長度的空白循環(huán)往復。這個倍數(shù)關系很重要——同一個 dasharray 數(shù)組在不同線寬下虛線的實際疏密會隨之變化。比如線寬 2 時[4, 2]的實線段是 8 像素線寬 10 時實線段就是 40 像素。如果希望虛線視覺疏密絕對一致需要結(jié)合 line-width 一起做 interpolate 調(diào)整否則會顯得很奇怪。Mapbox 的線帽和虛線有個聯(lián)動機制要留意當line-cap為butt時虛線段的端點會整齊切斷當line-cap為round時每個實線段的兩端會變成半圓形線段之間的視覺間距看起來略短。一個常見效果——鐵軌線就是round線帽加[2, 1]這種短周期 dasharray 產(chǎn)生的很適合用在跑步路線、規(guī)劃中的道路這類場景。3.4 進階視覺描邊、偏移與模糊line-offset可以把線條沿垂直方向平移一段像素正值為向右上偏移負值為向左下偏移。這個屬性的作用非常廣實現(xiàn)道路描邊、雙線道路、地鐵線路的平行線效果都靠它。一個典型的雙線道路做法先用一個line-offset: -4的圖層畫一條較寬的顏色甲線條再用line-offset: 4的圖層畫一條較窄的顏色乙線條。因為兩條線共用同一個 source幾何一致偏移方向相反視覺上就形成了道路中央分隔線的效果。line-blur給線條添加高斯模糊單位像素。配合較大的 line-width可以做出光帶效果常用于軌跡高亮、航線展示。要注意的是line-blur的模糊是向內(nèi)外兩側(cè)均勻擴散的模糊半徑超過線寬一半時線條中心會變淡出現(xiàn)空心感。我一般把 line-blur 設為線寬的 0.8 到 1.2 倍之間這樣既有光暈感又保留中心實體。line-gap-width是空心線的核心。它在線條內(nèi)部掏空一塊區(qū)域掏空寬度由該屬性決定。比如線寬 10、gap-width 4視覺上就是兩條各 3 像素的平行線夾著一條 4 像素的空白。做地鐵線路圖時一個線路圖層用 gap-width 配合深色背景就能畫出兩條平行軌道線再疊加一個白色小圓點圖層當車站標準的軌道效果就出來了。// 雙軌效果配置 const trackLayer { id: track, type: line, source: metro-line, paint: { line-color: #333, line-width: 8, line-gap-width: 5 } };3.5 線性漸變line-gradient 的使用邊界line-gradient允許線條顏色沿路徑方向漸變例如從起點到終點由綠變紅非常適合表達一條路徑的強度變化性能指標、擁堵程度等。它的使用機制和普通的 paint 屬性有很大不同必須配合line-gradient表達式且表達式需要一個[line-progress]作為輸入。paint: { line-width: 6, line-gradient: [ interpolate, [linear], [line-progress], 0, #2ecc71, 0.5, #f1c40f, 1, #e74c3c ] }[line-progress]是一個特殊變量取值從 0 到 1表示線段從頭到尾的進度比例。不能直接拿 properties 里的某個數(shù)值來做漸變的輸入只能用 line-progress。另外line-gradient生效時line-color完全失效且Mapbox 文檔里明確 line-gradient 不支持數(shù)據(jù)驅(qū)動也就是不能根據(jù)不同 feature 動態(tài)切換漸變規(guī)則它只能做全局統(tǒng)一的一條漸變管線??赡苡腥藭柲俏蚁虢o不同路段設置不同顏色而不是一條線上漸變該怎么做答案是回到數(shù)據(jù)驅(qū)動用line-color配合match或interpolate表達式按 properties 里的字段映射顏色。這才是按屬性區(qū)分顏色的正解。4. 動態(tài)改樣式與條件式渲染從靜態(tài)到交互靜態(tài)線條只能看動態(tài)改樣式才有靈魂。Mapbox 提供了setPaintProperty這個方法可以在運行時更新圖層樣式。這一步掌握之后懸停高亮、篩選顯示、主題切換都手到擒來。4.1 setPaintProperty 更新單屬性map.setPaintProperty(route-line, line-color, #e74c3c); map.setPaintProperty(route-line, line-width, 8);第一個參數(shù)是圖層 id第二個是 paint 屬性名第三個是新值。這個方法會以動畫方式更新樣式視覺上非常平滑。需要注意setPaintProperty 只能更新 paint 屬性不能更新 layout 屬性。如果你想換line-cap或line-join得用setLayoutPropertymap.setLayoutProperty(route-line, line-cap, butt);雖然 layout 屬性改動不觸發(fā) paint 的動畫過渡但也不會造成地圖整體重載所以不用擔心閃爍問題。4.2 數(shù)據(jù)驅(qū)動樣式match 和 interpolate 表達式的實際使用Mapbox 的樣式值支持表達式語法這意味著你可以在paint里寫一個函數(shù)而不只是一個常量。最常見的兩個場景場景一按類別給顏色假設 route 數(shù)據(jù)里有type字段值可能是expressway、local、footpath想分別顯示紅、黃、綠paint: { line-color: [ match, [get, type], expressway, #e74c3c, local, #f1c40f, footpath, #2ecc71, #95a5a6 // 默認兜底色 ], line-width: [ interpolate, [linear], [zoom], 8, 2, 12, 4, 16, 8 ] }match語法第一參是取值表達式后面是值-顏色成對出現(xiàn)的映射最后必須有一個兜底值不然匹配不到時會報錯。[get, type]表示從 feature 的 properties 里取 type 字段。場景二數(shù)值連續(xù)映射顏色如果屬性值是連續(xù)數(shù)值比如speed從 0 到 120想從綠色漸變到紅色paint: { line-color: [ interpolate, [linear], [get, speed], 0, #2ecc71, 60, #f1c40f, 120, #e74c3c ] }注意這里第一參是[linear]也可以用[exponential, 0.5]來做非線性插值但一般線性就夠用了。數(shù)據(jù)驅(qū)動樣式是 Mapbox 最強大的特性之一它的性能也足夠好Mapbox GL 通過分塊檢查 feature 的屬性值并按需重繪幾千個 feature 的樣式切換基本無感。4.3 鼠標懸停高亮與 feature-state 的結(jié)合懸停高亮經(jīng)常會用到feature-state這個機制比直接改源數(shù)據(jù)更高效。先在 source 上設置promoteId比如數(shù)據(jù)里每條 feature 有id字段map.addSource(route-data, { type: geojson, data: geojsonData, promoteId: id });然后在 paint 里用[feature-state, hover]作為條件paint: { line-color: [ case, [boolean, [feature-state, hover], false], #ff6b6b, #2b6cb0 ] }鼠標移動時通過setFeatureState更新狀態(tài)map.on(mousemove, route-line, (e) { if (e.features.length 0) { const featureId e.features[0].id; map.setFeatureState({ source: route-data, id: featureId }, { hover: true }); } }); map.on(mouseleave, route-line, () { map.removeFeatureState({ source: route-data, id: hoveredId }); });這套方案比setPaintProperty去逐條覆蓋顏色精準得多因為它按 feature 粒度管理狀態(tài)不會污染其他線條。如果數(shù)據(jù)里沒有 id 字段Mapbox 會基于坐標生成內(nèi)部 id但那個 id 不穩(wěn)定盡量保證數(shù)據(jù)里有穩(wěn)定的 id。5. 高頻坑與完整排查鏈路從線沒顯示到虛線不生效這里寫幾個我在實際項目中踩過、也幫別人排查過的典型問題。每個問題我都盡量還原完整的排查思路而不是直接丟結(jié)論因為排查思路本身比結(jié)論更有復用價值。5.1 坑一圖層加了但線完全看不見現(xiàn)象代碼不報錯地圖正常加載但 target 圖層區(qū)域空空如也。排查鏈路打開瀏覽器開發(fā)者工具在 Console 里看有沒有Unable to load source之類的報錯如果有先查 source 的 data URL 是否正確或者內(nèi)聯(lián) GeoJSON 是否格式非法。在 Console 執(zhí)行map.getLayer(route-line)返回undefined說明圖層壓根沒加上返回圖層對象但看不到說明數(shù)據(jù)或樣式有問題。執(zhí)行map.getSource(route-data)獲取 source 后調(diào)用getData()展開輸出的 JSON檢查 coordinates 數(shù)組里每個點是否[lng, lat]順序正確數(shù)值是否在合法范圍。檢查type是否為line。我見過有人把 GeoJSON 里的 geometry type 和 layer type 搞混geometry 是LineString但 layer 的type寫成了fill結(jié)果整個區(qū)域被填充而不是線條。檢查line-width是否被設成了0。說實話我有一段時間為了做只在某 zoom 級別顯示線用表達式把寬度設為 0后來忘了改回來排查了非常久。5.2 坑二虛線不生效顯示的還是實線現(xiàn)象設置了line-dasharray: [4, 2]但線條依然實線顯示。完整原因分析Mesh 層面有兩種可能。第一種是線條太短。Mapbox 的 dasharray 在短線上會因為 line-width 與比例關系導致虛線變化不明顯如果一條線只有幾個像素長虛線自然看不出效果。建議先拉長測試線排除數(shù)據(jù)長度因素。第二種原因是line-dasharray 與某些屬性不兼容。具體來說line-cap設置為round時虛線段的端點會繪制成圓頭如果 dasharray 數(shù)組的值較小圓頭和圓頭連在一起視覺上就會趨向于一條連續(xù)的粗線。另外line-gap-width與 dasharray 同時使用時虛線中的空隙區(qū)域表現(xiàn)會受 gap-width 影響出現(xiàn)實線段看起來被劈開、虛線間距消失的錯覺。排查方式先把 line-cap 改成 buttline-gap-width 改成 0看虛線是否恢復。如果恢復了就是這兩個屬性對 dasharray 的干擾。有一個隱藏很深的要求虛線渲染需要圖層是line類型且沒有開啟line-border之類的附加繪制某些舊版本 GL JS 在部分 GPU 驅(qū)動下對 dasharray 支持不完善會出現(xiàn)虛線渲染成實線或渲染成雪花點。遇到這種情況要么升級版本要么把 dasharray 轉(zhuǎn)成數(shù)據(jù)預處理——在 GeoJSON 層面生成多個線段配合實線-空白交替繪制不過這個方案復雜而且維護成本高非必要不建議做。5.3 坑三線寬隨縮放比例不對放大后鋸齒嚴重現(xiàn)象zoom 級別變大時線條沒有同步變粗且邊緣出現(xiàn)明顯鋸齒。原因分析地圖瓦片在放大時會進行插值縮放矢量數(shù)據(jù)的線條如果 line-width 沒有跟隨 zoom 變化就會出現(xiàn)縮放像素色塊導致的鋸齒。鋸齒感最直接的解決辦法是把line-width用 interpolate 表達式綁定 zoom讓線寬隨 zoom 平滑增加。paint: { line-width: [ interpolate, [linear], [zoom], 5, 1, 10, 3, 15, 8 ] }錨點zoom 值和線寬對應值不是隨便填的需要結(jié)合實際地圖場景調(diào)試。比如展示城市道路時zoom 5全國視野線寬 1px 比較合適zoom 10城市級3pxzoom 15街道級8px。這個沒有數(shù)學公式基本靠經(jīng)驗和視覺效果調(diào)。鋸齒還有一個來源是超長 LineString 在 WebGL 中被切割成多段繪制每段邊緣的抗鋸齒計算是獨立的導致接縫處出現(xiàn)拼接縫。這種情況可以通過減小單條 LineString 的長度拆分 feature來緩解或者用 GeoJSON 的tolerance字段在數(shù)據(jù)源端做幾何簡化。5.4 坑四Token 相關的 401 或樣式加載失敗雖然前面說了 token 獲取但每次都要強調(diào)地圖不顯示且 Console 里報Unauthorized或403第一個要查的就是 token 是否正確、是否超出配額。我在調(diào)試時經(jīng)常遇到一種情況有人把 Mapbox GL JS 的 CDN 地址復制錯了加載的是老版本 v1而 v1 和 v2 在 token 處理上存在差異。如果發(fā)現(xiàn)樣式一直掛在style.load事件不觸發(fā)檢查一下 script 引入的版本號和官網(wǎng)要求是否一致現(xiàn)在官方推薦直接用 v3 版本的 CDN 或 npm 包。還有一點如果你在本地開發(fā)用的是file://協(xié)議打開 HTML某些瀏覽器對跨域讀取本地 GeoJSON 文件有限制也會導致 source 加載失敗。開發(fā)階段建議用http-server、vite dev這類本地服務跑別直接雙擊 html 文件。6. 進階優(yōu)化與性能注意樣式不只是能顯示還要考慮性能尤其數(shù)據(jù)量上來之后。這一節(jié)不展開講所有性能優(yōu)化只講和線圖層直接相關的幾個點。6.1 大量線要素的性能隱患如果一次加載上萬條線比如全國的河流網(wǎng)、道路網(wǎng)每條線都有復雜的 LineString直接塞進 GeoJSON source 會讓地圖明顯卡頓。Mapbox 的矢量渲染效率已經(jīng)很高但過度復雜的幾何體仍然會在縮放時觸發(fā)大量 CPU/GPU 計算。常用策略是數(shù)據(jù)簡化simplify。發(fā)布前用工具降低坐標點密度保留幾何大輪廓即可。在 Mapbox 層面還能調(diào)整line-width的最小值、關閉line-blur之類耗性能的效果這些在小數(shù)據(jù)量時感覺不到大數(shù)據(jù)量時非常明顯。另外line-dasharray本身也會帶來額外的 GPU 紋理計算所以虛線圖層只加在必要的場景。如果整個地圖有幾百個虛線圖層幀率下降會很明顯。6.2 用 setData 更新數(shù)據(jù)與用 setStyle 的取舍動態(tài)更新線數(shù)據(jù)時可以直接操作 source 的setDatamap.getSource(route-data).setData(newGeoJSON);這個操作比整個setStyle輕量得多因為它只更新數(shù)據(jù)層不會重新加載底圖樣式和字體。但是有個注意事項setData后 source 上的promoteId、feature-state 等配置不會丟但已設置的 feature-state 會因數(shù)據(jù)更新而失效所以高亮狀態(tài)要在更新數(shù)據(jù)后重新設置。如果換了 token 或者想整體換底圖風格再考慮setStyle。6.3 與高德/Leaflet 線條樣式的差異是理解 Mapbox 的關鍵用過 Leaflet 或高德 JS API 的話你可能習慣直接給 polyline 設置color、weight、opacity。Mapbox 的思路不太一樣它把樣式按屬性拆得非常細并且允許每個屬性用表達式驅(qū)動。好處是復雜樣式的表達能力非常強壞處是初學階段需要多花點時間理解屬性體系。我自己的體會是一旦你習慣了 layout/paint 分離、表達式驅(qū)動、feature-state 這套模式再回去用 Leaflet 會覺得表達能力受限。Mapbox 的線樣式本質(zhì)上是聲明式的渲染管線你描述什么樣 Conditions 下畫成什么樣剩下的交給渲染引擎這和傳統(tǒng)命令式繪圖有本質(zhì)區(qū)別。7. 實用技巧與個人經(jīng)驗補充在收尾之前再分享幾個零散但非常實用的技巧都是我在實際項目中驗證過、覺得值得寫下來的。7.1 線條圖層的分層命名習慣圖層一多命名混亂是災難。我自己的習慣是{業(yè)務}_{類型}_{用途}比如road_line_main、road_line_outline、metro_line_track。這樣在代碼里查找和維護都方便也方便其他同事接手。同一個 source 的多層樣式之間用_main、_outline、_shadow這種后綴區(qū)分。7.2 在線的樣式調(diào)試工具Mapbox 官方提供的Mapbox Studio里可以可視化編輯樣式但它的樣式編輯器對表達式支持有限復雜的表達式仍然建議在代碼里維護。我更常用的調(diào)試方式是瀏覽器里開著開發(fā)者工具在 Console 里快速執(zhí)行map.setPaintProperty(...)反復調(diào)參顏色可以先用十六進制調(diào)到滿意再固化到代碼里。這個實時調(diào)參的流程比改代碼刷新頁面高效太多。7.3 巧用 opacity 疊加實現(xiàn)霓虹發(fā)光效果線條發(fā)光的實現(xiàn)不復雜一個線寬較大、透明度較低的圖層作為光暈層疊在上面再畫一條線寬較小、高亮顏色的主圖層兩者共用同一 source。光暈層用line-blur適當模糊不用單獨做漸變就能達到不錯的視覺效果。// 光暈層 map.addLayer({ id: route-glow, type: line, source: route-data, paint: { line-color: #ff6b6b, line-width: 12, line-opacity: 0.3, line-blur: 4 } }); // 主圖層 map.addLayer({ id: route-main, type: line, source: route-data, paint: { line-color: #ff2d2d, line-width: 3 } });注意圖層順序光暈層先 add主圖層后 add這樣主圖層壓在光暈層上方。如果順序反了主圖層的顏色會蓋住光暈效果。7.4 千萬別忽略 line-width 與 line-blur 的配合調(diào)line-blur時容易踩一個隱含邏輯blur 的值是像素但它是基于線寬繪制的紋理上做的。如果線寬只有 2pxblur 設成 20px那這條線會淡到幾乎看不見。我的經(jīng)驗是blur 的值不要超過 line-width 的 2 倍范圍否則調(diào)試時你會懷疑是不是透明度出了問題。最后再說一個個人體會。Mapbox 的線條樣式表看起來只是幾十個屬性但真正用好它考驗的是你對數(shù)據(jù)與渲染分離這一模型的理解。很多人在入門時急著去記 API 名字我卻建議先把 GeoJSON 結(jié)構(gòu)和屬性表達式搞明白數(shù)據(jù)里有什么字段、每條線代表什么含義、希望視覺上如何區(qū)分這些想清楚之后paint 屬性寫法水到渠成。如果遇到文檔里沒說透的細節(jié)比如某個屬性和另一個屬性打架、某個效果在不同縮放級別表現(xiàn)不一致別怕多試。拿一條簡單的測試線把可能的組合都調(diào)一遍比翻半天文檔更高效。你現(xiàn)在手上有項目的話挑一段真實路線數(shù)據(jù)把這篇文章里的配置依次鋪上去很快就能摸清 Mapbox 線條樣式的脾氣。