SVG入门与实践

  • 今天来聊聊SVG技术。本文先简单阐述svg的概念和一些特性, 再分享一些我在项目实战中用到的svg例子。

一、入门

概念

Scalable Vector Graphics (SVG) 可扩展矢量绘图,是一种用来描述二维矢量图形的XML标记语言。

特性

SVG 可被非常多的工具读取和修改(比如记事本)
SVG 与 JPEG 和 GIF 图像比起来,尺寸更小,且可压缩性更强。
SVG 是可伸缩的
SVG 图像可在任何的分辨率下被高质量地打印
SVG 可在图像质量不下降的情况下被放大
SVG 图像中的文本是可选的,同时也是可搜索的(很适合制作地图)
SVG 可以与 Java 技术一起运行
SVG 是开放的标准
SVG 文件是纯粹的 XML
SVG 的主要竞争者是 Flash。
与 Flash 相比,SVG 最大的优势是与其他标准(比如 XSL 和 DOM)相兼容。而 Flash 则是未开源的私有技术。

基础形状

矩形 <rect>
圆形 <circle>
椭圆 <ellipse>
线 <line>
折线 <polyline>
多边形 <polygon>
路径 <path>

使用方式

将SVG文件作为图片,与普通图片使用方式一致;但不会加载自身引用, 如字体、图标等等外部资源。

将SVG作为应用程序,SVG文件也可以作为<object>元素的data属性引入HTML中。但是,MIME type必须是image/svg+xml

混合文档, 直接跟HTML标签混合写入, 会继承父文档的样式,默认以inline方式显示。

使用总结

在我实际使用svg的过程中, 除了path, 我觉得实现起来都不会太难。 path的可以用于十分复杂的场景,也十分灵活, 但学习曲线比较陡。接下来就一起看看path的一些实践例子。

二、实践

svg实现波浪、水泡效果

先上效果图

实现代码(Reactjs中)

1、波浪的实现

创建单个波浪的svg,wave.svg(以下css的背景图引用) , 主要使用pathC指令绘制3次贝塞尔曲线, 其他指令可查阅svg教程。

1
2
3
4
5
6
7
8
9
10

<svg xmlns="http://www.w3.org/2000/svg" width="1600" height="698">
<defs>
<linearGradient id="a" x1="50%" x2="50%" y1="-10.959%" y2="100%">
<stop stop-color="#ffffff" stop-opacity=".25" offset="0%"/>
<stop stop-color="#ffffff" offset="100%"/>
</linearGradient>
</defs>
<path fill="url(#a)" fill-rule="evenodd" d="M.005 121C311 121 409.898-.25 811 0c400 0 500 121 789 121v77H0s.005-48 .005-77z" transform="matrix(-1 0 0 1 1600 0)"/>
</svg>

创建4个div

1
2
3
4
5
6
<div className="ocean">
<div className="wave"></div>
<div className="wave"></div>
<div className="wave"></div>
<div className="wave"></div>
</div>

编写样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
.ocean {
opacity: .4;
height: 320px;
width: 100%;
position: absolute;
bottom: 0;
left: 0;
z-index: 1;
}


.wave {
background: url('../../assets/login/svg/wave.svg') repeat-x;
position: absolute;
top: 119px;
width: 12800px;
height: 318px;
animation: wave 4.6s cubic-bezier(0.36, 0.45, 0.63, 0.53) -.125s infinite;
transform: translate3d(220, 110, 0);
opacity: 1;
}
.wave:nth-of-type(2) {
top: 125px;
animation: wave 5.5s cubic-bezier(0.36, 0.45, 0.63, 0.53) -.125s infinite;
opacity: .6;
}
.wave:nth-of-type(3) {
top: 130px;
animation: wave 7s cubic-bezier(0.36, 0.45, 0.63, 0.53) -.125s infinite;
opacity: .4;
}
.wave:nth-of-type(4) {
top: 135px;
animation: wave 6s cubic-bezier(0.36, 0.45, 0.63, 0.53) infinite;
/* animation: wave 6s cubic-bezier(0.36, 0.45, 0.63, 0.53) -.125s infinite, swell 7s ease -1.15s infinite;*/
opacity: .3;
}
@keyframes wave {
0% {
margin-left: 0;
}
100% {
margin-left: -1600px;
}
}
@keyframes swell {
0%, 100% {
transform: translate3d(0, -115px, 0);
}
50% {
transform: translate3d(0, 5px, 0);
}
}

