svg画图体会

我在上一篇日志里用了svg来画图,一改以往画个流程图然后截图上传的方式,一来方便部署,无须上传图床,二来配合css还能展现动图效果。

工作上一直没碰到过用svg,回想起来第一次见好像是在学习font-face的时候,其作为一款字体文件存在?不过查了一下svg的兼容性只到ie9的样子。最近第一次尝试用svg,用来实现触屏版web设计稿中出现的环形进度图,用svg相当方便。也是从这里学来的»,利用svg的stroke-dasharray属性的变动来实现进度的变化。

而刚提到的用svg画日志的示意图,也是启发于 google开发者的某篇文章,其用svg来画动图,以至于本想收藏动图的时候看源码发现,原来还有这种操作!

上述的svg主要是配合css3的animation来做动画的,貌似兼容性方面,chrome新版基本都支持,其他就不敢保证了,所以,如果这里浏览器打开看不到动图或者图不动,那基本就是浏览器不兼容了。

秀一下最近画的三个图吧,两个示意图+一个小动画

戳这里去找源码

戳这里去找源码

pagehtml Viewport 背景色代表page有实体内容 page滑到接近底部加载page 保证可视page的临近有内容

下面有svg bug,说明见下文

我喜欢你

关于svg入门

我直接在mdn 发现 svg入门,然后跟着边看边写代码验证的。貌似还有补充的内容没跟上,不过认识基本的文字text和图形也就差不多了。

较为复杂需要认真学习的是path(画路径) 以及 其贝赛尔曲线,而path本身足够强大,基本可以画出任何图形来,比如小动画里的爱心,就是用path画出来的。

多形状的动画联动

以上述小动画为例,简单介绍一下多个形状之间的animation是如何联动的,关于animation,其中主要用到的4个属性:animation-name、 animation-duration、animation-delay、animation-iteration-count,分别指的是 指定的动画、动画持续时间、动画开始延迟时间、动画迭代次数。

一般来说动画基本是设定成循环播放的,所以迭代次数一般设为:infinite

我们先用svg把基本形状都画出来

<svg class="process" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 800 800">
<!-- 背景 -->
  <rect class="bkg" x='0' y='0' width="800" height="800" stroke="#333" fill="#fff"></rect>
<!-- 左侧男孩 -->
  <g id="boy">
    <!-- 头部 -->
    <circle class="head item" cx="150" cy="400" r="50"></circle>
    <!-- 脸颊部分,用于害羞 -->
    <ellipse class="item face" cx="170" cy="410" rx="20" ry="5" />
    <!-- 身体部分 -->
    <path class="body item" d="M150,450 l-100,350 l200,0Z"/>
    <!-- 爱心和手组合 -->
    <g id="heart">
      <line class="hand" x1="150" y1="500" x2="150" y2="650"/>
      <path d="M150,720 l -70.7,-70.7 a 50 50  0 0 1  70.7 -70.74 a 50 50  0 0 1  70.7 70.74 l -70.7 70.7z "  stroke="#fff" stroke-width="1" ></path>
    </g>
    <!-- 对话框 -->
    <g id="talk">
      <path class="item" d="M150,300 l-30,-30 l-100,0 l0,-220 l500,0 l0,220 l-340,0 Z"/>
      <text x="200" y="150" text-anchor="middle" font-size="40">我喜欢你</text>
    </g>
  </g> 
<!-- 右侧女孩 --> 
  <g id="girl">
    <g class="head-part">
    <circle class="head item" cx="650" cy="400" r="50"></circle>
    <!-- 眼睛,用于标识低头的一个视觉参照 -->
      <circle class="eye item" cx="620" cy="400" r="5"></circle>
    <ellipse class="item face" cx="630" cy="410" rx="20" ry="5" />
    </g>
    <path class="body item" d="M650,450 l-100,350 l200,0Z"/>
    <line class="hand" x1="650" y1="500" x2="650" y2="650"/>
  </g>  
</svg>

基本形状画出来之后,就是一连串动作的一个定义了,首先是手拿红心向上前方抬起(动作1),然后出现对话框表白(动作2),然后就是脸颊变红,同时被表白对象还低下头表害羞状(动作3)。

因为每个动作的对象,并不都是一个形状,无法用单个animation来实现,而多个animation之间有一个顺序的关系在里面,那就是动作2必须要在动作1完成之后才能开始行动,听起来,好像用animation-delay就可以搞定了。

然而,我一开始也是考虑delay,结果效果一出来懵逼了一会,那就是一开始正常,后面动作就不连贯,甚至出现同步了,为什么呢?因为delay指的是动画开始的延迟时间,并不是每次动画开始的延迟时间,当动画是循环播放时,这个delay只作用于动画第一次播放,后面就没有delay什么事了,于是联动效果扑街~

主要做法是,把动作都定成一样的时长,将动作的状态分解到keyframes的百分比去。

比如,抬起手和对话框呈现的动作。可以这么定义

 @keyframes hand{
    0%,20%{
      transform: rotate(0deg);
    }
    25%,100%{
      transform: rotate(-90deg);
    }
  }
  @keyframes talk{
    0%,45%{
      opacity: 0;
    }
    50%,100%{
      opacity: 1;
    }
  }

手在20%-25%处开始执行动作,并在往后一直保持动作后状态,而对话框则是在45%-50%开始动作,并随后保持。

那么这个时间阶段怎么确定呢?

目前我是这么来的(不一定靠谱),比如现在有四个顺序动作,那么100分成了4份,于是0-25-50-75-100.

再然后每个阶段往前推移一点定义一下动作前的保持,比如25,取20,于是第一个动作的执行时长阶段时在20%-25%,后面只是做动作结尾的一个保持。理解了这个之后动画联动就没那么难理解了。

其中svg画图,我觉得确定动画阶段挺麻烦的,但更麻烦的,估计是画图的时候坐标的考虑问题吧。而动作那块基本上无非就是transform的属性设置了。

Update

大概去年重新点开这个页面的时候发现了最后一个svg动画有bug,之前都好好的。落下的技术债,现在简单补上。以前记得是chrome是可以正常显示的,但是现在一旦旋转,似乎就旋转中心找错的方向,导致动画乱了。经排查,主要是transform-origin的取值,试验发现,取值为top|center等,不再为当前元素为根据(原本center指的是元素的中心),而是以整个svg为根据了,导致center center是绕着svg中心旋转,解决方案:transform-origin取具体坐标像素值即可,但多了一层手工计算,麻烦。

更新后的svg在这里»