HTML5 SVG实践(一):徒手打造数码时钟

Rykka at 2014/09/05

本文地址:http://rykka.me/html5_svg_clock.html

本文主要演示了在没有外部素材的情况下, 如何使用SVG徒手创造一个数码时钟。

在这个过程中,首先对SVG的基本结构进行熟悉并进行绘制。

最后分别使用Snap.svg和d3.js让时钟运行起来。

Living Demo (高级的浏览器才可以哦)



1.   SVG是什么?

SVG,全称可伸缩矢量图形 (Scalable Vector Graphics), 是W3C的标准之一(http://www.w3.org/TR/SVG/)。

其实SVG很早就出现了,但比Canvas还要没人关注, 不过自从HTML5可以内联SVG,并将SVG的很多特性纳入标准后, 各大浏览器厂商都开始大力支持SVG, SVG的Javascript绘制库(d3/Raphael/Snap...)也不断涌现, SVG也越来越受到前端的青睐了。

想起Applet/Flash/SilverLight/Canvas/……, 真是 长江后浪推前浪,前端死在沙滩上

2.   分析问题

首先我们要知道,数码时钟里的数字是如何显示的?

用过电子计算器或者数码时钟的人都知道, 一个数码时钟里面的一个数字是由7个部分组成:

A Digit Number in Digital Clock
         ———
        |   |
         ———
        |   |
         ———

根据显示的数字不同,它们将分别点亮, 比如1和2:

Digit Number 1      Digit Number 2
                         ———
        |                   |
                         ———
        |               |
                         ———

我们将每个部分分别命名为c1-c7:

A Digit Number               c3↓
     ———                     ———
    |   |              c1 → |   |← c6
     ———               c4 →  ——-
    |   |              c2 → |   |← c7
     ———                     ———
                             c5↑

那么在不同的数字时,让不同部分分别点亮。 That's it!

那么,如何点亮呢? ↓ ↓ ↓ ↓

3.   CSS Style

因为SVG的结构也可以套用class,并且被CSS渲染, 那么可以用CSS来根据每个数字的class来点亮。

比如n=1的时候, 我们为数字的svg加上一个'n1'的class, 此时c6和c7是点亮的,则有

.n1 .c6, .n1 .c7 {
    fill: blue;
}

最终 0~9的 css如下

.n1 .c6, .n1 .c7,
.n2 .c2, .n2 .c3, .n2 .c4, .n2 .c5, .n2 .c6,
.n3 .c3, .n3 .c4, .n3 .c5, .n3 .c6, .n3 .c7,
.n4 .c1, .n4 .c4, .n4 .c6, .n4 .c7,
.n5 .c1, .n5 .c3, .n5 .c4, .n5 .c5, .n5 .c7,
.n6 .c1, .n6 .c2, .n6 .c3, .n6 .c4, .n6 .c5, .n6 .c7,
.n7 .c3, .n7 .c6, .n7 .c7,
.n8 .c1, .n8 .c2, .n8 .c3, .n8 .c4, .n8 .c5, .n8 .c6, .n8 .c7,
.n9 .c1, .n9 .c3, .n9 .c4, .n9 .c5, .n9 .c6, .n9 .c7,
.n0 .c1, .n0 .c2, .n0 .c3, .n0 .c5, .n0 .c6, .n0 .c7
{ fill: #7A7ACC; }

4.   绘制SVG数字

CSS有了,再来一个数码时钟就够了~

这里我们采用SVG进行绘制。

要绘制1个SVG,有两个办法:

  1. 用矢量软件绘制( Illustrator/InkScape)。
  2. 用代码直接编写。

首先尝试下第一种方法(InkScape,因为使用的是Linux)。

4.1.   用InkScape绘制

4.1.1.   安装InkScape

sudo apt-get install inkscape

4.1.2.   绘制

打开Inkscape

  1. 画一个竖向的矩形,代表c1。

  2. 为矩形命名,右键点击矩形,选择Object Properties, 在打开的窗口中将ID改为c1。

  3. 复制这个矩形,将其复制并移动(旋转)到相应位置,并命名。

本来还应该将这个图形制作成元件(Symbol),但是由于 InkScape该项功能还没有正式发布。 所以和后面的简化SVG结合到一起。

至此Inkscape绘制工作完成,保存。

4.1.3.   简化SVG

用编辑器打开保存的SVG文件。 我们会发现里面有很多冗余信息, 需要手工进行简化。

  1. 去掉多余的SVG Tag: xml,xmlns,namespace,metadata,namedview和def 只保留 <svg xmlns:svg="http://www.w3.org/2000/svg" > 部分

  2. 去掉style和inkscape namespace, 否则我们不能动态渲染 (vim command):

    %s/style="[^"]*"//g
    %s/inkscape:\w*="[^"]*"//g
    
  3. 将每个rect的 id替换为class.

  4. 将它元件化 可以看到我们的``<rect>``被一个 <g> 包围着, g 这里代表 group的意思。

    我们把 g 改成 symbol, 并将id改为'digit'就可以了。

4.1.4.   在Html中测试

创建一个html文件,并将刚才简化的svg Tag复制到Html文件里。

我们会发现什么都没有显示。

这是因为元件(Symbol)是不显示的,我们必须在svg中引用它才行。

创建一个id为'digit_clock'的svg并引用(use, 具体用法见w3c)

<svg id='digit_clock'>
    <use xlink:href='#digit'></use>
</svg>

#digit 就是我们刚才创建的symbol的id:

再次打开浏览器,可以看到引用的元件显示在另一块svg后面, 并且只显示了一小部分(这里为svg添加了 background:gray; )。

