专栏首页计算机图形学 前端可视化 WebGL利用canvas阴影功能与双线技巧绘制轨道交通大屏项目效果

利用canvas阴影功能与双线技巧绘制轨道交通大屏项目效果

利用canvas阴影功能与双线技巧绘制轨道交通大屏项目效果

前言

近日公司接到一个轨道系统的需求,需要将地铁线路及列车实时位置展示在大屏上。既然是大屏项目,那视觉效果当然是第一重点,咱们可以先来看看项目完成后的效果图。

可以看到中间线路里轨道的效果是非常炫酷的,那么本文的主要内容就是讲解如何在canvas上绘制出这种效果。

分析设计稿

先看看设计稿中的轨道效果

程序员解决问题时经常喜欢用到的方法是把一个大问题拆解为若干个小问题然后逐一处理,也就是分而治之,所以我在思考这个轨道效果的实现时,也是先考虑到将它拆解。 根据设计稿我们可以看到这个线路实际上是由 外层的空心线+发光效果+内层的斑马线+倒影 组成的,所以我们要做的就是如何处理这几个小问题。

实现效果

绘制空心线与发光效果

绘制空心线时我们需要利用到[CanvasRenderingContext2D.globalCompositeOperation](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation)这个属性,详细原理可以查看canvas 绘制双线技巧,本文不再做赘述。 了解实现原理之后动手就很容易了,简述思路就是: 通过ctx.globalCompositeOperation = "destination-out"绘制空心线,再利用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方法就可以。

/**
 * 绘制空心线
 * @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";
}

效果图

绘制轨道中间的斑马线效果

中间的斑马线效果我们又可以再拆分为两个部分,先绘制一条底色的连线,然后再通过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],
  };
}

效果图

至此我们就还原了设计稿上的轨道效果了!

结语

至此文章已经到达尾声,我们可以总结一下绘制这条轨道线路效果所用到的技术点

  1. CanvasRenderingContext2D.globalCompositeOperation
  2. CanvasRenderingContext2D.shadowBlur
  3. CanvasRenderingContext2D.setLineDash()
  4. 离屏canvas技巧

可以看到想要达到好的效果还是不容易的,需要我们灵活配合使用多种绘制技巧,希望这篇文章能对大家有所帮助!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 利用canvas阴影功能与双线技巧绘制轨道交通大屏项目效果

    近日公司接到一个轨道系统的需求,需要将地铁线路及列车实时位置展示在大屏上。既然是大屏项目,那视觉效果当然是第一重点,咱们可以先来看看项目完成后的效果图。

    用户3158888
  • canvas可视化效果之内阴影效果

    在之前的一个轨道交通可视化项目中,运用到了很多绘制技巧。 可以参考 之前的一篇文章 《利用canvas阴影功能与双线技巧绘制轨道交通大屏项目效果》

    用户3158888
  • canvas可视化效果之内阴影效果

    在之前的一个轨道交通可视化项目中,运用到了很多绘制技巧。 可以参考 之前的一篇文章 《利用canvas阴影功能与双线技巧绘制轨道交通大屏项目效果》

    用户3158888
  • 利用canvas阴影功能与双线技巧绘制轨道交通大屏项目效果

    近日公司接到一个轨道系统的需求,需要将地铁线路及列车实时位置展示在大屏上。既然是大屏项目,那视觉效果当然是第一重点,咱们可以先来看看项目完成后的效果图。

    用户3158888
  • JavaScript模拟自由落体

    但是实际呈现的效果却不尽人意,应该是反弹位移计算有误,经反复思考无果(若哪位大拿有更好的实现方式欢迎评论告知)

    Jack Chen
  • DartVM服务器开发(第十二天)--Jaguar获取请求内容

    当路径上为参数时,我们可以在参数名前添加:符号,表面该路径上有一个是参数,下面我们请求一下这个地址吧!

    rhyme_lph
  • Canvas绘制圆点线段 思路二 setLineDash方法+lineCap设置扩展

    他自己实现了一种思路,然后咨询我有没有更好的思路。 先看看他的思路是如何实现的,大致代码如下:

    用户3158888
  • canvas绘制图形时beginPath应用

    当我们在使用canvas绘制图形的时候,不免都会使用到beginPath这个方法。今天我们就来讲解一下这个函数的重要性以及应用。

    小明爱学习
  • SDP(4):ScalikeJDBC- JDBC-Engine:Updating

        在上一篇博文里我们把JDBC-Engine的读取操作部分分离出来进行了讨论,在这篇准备把更新Update部分功能介绍一下。当然,JDBC-Engine的...

    用户1150956
  • 【融职培训】Web前端学习 第8章 egg基础教程2 路由

    路由器主要用于描述请求URL和具体采取执行动作的对应Controller关系,框架约定了app/router.js文件用于统一所有路由规则。通过统一的配置,我们...

    学习猿地

扫码关注云+社区

领取腾讯云代金券