设定每个div的位置和动画, 动画wave表示波浪的作用位置,而且无限循环,动画swell表示波浪的变换。就是如此简单!

2、水泡的实现

创建单个水泡(ES6)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//单个水泡
createPop = (i) => {
let bottom = Math.round(Math.random() * 100).toFixed(2);
bottom = bottom > 55 ? 55 : bottom;
let left = Math.round(Math.random() * 100).toFixed(2);
let second = Math.random() * 10;
return (
<svg width="8px" height="8px" version="1.1"
id={`login-pop${i}`}
style={{
position: 'absolute',
bottom: `${bottom}%`,
left: `${left}%`,
animation: ` popup ${second}s cubic-bezier(0.36, 0.45, 0.63, 0.53) -.125s infinite`
}}
xmlns="http://www.w3.org/2000/svg">
<circle cx='4px' cy='4px' r="4"
fill='rgba(255,255,255,0.2)'/>
</svg>
);
};

创建svg画布,大小8,使用<circle>创建圆,半径是4。内联样式,规定布局位置是: absolute, 具体坐标是随机生成的,再加入动画,无限循环。

创建多个水泡

1
2
3
4
5
6
7
8
9
10
11
12
//生成指定个数的随机位置水泡
createRandomPops = () => {
let arr = [];
for (let i = 0; i < 40; i++) {
arr.push(i);
}
return arr.map((s, i) => {
return <div key={i}>
{this.createPop(i)}
</div>;
});
};

在Reactjs的渲染中调用

1
{this.createRandomPops()}

水泡的样式

1
2
3
4
5
6
7
8
@keyframes popup {
0%{
bottom: 16%
}
100%{
bottom: 55%;
}
}

水泡只需要向上冒即可,开始从底部16%的位置, 终止位置大约在页面中间,无限循环。

svg实现渐变进度条

效果图

  • 图中的进度条是由左右两个半圆组合而成。

代码实现(Reactjs)

绘制扇形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

/**
* 扇形绘图方法,计算终点的 x,y 坐标
*
* @param Number progr 进度值, 小于100的整数
* @param String direction 左半圆还是有半圆
* @param cx, cy, r默认的x、y坐标, r是半径
* @return 返回path中d指令参数
*/
sector = (progr = 0, direction = 'right', cy = 63, cx = 220, r = 156) => {
let progress = progr > 100 ? 100 : (progr < 0 ? 0 : progr);
// 计算当前的进度对应的角度值
const degrees = progress * (360 / 100);
// 计算当前角度对应的弧度值
let rad = degrees * (2 * Math.PI / 360);
let startY = cy;
let x2 = cx, //结束X点
y2 = cy; //结束Y点
// sweepFlag 1 顺时针,0逆时针
if (direction === 'right') {
//右边圆环
if (progress > 50) {
//多于50%,右边圆环满进度
x2 = cx;
y2 = startY + r * 2;
} else {
x2 = cx + r * Math.sin(rad);//计算x轴
y2 = startY + r - r * Math.cos(rad);//计算y轴
}
} else {
//左边圆环
//需要100-progress
if (progress > 50) {
const leftRad = (100 - progress) * (360 / 100) * (2 * Math.PI / 360);
//console.log(leftRad,811)
startY = startY + r * 2;
x2 = cx - r * Math.sin(leftRad);//计算x轴
y2 = startY - r - r * Math.cos(leftRad);//计算y轴
}
}
return ['M', cx, startY, 'A', r, r, 0, 0, 1, x2.toFixed(0), y2.toFixed(0)];
};