Why?

这是因为包含元件的svg也被显示了。 同时svg的ViewBox默认为Symbol的宽和高。

为元件SVG添加hide属性,同时更改'digit_clock'代码如下

<svg id='digit_clock' width='200' height='200' viewBox='0 0 1000 1000'>
  <use xlink:href='#digit'></use>
</svg>

现在显示正常了。

4.1.5.   About ViewBox

代码里的ViewBox参数为 ViewBox='x,y,h,w'

它对元件显示的大小和位置进行了调整,

我们可以把它想象成一个可远可近的镜头。

当ViewBox的宽高放大时,就相当于把镜头放远。 当ViewBox的宽高缩小时,就相当于把镜头拉近。

也可以参考这里 张鑫旭同学的一篇文章

至此SVG已经可以正常使用了。

4.2.   用代码绘制

但是,目前的元件SVG里的数字看起来长短大小不一,怎么办?

作为不折腾不死星人,就用代码直接写吧!

代码如下,可参考 http://www.w3.org/TR/SVG/struct.html

<svg class='hide' xmlns:svg="http://www.w3.org/2000/svg">
    <symbol id='digit_c'>
    <rect width='100' height='400' x='0' y='0'></rect>
    </symbol>

    <symbol id='digit' viewBox='0 0 500 1100'
      preserveAspectRatio="xMinYMin meet" >
        <use class='c1' xlink:href='#digit_c' y='100'></use>
        <use class='c2' xlink:href='#digit_c' y='600'></use>
        <use class='c3' xlink:href='#digit_c' x='-100' y='100' transform='rotate(-90,0,0)'></use>
        <use class='c4' xlink:href='#digit_c' x='-600' y='100' transform='rotate(-90,0,0)'></use>
        <use class='c5' xlink:href='#digit_c' x='-1100' y='100' transform='rotate(-90,0,0)'></use>
        <use class='c6' xlink:href='#digit_c' x='500' y='100' ></use>
        <use class='c7' xlink:href='#digit_c' x='500' y='600'></use>
    </symbol>
</svg>

这里我们都是引用的同一个元素, 因此使用了transform函数进行旋转, 这是SVG很重要的属性,可参考 http://www.w3.org/TR/SVG/coords.html#TransformAttribute

5.   绘制SVG时钟

有了前面的基础,我们很容易绘制出一个数码时钟的所有数字和分隔符了。

我们为数字添加了'num'的class,为分隔符添加了'dot'的class。

值得注意的是,x和y的距离是根据symbol里元素大小来计算的。 如果symbol里的元素宽和高不一致,则需要相应的修改。

代码如下。

<svg id='digit_clock' ViewBox='0 0 890 100'>
    <use class='num' x="0" y="0" xlink:href="#digit"></use>
    <use class='num' x="100" y="0" xlink:href="#digit"></use>
    <g class='dot' id="digit_dot">
        <circle cx='200' cy='40' r='5'></circle>
        <circle cx='200' cy='70' r='5'></circle>
    </g>
    <use class='num' x="230" y="0" xlink:href="#digit"></use>
    <use class='num' x="330" y="0" xlink:href="#digit"></use>
    <use class="dot" x="230" y="0" xlink:href="#digit_dot"></use>
    <use class='num' x="460" y="0" xlink:href="#digit"></use>
    <use class='num' x="560" y="0" xlink:href="#digit"></use>
</svg>

预览下, 有点数码时钟的样子了。

6.   让数字动起来

好吧,我们需要让数字动起来,

逻辑很简单,取得当前的小时,分,秒, 并分别给对应的数字设置相应的class.

要控制svg元素,虽然只是设置class属性, jQuery是不行的(至少目前不行), 我们可以用 d3.js ,也可以用 Snap.svg

它们的语法很相似(都和jQuery类似),在这里的代码略有区别, 整体而言,d3.js处理数据更加方便一点,Snap处理动画更加方便一点。

snap.svg:

<script src="snap.svg-min.js"></script>
<script type="text/javascript">
    var digit_clock = Snap.select("#digit_clock");
    var dot = digit_clock.selectAll(".dot");
    var digit = digit_clock.selectAll(".num");
    var now, data;

    setTimeout(setInterval(function(){
            now = new Date,
                hours = now.getHours(),
                minutes = now.getMinutes(),
                seconds = now.getSeconds();

            data = [hours / 10 | 0, hours % 10, minutes
/ 10 | 0, minutes % 10, seconds / 10 | 0, seconds % 10]
            digit.forEach(function(elem,i){
                elem.attr('class', 'n'+data[i]);
            })
            dot.forEach(function(elem,i){
                elem.toggleClass('lit', seconds & 1 );
            })
    }, 1000), new Date % 1000);
</script>

d3.js:

<script src="d3.min.js"></script>
<script type="text/javascript">
    var digit_clock = d3.select("#digit_clock");
    var dot = digit_clock.selectAll(".dot");
    var digit = digit_clock.selectAll(".num");
    var now;
    setTimeout(setInterval(function(){
            now = new Date,
                hours = now.getHours(),
                minutes = now.getMinutes(),
                seconds = now.getSeconds();
                milliseconds = now.getMilliseconds();

            digit = digit.data([hours / 10 | 0, hours % 10, minutes
/ 10 | 0, minutes % 10, seconds / 10 | 0, seconds % 10,
milliseconds / 100 | 0]);
            digit.attr('class',function(d, i){
                    return 'n'+d;
            });
            dot.classed('lit', seconds & 1);
    }, 1000), new Date % 1000);
</script>

本文完