近日公司接到一个轨道系统的需求,须要将地铁线路及列车实时位置展现在大屏上。既然是大屏项目,那视觉效果固然是第一重点,我们能够先来看看项目完成后的效果图。
能够看到中间线路里轨道的效果是很是炫酷的,那么本文的主要内容就是讲解如何在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彪叔” 能够及时收到更多有价值的文章。