[翻译]用SVG和CSS动画实现一个简单的互动图表

Codrop是我很喜欢的一个Web前端开发教程站,页面精美讲解详细,今天翻译一篇跟我最近感兴趣的SVG相关的文章,也顺便体验用Markdown写作的简单顺畅感。

参考原文   INTERACTIVE INFOGRAPHIC WITH SVG AND CSS ANIMATIONS

预览效果           源码和附件

翻译模式开挂

和以PNG,JPG,GIF等为代表的位图比起来,SVG(可伸缩矢量图)的最大好处就是在任何分辨率及缩放下都不会变形,当需要大幅面的显示时,SVG比起位图的占用空间可谓是沧海一栗,可以显著减小文件大小,减少下载时间。

但是SVG最给力的还是它使用XML来描述,和HTML相似,因此我们可以用与操作DOM相似的方法(CSS,Javascript)来操作它,这可是前端攻城狮们的看家本领啊。 借现代浏览器助力(IE乖乖,出门右转吃糖糖去吧,进化到9以后再回来听大人说话),接下来我们就用SVG来做个交互信息图。

翠花,上SVG

生成SVG的方法真是汗牛充栋,哪怕用手写代码也能哼哧哼哧出来,不过咱是攻城狮,不是攻城狗,累死累活太丑陋,咱们要时刻保持优雅。美工们熟悉的Adobe Illustrator就能导出SVG,不过如果你差钱,这里有个叫做Inkscape的免费软件也相当不错,而且它处理SVG还更加专业,更适合我们。 用什么软件都行,但它必须能对图层分组和自定义组名(用作SVG元素的id属性值),这样我们才能建立SVG的层级结构以方便之后的CSS/Javascript操作。以上提到的两个软件都可以在对象(Object)->分组(Group)菜单下实现该功能。 例如在Illustrator中,双击图层名就可以编辑名称,之后在转化到SVG之后会成为分组id值,所以命名时要避免特殊字符,两个图层也不要重名。 在Inkscape中,使用Object > Object Properties指定组名。也可以用Edit > XML Editor来编辑细节,这里面功能更多,除了id以外,还可以指定class属性。

在本文例子中,保存好SVG文件后用文本编辑器打开,就会看到下面这些有条理的好孩子们:

<g id="background">
    <g id="bg-lines-left"> <!-- left background lines --> </g>
    <g id="bg-lines-right"> <!-- right background lines -->  </g>
</g>
<g id="logo"> <!-- logo graphics -->  </g>
<g id="quote">
    <g id="quote-left-brace"> <!-- left quote brace -->  </g>
    <g id="quote-right-brace"> <!-- right quote brace --> </g>
    <g id="quote-text"> <!-- quote text --> </g>
</g>
<g id="timeline">
    <g id="coffee">
        <rect id="coffee-bar" />
        <polyline id="coffee-arrow" />
        <g id="coffee-time"> <!-- time text --> </g>
        <g id="coffee-badge">
            <circle id="coffee-circle" />
            <g id="coffee-title"> <!-- title text --> </g>
            <g id="coffee-details"> <!-- icon, description --> </g>
        </g>
    </g>
    <g id="design">
        <rect id="design-bar" />
        <polyline id="design-arrow" />
        <g id="design-time"> <!-- time text --> </g>
        <g id="design-badge">
            <circle id="design-circle" />
            <g id="design-title"> <!-- title text --> </g>
            <g id="design-details"> <!-- icon, description --> </g>
        </g>
    </g>
    <g id="build">
        <rect id="build-bar" />
        <polyline id="build-arrow" />
        <g id="build-time"> <!-- time text --> </g>
        <g id="build-badge">
            <circle id="build-circle" />
            <g id="build-title"> <!-- title text --> </g>
            <g id="build-details"> <!-- icon, description --> </g>
        </g>
    </g>
    <g id="complain">
        <rect id="complain-bar" />
        <polyline id="complain-arrow" />
        <g id="complain-time"> <!-- time text --> </g>
        <g id="complain-badge">
            <circle id="complain-circle" />
            <g id="complain-title"> <!-- title text --> </g>
            <g id="complain-details"> <!-- icon, description --> </g>
        </g>
    </g>
    <g id="beer">
        <rect id="beer-bar" />
        <polyline id="beer-arrow" />
        <g id="beer-time"> <!-- time text --> </g>
        <g id="beer-badge">
            <circle id="beer-circle" />
            <g id="beer-title"> <!-- title text --> </g>
            <g id="beer-details"> <!-- icon, description --> </g>
        </g>
    </g>
</g>

以上只给出了SVG文件的骨架,具体内容请看源码。

看着跟HTML有点像吧?它们都使用了XML语法,每一个<g>标签代表一个分组,亦可以嵌套到别的分组里。SVG不强制每个元素拥有id属性,不过我们给它们指定id才方便之后的JavaScript/CSS操作。具体标签含义可以查看亲爱的W3CSCHOOL提供的SVG的教程