关键点:由于是动态的进度,x、y的值必须是动态计算,这里包含是数学知识是:在圆中根据角度计算坐标值。M指令是移动光标到初始位置的坐标, A指令是绘制一条曲线。

在React中定义svg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

<div className="progress-wrap">
<svg width="160" height="160" viewBox="0 0 440 440" className="center">
<defs>
<linearGradient x1="0" y1="0" x2="1" y2="1" id={'left' + idx}>
<stop offset="100%" style={{stopColor: colors.max}}></stop>
<stop offset="0%" style={{stopColor: colors.middle}}></stop>
</linearGradient>
<linearGradient x1="1" y1="1" x2="0" y2="0" id={'right' + idx}>
<stop offset="0%" style={{stopColor: colors.mini}}></stop>
<stop offset="100%" style={{stopColor: colors.max}}></stop>
</linearGradient>
<filter id="f1" x="0" y="0" height="200%" width="200%">
<feOffset result="offOut" in="SourceAlpha" dx="4" dy="4"/>
<feGaussianBlur result="blurOut" in="offOut" stdDeviation="8"/>
<feBlend in="SourceGraphic" in2="blurOut" mode="normal"/>
</filter>
<linearGradient id={'right-' + idx} gradientUnits="userSpaceOnUse" x1="220" y1="60" x2="220"
y2="375">
<stop offset="0" style={{stopColor: colors.mini}}></stop>
<stop offset="1" style={{stopColor: colors.middle}}></stop>
</linearGradient>
<linearGradient id={'left-' + idx} gradientUnits="userSpaceOnUse" x1="0" y1="60" x2="0"
y2="375">
<stop offset="0" style={{stopColor: colors.max}}></stop>
<stop offset="1" style={{stopColor: colors.middle}}></stop>
</linearGradient>
</defs>
<g>
/* 进度条的底色 */
<circle cx="220" cy="220" r="160" strokeWidth="50" stroke="#e5e8f8" fill="none"
strokeDasharray="1005 1005"></circle>
<path d={`${this.sector(percent).join(' ')}`} stroke={`url('#right-${idx}')`} fill="none"
strokeWidth="40"></path>
{percent > 50
&&
<path d={`${this.sector(percent, 'left').join(' ')}`} stroke={`url('#left-${idx}')`} fill="none"
strokeWidth="40"></path>}
<circle cx="220" cy="220" r="197" strokeWidth="40" stroke="#f5f6fa" fill="none"
strokeDasharray="1256 1256"></circle>
</g>

</svg>
<p className="percent-text " style={{color: colors.max}}>
<span className={'percent-number'}>
{percent}
</span>
<span className={'percent-symbol'}>
%
</span>
</p>
</div>

linearGradient是表示创建渐变;

g中的circle表示进度条的底色和外部包裹圆环;当进度大于50%时才创建左半圆,边框的宽度都是40px;

colors是预定义的几种颜色,用于渐变 :const colors = {max: '#3cc8bc', middle: '#27aea3', mini: '#9eece7'}

百分比显示在圆环中间。

总结

本文通过两个简单例子展示svg的强大之处。总的来说,svg不难, 只是涉及的知识比较多, 多实践,就熟练了。

×

纯属好玩

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. 一、入门
    1. 1.1. 概念
    2. 1.2. 特性
    3. 1.3. 基础形状
    4. 1.4. 使用方式
    5. 1.5. 使用总结
  2. 2. 二、实践
    1. 2.1. svg实现波浪、水泡效果
      1. 2.1.1. 先上效果图
      2. 2.1.2. 实现代码(Reactjs中)
        1. 2.1.2.1. 1、波浪的实现
    2. 2.2. svg实现渐变进度条
      1. 2.2.1. 效果图
      2. 2.2.2. 代码实现(Reactjs)
  3. 3. 总结