模仿對象 此前在嘗試製作時,我採用 Nike+ 官網效果為模板仿製。目前 Nike+ 已經升級,看不了之前版本的樣式及動態效果,暫且看看樣式區別不大的 Nike+ Run Club App 地圖,動畫部分依然以此前 Nike+ 官網效果為準。 分析一下,想要仿製跑步路線圖,其中有兩個難點,第一個是畫 ...
模仿對象
此前在嘗試製作時,我採用 Nike+ 官網效果為模板仿製。目前 Nike+ 已經升級,看不了之前版本的樣式及動態效果,暫且看看樣式區別不大的 Nike+ Run Club App 地圖,動畫部分依然以此前 Nike+ 官網效果為準。
分析一下,想要仿製跑步路線圖,其中有兩個難點,第一個是畫線動態效果,第二個是路線的漸變效果。畫線動畫是跑步過程的表現,漸變效果則是實時配速的表現。
光看都能感覺到漸變效果比較難,故這邊就先不模仿它,搞定畫線動畫先。之前在 Nike+ 網頁端,還在終點顯示了跑步路程,畫線動畫進行的同時顯示已跑的距離。
實現過程
網頁端顯示不比 App,尤其是還想嵌入在文章中的。一般是載入後,再給個點擊事件激活動畫效果。
添加折線
首先,把跑步折線顯示出來,並顯示起終點。在高德地圖 API 文檔中看到,可以使用 HTML 代碼顯示點標記的內容[1],這樣一來,起終點以及距離都直接寫 HTML,樣式直接用 CSS 寫就行。也方便後續畫線動畫時實時顯示已跑的距離。
// 坐標集 var lineArr = [ [116.81333,23.48132], [116.81333,23.48132], [116.81333,23.48132], [116.81352,23.48133], [116.81353,23.48124], ... ]; // 坐標總數,起終點坐標 var count = lineArr.length; var first = lineArr[0]; var last = lineArr[count - 1]; // 構造地圖對象 var map = new AMap.Map('map'); // 跑步路線折線 var polyline = new AMap.Polyline({ map: map, path: lineArr, lineJoin: 'round', strokeColor: "#52EE06", strokeOpacity: 1, strokeWeight: 3, strokeStyle: "solid" }); // 地圖自適應 map.setFitView(); // 起點 new AMap.Marker({ map: map, position: first, zIndex: 11, offset: new AMap.Pixel(-8, -8), content: '<div class="marker-circle green"></div>' }); // 終點 new AMap.Marker({ map: map, position: last, zIndex: 11, offset: new AMap.Pixel(-8, -8), content: '<div class="marker-circle red"></div>' }); // 距離 var distance = new AMap.Marker({ map: map, position: last, zIndex: 10, offset: new AMap.Pixel(-64, -12), // 採用 Polyline 類的 getLength() 方法直接獲取折線長度 content: '<div class="running-distance"><span class="running-number">' + (polyline.getLength()/1000).toFixed(1) + '</span>公裡</div>' });
到這裡,CSS 稍微修飾一翻,便可正常顯示出跑步的路線、起終點坐標以及跑步距離。
添加動畫
接下來是複雜一點的畫線動畫,先分析動畫需要顯示的:
- 畫線效果為不斷加長的折線
- 有個實時移動的點標記,剛開始是不顯示的
- 畫線同時,跑步路線底層為透明效果的黑色折線
- 畫線同時,跑步距離文字隨著動畫效果而變化
其中:
- 折線可利用高德地圖 API Polyline 類的
setPath()
方法來實現 - 點標記則是用
setPosition()
方法 - 底層的透明折線則可將上面顯示的折線直接拿過來用,方法為
setOptions()
- 點標記的
setContent()
方法
於是將畫線效果封裝成函數,採用 setTimeout()
方法做延時,為了看到的是效果流暢,將 delay
設置為 40(即 40 毫秒,每秒 25 幀),自增變數並迴圈執行。
// 變化的折線 var runPolyline = new AMap.Polyline({ map: map, lineJoin: 'round', strokeColor: "#52EE06", strokeOpacity: 1, strokeWeight: 3, strokeStyle: "solid", }); // 移動的點標記 var current = new AMap.Marker({ map: map, zIndex: 12, visible: false, offset: new AMap.Pixel(-8, -8), content: '<div class="marker-circle black"></div>' }); // 點擊地圖事件 map.on('click', function() { // 將上面上面折線改為黑色透明作為底層 polyline.setOptions({ strokeColor: '#000000', strokeOpacity: 0.2 }); // 顯示畫線點標記 current.show(); i = 0; drawline(); }); // 畫線動畫 function drawline() { if ( i < count ) { current.setPosition(lineArr[i]); runPolyline.setPath(lineArr.slice(0, i+1)); distance.setContent('<div class="running-distance"><span class="running-number">' + (runPolyline.getLength()/1000).toFixed(1) + '</span>公裡</div>'); i++; } else { current.hide(); return; } setTimeout(drawline, 40) }
完善動畫
Nike+ 的坐標約為十米一記,一個半馬兩千個點,若一下子迴圈執行這麼多次,一些瀏覽器可能性能不保,會影響到具體顯示的效果。在這裡需要做優化,將每次畫線增加的距離改為可控。
// 畫線動畫 function drawline(step) { if (i < count / step) { var start = i * step; var end = (i + 1) * step >= count ? count - 1 : (i + 1) * step; current.setPosition(lineArr[end]); runPolyline.setPath(lineArr.slice(0, end+1)); distance.setContent('<div class="running-distance"><span class="running-number">' + (runPolyline.getLength()/1000).toFixed(1) + '</span>公裡</div>'); i++; } else { current.hide(); return; } setTimeout(function(){ drawline(step); }, 40) }
這樣一來,即 drawline(10)
則為一幀 100 米,一幀多少米也可根據點的數量指定,從而控制動畫運行的總時間及保住某些瀏覽器。
最後給它一個 flag
,將畫線動畫改為可暫停。
var running = false; var i = 0; // 點擊地圖事件 map.on('click', function() { // 將上面上面折線改為黑色透明作為底層 polyline.setOptions({ strokeColor: '#000000', strokeOpacity: 0.2 }); // 顯示畫線點標記 current.show(); running = running == false ? true : false; // 動畫運行總時間約五秒 var step = parseInt(count/50); step = step == 0 ? 1 : step; drawline(step); }); // 畫線動畫 function drawline(step) { if ( i < count / step ) { if( running == true ){ var start = i * step; var end = (i + 1) * step >= count ? count - 1 : (i + 1) * step; current.setPosition(lineArr[end]); runPolyline.setPath(lineArr.slice(0, end+1)); distance.setContent('<div class="running-distance"><span class="running-number">' + (runPolyline.getLength()/1000).toFixed(1) + '</span>公裡</div>'); i++; } else { return; } } else{ current.hide(); i = 0; running = false; return; } setTimeout(function(){ drawline(step); }, 40) }
完整實例
加上 CSS,我將完整的實例扔在 GitHub[2],需要自取。
參考資料