使用svg绘制一张漂亮的地铁图(进阶篇)

作者: 张阳君 分类: 前端技术

在上一篇的基础教程里,我们已经可以使用d3来绘制地铁的线路和站点了,但是页面看上去光秃秃的,这样一个作品交付出去,都不好意思说自己是做前端的。这篇进阶教程将带你了解,如何使用定时器和svg渐变来实现酷炫的动画。开发过程中,我也踩了不少坑,其中的动画核心代码重构了2次,这些血的教训我都会在本文一一总结出来,希望大家可以举一反三,避免踩坑。

最初的思路

早早就完成了线路和站点的绘制引擎,心情还是很不错的。可当开始制作动画时,却六神无主了。客户的要求是,动效要炫一点,然后就没有然后了。这需求看似简单,尼玛是一道送命题!

想了半天还没有思路,干脆就先上一个最简单的动画:不同色彩的圆点跟随线路移动!感觉不错,就开始着手敲代码了,一开始想的是计算线路的关键点,然后用setInterval去控制圆点的运动轨迹。后来发现,其实根本不用那么麻烦,在svg的世界里,有个标签叫animateMotion(路径动画)。只需要在每个元素里面嵌套一个路径动画,调整一些属性,就能达到预想的动画效果。

用html可以这么写:

<svg>
  <g>
    <circle r="6">
      <animateMotion path="M20,20L200,20L200,100" begin="0s" dur="10s"></animateMotion>
    </circle>
  </g>
</svg>

用d3可以这么写:

// 创建半径为6的一个圆点
var circle = this.group.append('circle').attr('r',6);
// 让该圆点沿着具体路径运动
circle
  .append('animateMotion')
  .attr('path','M20,20L200,20L200,100')
  .attr('begin','0s')
  .attr('begin','10s')

页面效果如下:

animateMotion的属性这里就不赘述了,有了上面的demo,只需要把对应线路的path字符串取出来赋值给animateMotion,然后给粒子添加对应的颜色,就大功告成啦!

然鹅,客户表示效果不行。确实,从客户的角度来看,这个动画不那么酷炫,仅仅是一些粒子在平移。然后就有了下面“这一劫”。

进化后的思路

为了找到一个合适的效果,去Echarts的demo列表页溜达了一圈,最终决定使用城市公交例子中的彗尾效果。那种彗星拖着尾巴,若隐若现滑过轨道的样子客户一定很满意。那么问题来了,如何实现这种动画?思索良久,网上也搜了一圈,没有发现类似的svg动画。

如果简单在circle后面加个rect标签作为尾巴,那么转折点处的动画会特别生硬,尾部不能很好的和线路契合。思来想去,只有用path来做彗尾了。path路径怎么得到呢?从最初的思路出发,可以这么实现:每次发射两个一组的圆,这两个圆有时间差,用一个定时器每隔16毫秒去获取两个圆的坐标,将它们作为彗星path的首尾坐标,然后根据线路的路径点,推导出首尾坐标之间的转折点。这么一来,path坐标就有了,我们接下来只需要每隔16毫秒绘制一次,加上渐变,彗星的效果就出来了。

然鹅,这一次被animateMotion坑了一把,动画是出来了,但是一旦页面最小化,或者来个alert弹窗,animateMotion就彻底凌乱了。

其实原因在于浏览器进入休眠状态后,animateMotion动画会短暂失控,加上定时器受到休眠状态的影响,高频函数会休眠,而低频函数仍在执行(实际开发中发现chrome浏览器存在一个阈值)。

到这里才发现,自己踩入了一个深坑,animteMotion是肯定不能再使用了,必须手动实现圆点跟随路径的算法。

最终的解决方案

其实有了上面的经验教训,svg制作彗星动画的思路已经很清晰了。先总结一下:

  1. 写一个定时器,每次发射一组有时间间隔的粒子,根据两个粒子的坐标,计算得到彗星的path路径。
  2. 将彗星的path绘制出来,盖上渐变特效

计算彗星的path算法相对复杂一点,里面还涉及到坐标的防抖等,这里不详述,文末有源码的github地址。

这里说一下渐变。因为彗星是线条,这里选择了线性渐变。d3写法如下:

var defs = self.effectGroup.append('defs');
var linear = defs.append('linearGradient').attr('id', id).attr('gradientUnits', 'userSpaceOnUse');
linear.append('stop')
      .attr('offset', '0%')
      .style('stop-color', item.color)
      .style('stop-opacity', 0);
linear.append('stop')
      .attr('offset', '100%')
      .style('stop-color', LightenDarkenColor(item.color, 120))
      .style('stop-opacity', 1);

注意一下渐变的gradientUnits属性,这里设为了userSpaceOnUse,为什么不用默认的objectBoundingBox呢?由于path路径没办法设置完整的宽高属性,因此在横向或纵向path段,渐变是不生效的。用了userSpaceOnUse,并动态设置渐变的坐标,就能有效避免这个问题。

LightenDarkenColor是高亮16进制颜色的函数,将其用在渐变头部,就能实现头部高亮了。尾部的透明度设为0,这样就有一种若隐若现的感觉了。

最终效果如下:

到这里,我们就能用svg绘制出比较酷炫的地铁动效了,谢天谢地,客户看了也比较满意。

这个进阶篇贴的代码较少,需要完整代码的,可以到github里获取,欢迎star。

github地址:https://github.com/supervergil/d3-metro

(全文完)

0 条评论
回复
支持 Markdown 语法
暂无评论,来抢个沙发吧!