近日公司接到一個軌道系統的需求,須要將地鐵線路及列車實時位置展現在大屏上。既然是大屏項目,那視覺效果固然是第一重點,我們能夠先來看看項目完成後的效果圖。
能夠看到中間線路里軌道的效果是很是炫酷的,那麼本文的主要內容就是講解如何在canvas上繪製出這種效果。android
先看看設計稿中的軌道效果
程序員解決問題時常常喜歡用到的方法是把一個大問題拆解爲若干個小問題而後逐一處理,也就是分而治之,因此我在思考這個軌道效果的實現時,也是先考慮到將它拆解。
根據設計稿咱們能夠看到這個線路其實是由 外層的空心線+發光效果+內層的斑馬線+倒影 組成的,因此咱們要作的就是如何處理這幾個小問題。程序員
繪製空心線時咱們須要利用到[CanvasRenderingContext2D.globalCompositeOperation](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation)
這個屬性,詳細原理能夠查看canvas 繪製雙線技巧,本文再也不作贅述。
瞭解實現原理以後動手就很容易了,簡述思路就是:
經過ctx.globalCompositeOperation = "destination-out"繪製空心線,再利用canvas的陰影配置來模擬發光的效果。
直接上代碼:canvas
// 獲取頁面裏的畫布元素和其上下文對象 var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); // 因爲ctx.globalCompositeOperation = "destination-out"會影響到畫布上已有的圖像 // 因此須要先建立一個離屏canvas,把空心線繪製到離屏canvas上,再將離屏canvas繪製到頁面的畫布中 var tempCanvas = document.createElement("canvas"); tempCanvas.width = 800; tempCanvas.height = 800; var tempCtx = tempCanvas.getContext("2d"); // 建立座標點用來連線 var points = [createPoint(50, 50), createPoint(500, 50), createPoint(500, 500)]; // 配置參數 var options = { color: "#03a4fe", // 軌道顏色 lineWidth: 26, // 總寬度 borderWidth: 8, // 邊框寬度 shadowBlur: 20, // 陰影模糊半徑 }; paint(ctx, points, options); // 繪製 function paint(ctx, points, options) { paintHollow(tempCtx, points, options); // 將離屏canvas繪製到頁面上 ctx.drawImage(tempCanvas, 0, 0); } /** * 繪製空心線 * @param {*} ctx 畫布上下文 * @param {*} points 座標點的集合 * @param {*} options 配置 */ function paintHollow( ctx, points, { color, lineWidth, borderWidth, shadowBlur } ) { // 連線 paintLine(ctx, points); // 添加配置參數 ctx.lineWidth = lineWidth; ctx.strokeStyle = color; ctx.lineCap = "round"; ctx.lineJoin = "round"; // 利用陰影 ctx.shadowColor = color; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; ctx.shadowBlur = shadowBlur; ctx.stroke(); ctx.globalCompositeOperation = "destination-out"; ctx.lineWidth -= borderWidth; ctx.strokeStyle = color; ctx.stroke(); ctx.globalCompositeOperation = "source-over"; } /** * 根據點位繪製連線 * @param {*} ctx 畫布上下文 * @param {Array} points 座標點的集合 */ function paintLine(ctx, points) { var pointIndex = 0, p0, value, pointCount = points.length; p0 = points[0]; ctx.beginPath(); ctx.moveTo(p0.x, p0.y); for (pointIndex = 1; pointIndex < pointCount; pointIndex++) { value = points[pointIndex]; ctx.lineTo(value.x, value.y); } }
效果圖微信
能夠看到設計稿裏的倒影效果就是在軌道下方再次繪製了一條透明度較低的空心線,因此這裏實現起來就比較簡單了,稍微改造一下paintHollow方法就能夠。cookie
/** * 繪製空心線 * @param {*} ctx 畫布上下文 * @param {*} points 座標點的集合 * @param {*} options 配置 * @param {*} isReflect 當前繪製的是不是倒影效果 */ function paintHollow( ctx, points, { color, lineWidth, borderWidth, shadowBlur, reflectOffset }, isReflect = false ) { if (!isReflect) { // 繪製倒影的時候透明度下降 ctx.globalAlpha = 0.5; // 經過自調繪製一個倒影效果出來 paintHollow( ctx, points.map(({ x, y }) => { return { x, y: y + reflectOffset }; }), { color, lineWidth, borderWidth, shadowBlur: 0 }, true ); ctx.globalAlpha = 1; } // 連線 paintLine(ctx, points); // 添加配置參數 ctx.lineWidth = lineWidth; ctx.strokeStyle = color; ctx.lineCap = "round"; ctx.lineJoin = "round"; // 利用陰影 ctx.shadowColor = color; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; ctx.shadowBlur = shadowBlur; ctx.stroke(); ctx.globalCompositeOperation = "destination-out"; ctx.lineWidth -= borderWidth; ctx.strokeStyle = color; ctx.stroke(); ctx.globalCompositeOperation = "source-over"; }
效果圖spa
中間的斑馬線效果咱們又能夠再拆分爲兩個部分,先繪製一條底色的連線,而後再經過lineDash屬性繪製一條虛線,就能夠達到設計稿上的效果了。設計
/** * 繪製軌道中間部分 * @param {*} ctx * @param {*} points * @param {*} param2 */ function paintInner( ctx, points, { color, innerWidth, borderWidth, innerColor, shadowBlur } ) { ctx.lineCap = "round"; ctx.lineJoin = "round"; paintLine(ctx, points); ctx.lineWidth = innerWidth; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; ctx.shadowBlur = shadowBlur; ctx.strokeStyle = innerColor; ctx.shadowColor = color; // 先根據中間部分的顏色繪製一條線出來 ctx.stroke(); ctx.lineCap = "butt"; ctx.setLineDash([5, 15]); ctx.lineDashOffset = 0; const { r, g: green, b } = getRgba(color); // 再根據軌道的主色調繪製一條透明度較低的虛線 ctx.strokeStyle = `rgba(${r},${green},${b},0.4)`; ctx.stroke(); } /** * 獲取一個顏色值的r,g,b,a * @param {*} color */ function getRgba(color) { if (!canvas1 || !ctx1) { canvas1 = document.createElement("canvas"); canvas1.width = 1; canvas1.height = 1; ctx1 = canvas1.getContext("2d"); } canvas1.width = 1; ctx1.fillStyle = color; ctx1.fillRect(0, 0, 1, 1); const colorData = ctx1.getImageData(0, 0, 1, 1).data; return { r: colorData[0], g: colorData[1], b: colorData[2], a: colorData[3], }; }
效果圖
至此咱們就還原了設計稿上的軌道效果了!netty
至此文章已經到達尾聲,咱們能夠總結一下繪製這條軌道線路效果所用到的技術點code
能夠看到想要達到好的效果仍是不容易的,須要咱們靈活配合使用多種繪製技巧,但願這篇文章能對你們有所幫助!對象
若是對可視化感興趣,能夠和我交流,微信541002349. 另外關注公衆號「ITMan彪叔」 能夠及時收到更多有價值的文章。