用Javascript将SVG装入HTML页面

The HTML

SVG嵌入旧的HTML有好几种做法,一是用<img>标签,二是用<embed>标签,你甚至都可以用CSS的background-image属性,但是这样显示出的SVG都只是图片,我们无法进入它的内部进行操控。在本文中,要操作SVG内的DOM,我们需要要好好利用HTML5的行内SVG支持特性,直接把它载入HTML文档就行,载入过程可以借助jQuery。

先在HTML里放一个占位的div当做容器:

<div id="stage"> 
    哟哟切克闹~~我是提示文字,当浏览器不支持SVG时候我就会显示,支持的话我就会被SVG覆盖掉了(PД`q。)·。'゜
</div>

JavaScript部分

用jquery.load将SVG文件载入id为stage的div当中,成功以后给#stage加上svgLoaded类名,这样就可以开始我们用CSS定义的动画了(CSS在后面给出)。

$(function(){

    $("#stage").load('interactive.svg',function(response){

        $(this).addClass("svgLoaded");

        if(!response){
            // 没有response说明SVG载入失败
            // 
        }

    });
});

注意: 如果以本地文件形式打开,出于安全原因(同源策略),Chrome会阻止你读取本地文件(其他浏览器估计也不会允许)。要想本文源码正确工作,请确定你的脚本运行在localhost或是服务器端。

CSS 部分

在本文里所有的示例CSS都没有添加浏览器特性前缀(-webkit-,-moz-这些),但是最终源码的CSS会完整包含。

先指定SVG的容器div,默认情况下SVG会自动撑满它的容器。如果不设定容器大小的话,也许会有不可控状况发生。

#stage {
    width: 1024px;
    height: 1386px;
}

用CSS为SVG添加样式,设定变形原点

本文的SVG元素动画技术的关键在于设定transform-origin(变形原点)值,因为默认情况下SVG内元素的transform-origin都在SVG的原点,即(0px,0px),若要元素正确地变形(旋转,缩放,平移等)则需要将它们的变形原点放到与它们相应的地方(一般来说是圆心)。

咔嘭咔嘭: 原文用了个很笨的方法,一个一个的从Inkscape里复制粘贴每个元素的变形原点数据。

#coffee { 
    transform-origin: 517px 484px;
}
#coffee-badge { 
    transform-origin: 445px 488px;
}
#coffee-title { 
    transform-origin: 310px 396px;
}
#coffee-details { 
    transform-origin: 311px 489px;
}

#design { 
    transform-origin: 514px 603px;
}
#design-badge { 
    transform-origin: 580px 606px;
}
#design-title { 
    transform-origin: 712px 513px;
}
#design-details { 
    transform-origin: 710px 620px;
}

#build { 
    transform-origin: 511px 769px;
}
#build-badge { 
    transform-origin: 445px 775px;
}
#build-title { 
    transform-origin: 312px 680px;
}
#build-details { 
    transform-origin: 310px 790px;
}

#complain { 
    transform-origin: 512px 1002px;
}
#complain-badge { 
    transform-origin: 586px 1000px;
}
#complain-title { 
    transform-origin: 718px 921px;
}
#complain-details { 
    transform-origin: 717px 1021px;
}

#beer { 
    transform-origin: 513px 1199px;
}
#beer-badge { 
    transform-origin: 444px 1193px;
}
#beer-title { 
    transform-origin: 313px 1097px;
}
#beer-details { 
    transform-origin: 316px 1202px;
}

Apply some initial transformations

在我们用Inkscape创建的SVG文件里,给#timeline组中一些元素设立了'hover(鼠标悬停)'效果,接下来我们要用CSS3属性选择器强大的后缀选择功能(功能强大咱就能少写几行字了)来指定元素们在正常情况下的样子。

[id$=badge] { /* id以'badge'结束的所有元素 */
    transform: scale(0.5, 0.5);
}
[id$=title] {  /* id以'title'结束的所有元素 */
    transform: scale(1.8) translate(0px, 48px);
}
[id$=details] {  /* id以'details'结束的所有元素 */
    transform: scale(0, 0);
}

在Inkscape中可以指定class属性(即类名),所以你也可以用.someEle来选择类名为'someEle'的元素。 但是如果你把SVG文件导入Illustrator,再次保存时它会去掉它不认识的class属性,造成信息丢失。所以说编辑SVG还是Inkscape比较保险。

加入:hover鼠标悬停效果

现在我们要给在id为'timeline'的组内的那些id以'badge','details'或者'tittle'结尾的元素加上点效果,我们添上了transition属性实现一点动画渐变效果。

#timeline > g:hover [id$=badge], #timeline > g:hover [id$=details] {
    transform: scale(1, 1); 
}
#timeline > g:hover [id$=title] {
    transform: scale(1) translate(0px, 0px);
}
[id$=badge], [id$=title], [id$=details] {
    transition: transform 0.25s ease-in-out;
}

开场动画来一个

要实现一个流逼流畅又丰富的动画,要敲的代码还真不算少,定制CSS动画的方法之一就是制定keyframes(动画关键帧)。

以下使用了translateX,所以如果你的浏览器不支持CSS的3D变换的话,那就没得玩了。

@keyframes left-brace-intro {
    0% { 
        transform: translateX(220px);
        opacity: 0;
    }
    50% { 
        opacity: 1;
        transform: translateX(220px);
    }
    100% { 
        transform: translateX(0px);
    }
}
@keyframes right-brace-intro {
    0% { 
        transform: translateX(-220px);
        opacity: 0;
    }
    50% { 
        opacity: 1;
        transform: translateX(-220px);
    }
    100% { 
        transform: translateX(0px);
    }
}
@keyframes fade-in {
    0% { 
        opacity: 0;
    }
    100% { 
        opacity: 1;
    }
}
@keyframes grow-y {
    0% { 
        transform: scaleY(0);
    }
    100% { 
        transform: scaleY(1);
    }
}
@keyframes grow-x {
    0% { 
        transform: scaleX(0);
    }
    100% { 
        transform: scaleX(1);
    }
}
@keyframes grow {
    0% { 
        transform: scale(0, 0);
    }
    100% { 
        transform: scale(1, 1);
    }
}

动画keyframe(关键帧)定义好了以后,我们只要动用CSS选择器,给元素的animation属性指定动画名和相应设置。这里的animation-fill-mode: backwards表示在动画结束后元素会回到初始(也就是keyframes中的0%)状态。

.svgLoaded #logo { 
    animation: fade-in 0.5s ease-in-out; 
    //简写方法,分别对应animation-name,animation-duration,animation-timing-function
}
.svgLoaded #quote-text { 
    animation: fade-in 0.5s ease-in-out 0.75s;
    animation-fill-mode: backwards; 
}
.svgLoaded #quote-left-brace { 
    animation: left-brace-intro 1s ease-in-out 0.25s;
    animation-fill-mode: backwards; 
}
.svgLoaded #quote-right-brace { 
    animation: right-brace-intro 1s ease-in-out 0.25s;
    animation-fill-mode: backwards; 
}
.svgLoaded #background { 
    animation: grow-y 0.5s ease-in-out 1.25s;
    transform-origin: 512px 300px;
    animation-fill-mode: backwards; 
}
.svgLoaded #background > g { 
    animation: grow-x 0.25s ease-in-out 1.75s;
    animation-fill-mode: backwards; 
}
.svgLoaded #background > g:last-of-type { 
    transform-origin: 458px 877px; 
}
.svgLoaded #background > g:first-of-type { 
    transform-origin: 563px 877px; 
}
.svgLoaded #coffee, .svgLoaded #design, .svgLoaded #build, .svgLoaded #complain, .svgLoaded #beer { 
    animation: grow 0.25s ease-in-out;
    animation-fill-mode: backwards; 
}
.svgLoaded #coffee { 
    animation-delay: 2s; 
}
.svgLoaded #design { 
    animation-delay: 2.25s; 
}
.svgLoaded #build { 
    animation-delay: 2.5s; 
}
.svgLoaded #complain { 
    animation-delay: 2.75s; 
}
.svgLoaded #beer { 
    animation-delay: 3s; 
}

WEB自定义字体

本例中我们使用了非WEB标准字体LeagueGothic,导出成SVG文件后,会用font-family标出:

<!-- .SVG文件部分内容. -->
    <text font-family="'LeagueGothic'" font-size="28">12PM</text>
<!-- ... -->

在CSS中记住要用@font-face定义这种字体以及字体文件所在位置:

<!-- .CSS文件部分内容. -->
    @font-face {
        font-family: 'LeagueGothic';
        url("../fonts/league-gothic/league-gothic.eot.woff") format("woff");
     }

该说的都说了,大家来这里看看效果,如果看不到图片或者没有动态效果,请确定你的浏览器支持SVG显示以及CSS3的3D变换效果,在这里给chrome做个广告,你绝对值得拥有居家旅行杀人放火必备浏览器

想自己琢磨多点的,可以先把源码抱回去。

咔嘭呛后话

本文中用CSS3实现变换和动画,其实SVG本身也支持变形动画,效果也挺不错。具体可以看微软的教程

再推荐一个我最近很喜欢的开源Javascript项目Raphael,作者是Adobe的JS大牛Dmitry Baranovskiy,Raphael封装了强大的SVG绘画和动画实现,API清晰,容易扩展,可以用来做互动图表和游戏,官网有几个不错的demo,你也可以去它的github仓库看看相关项目和资料。

posted @ 2013-03-23 10:51  咔嘭呛  阅读(1000)  评论(0编辑  收藏  举报