一、基本图形
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="400">
<rect x="10" y="10" width="150" height="100" rx="5" ry="5" stroke="red" fill="none"></rect>
<circle cx="250" cy="60" r="50" fill="none" stroke="red"></circle>
<ellipse cx="400" cy="60" rx="70" ry="50" fill="none" stroke="red"></ellipse>
<!-- 注意 line 的写法 x1 y1 x2 y2 -->
<!-- 错误写法 <line path="M10 120 L 160 220" stroke="red"></line>-->
<line x1="10" y1="120" x2="160" y2="220" stroke="red"></line>
<!-- 注意 polyline 的写法 polyline points-->
<!-- 错误写法 <line path="M200 220 L 300 220 250 120" stroke="red"></line>-->
<polyline points="200 220 300 220 250 120" stroke="red" fill="none"></polyline>
<!-- 注意 polygon 的 写法 points -->
<!-- 错误写法 <polygon path="M300 220 L 400 220 350 120" fill="yellow" stroke-width="3" stroke="red"></polygon>-->
<polygon points="350 220 450 220 400 120" fill="yellow" stroke-width="5" stroke="red"></polygon>
</svg>
基本操作API
- 创建图形
document.createElementNs(NS, tagName)
- 添加图形
element.appendChild(childElement)
- 设置/获取属性
element.setAttribute(name, value)
element.getAttribute(name)
二、颜色渐变和笔刷
hsla(h, s%, l%,a)
分别表示表示颜色、饱和度、亮度和透明度
• 取值范围: h: [0, 359] s, l: [0, 100] a[0,1]
linearGradient 和 radialGradient
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="500" viewbox="0 0 300 500">
<defs>
<!-- gradientUnits 默认单位是 objectBoundingBox ,可选 userSpaceOnUse 代表世界坐标系填充 -->
<linearGradient id="linearG" gradientUnits="objectBoundingBox" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stop-color="#f00"></stop>
<stop offset="1" stop-color="#00f"></stop>
</linearGradient>
<radialGradient id="radialG" cx="0.5" cy="0.5" r="0.5">
<stop offset="0" stop-color="rgba(255,0,0,0.8)"></stop>
<stop offset="0.5" stop-color="rgba(0,255,0,0.8)"></stop>
<stop offset="1" stop-color="rgba(0,0,255,0.8)"></stop>
</radialGradient>
</defs>
<rect x="10" y="10" width="200" height="100" fill="url(#linearG)"></rect>
<rect x="10" y="130" width="200" height="100" fill="url(#radialG)"></rect>
</svg>
pattern
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 600">
<defs>
<!-- patternUnits 默认单位是 objectBoundingBox (使用百分比),可选 userSpaceOnUse(使用具体数字) 代表世界坐标系填充 -->
<!-- patternContentUnits 默认单位是 userSpaceOnUse (使用具体数字),可选 objectBoundingBox (使用百分比) -->
<pattern id="p1" x="0" y="0" width="20%" height="20%" patternUnits="objectBoundingBox" patternContentUnits="userSpaceOnUse">
<circle cx="10" cy="10" r="5" fill="red"></circle>
<polygon points="30 10 60 50 0 50" fill="green"></polygon>
</pattern>
<pattern id="p2" x="0" y="0" width="20%" height="20%" patternUnits="objectBoundingBox" patternContentUnits="objectBoundingBox">
<circle cx="0.1" cy="0.1" r="0.1" fill="red"></circle>
</pattern>
</defs>
<rect x="100" y="100" width="300" height="200" fill="url(#p1)" stroke="blue"></rect>
<rect x="100" y="310" width="300" height="200" fill="url(#p2)" stroke="blue"></rect>
</svg>
三、path路径
命令 | 含义 |
---|---|
M/m (x,y)+ | 移动当前位置(后面如果有重复参数,会当做 L 命令) |
L/l (x,y)+ | 从当前位置绘制线段到指定位置 |
H/h (x)+ | 从当前位置绘制水平线到达指定的 x 坐标 |
V/v (y)+ | 从当前位置绘制竖直线到达指定的 y 坐标 |
Z/z | 闭合当前路径 |
Q/q (x1,y1,x,y)+ | 从当前位置绘制二次贝塞尔曲线到指定位置 |
T/t (x,y)+ | 从当前位置光滑绘制二次贝塞尔曲线到指定位置(省略第一个控制点) |
C/c (x1,y1,x2,y2,x,y)+ | 从当前位置绘制三次贝塞尔曲线到指定位置 |
S/s (x2,y2,x,y)+ | 从当前位置光滑绘制三次贝塞尔曲线到指定位置 (省略第一个控制点) |
A/a (rx,ry,xr,laf,sf,x,y) | 从当前位置绘制弧线到指定位置 |
- 区分大小写:大写表示坐标参数为绝对位置,小写则为相对位置
- 最后的参数表示最终要到达的位置
- 上一个命令结束的位置就是下一个命令开始的位置
- 命令可以重复参数表示重复执行同一条命令
贝塞尔曲线
S/s (x2,y2,x,y)+
是省略了第一个控制点坐标
四、svg文本
文本对齐
- 水平对齐
text.setAttribute('text-anchor', 'start') // start/center/end
-
垂直对齐
需要用dy
模拟
<select id="select">
<option value="top">top</option>
<option value="middle">middle</option>
<option value="bottom">bottom</option>
</select>
<svg width="500" height="300">
<path stroke="green" d="M 0 100.5 500 100.5 M140 0 v200"></path>
<text id="text" x="140" y="100" fill="red" font-size="50">慕课网</text>
<rect id="rect" stroke="blue" fill="none"></rect>
</svg>
<script>
select.addEventListener('input', function () {
text.setAttribute('dy', 0);
var dy = getAlignmentDy(select.value);
text.setAttribute('dy', dy);
})
function getAlignmentDy (value) {
var box = text.getBBox(); // 使用getBBox获取文本区域 SVGRect {x: 140, y: 47, width: 150, height: 66}
var y = +text.getAttribute('y');
switch (value) {
case 'top':
return y - box.y;
case 'middle' :
return y - (box.y + box.height / 2);
case 'bottom':
return y - (box.y + box.height);
}
}
</script>
textSin
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<style>
html, body {
height: 100%;
}
</style>
<body>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
<defs>
<pattern id="grid" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
<path stroke="#F0F0F0" fill="none" d="M0,0H20V20"></path>
</pattern>
</defs>
<rect width="1200" height="1000" fill="url(#grid)"></rect>
<text id="sintext" x="100" y="160" style="font-size: 14px;font-family: Arial"></text>
<path d="M100,0V200M0,100H200" transform="translate(0,60)" stroke="red"></path>
</svg>
<script>
var NS = 'http://www.w3.org/2000/svg';
var text = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
var n = text.length;
var x = [];
var y = null;
var i = n;
var s = 100;
var w = 0.4;
var t = 0;
while (i--) {
x.push(10)
var tspan = document.createElementNS(NS, 'tspan');
var index = n - i - 1
tspan.textContent = text[index]; // node.textContent 生效 HTMLElement.innerText 不生效
var h = Math.round(360 / n * index)
tspan.setAttribute('fill', 'hsl(' + h + ', 100%, 80%)');
sintext.appendChild(tspan)
}
function arrange (t) {
y = [];
var ly = 0, cy;
for (i = 0; i < n; ++i) {
cy = -s * Math.sin(w * i + t); // cy 当前值
y.push(cy - ly); // 存储差距
ly = cy; // ly 上一个值
}
}
function render () {
sintext.setAttribute('dx', x.join(' '))
sintext.setAttribute('dy', y.join(' '))
}
function frame () {
t += 0.02;
arrange(t);
render();
requestAnimationFrame(frame)
}
frame()
</script>
</body>
</html>
textPath
<textPath xlink:href="#path1" startOffset="50%">
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>路径文本</title>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="600">
<path id="path1" d="M 100 200 Q 200 100 300 200 T 500 200" stroke="rgb(0,255,0)" fill="none">
</path>
<text style="font-size:24px;" text-anchor="middle">
<!-- textPath xlink:href #path1 -->
<textPath xlink:href="#path1" startOffset="50%">
<tspan>这个文字先</tspan>
<tspan dx="20" dy="-30" fill="blue">上去,</tspan>
<tspan dx="20" dy="30">又</tspan>
<tspan dy="30" fill="red">下来</tspan>
<tspan dy="-30">了。</tspan>
</textPath>
</text>
</svg>
</body>
</html>
五、图形引用裁剪、蒙版
<use> 标签创建图形引用
<use xlink:href="#id"></use>
<clipPath> 标签裁切图形
<clipPath id="clip-id"></clipPath>
clip-path="url(#clip-id)"
<mask>标签创建蒙版
<mask id="mask-id"></mask>
mask="url(#mask-id)"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>starSky</title>
</head>
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: #001122;
line-height: 0;
font-size: 0;
}
</style>
<body>
<svg width="100%" height="100%" viewBox="-400 -300 800 600" preserveAspectRatio="xMidYMid slice">
<defs>
<polygon id="star" points="0 -10 2 -2 10 0 2 2 0 10 -2 2 -10 0 -2 -2" fill="white"></polygon>
</defs>
<g id="real">
<g id="star-group"></g>
<g id="light-tower" transform="translate(250, 0)">
<defs>
<!-- 注意linearGradient 写法 x1 y1 x2 y2-->
<linearGradient id="tower" x1="0" y1="0" x2="1" y2="0">
<!-- 注意stop offset stop-color 用法 -->
<stop offset="0" stop-color="#999"></stop>
<stop offset="1" stop-color="#333"></stop>
</linearGradient>
<radialGradient id="light" cx="0.5" cy="0.5" r="0.5">
<stop offset="0" stop-color="rgba(255,255,255,0.8)"></stop>
<stop offset="1" stop-color="rgba(255,255,255,0)"></stop>
</radialGradient>
<clipPath id="light-clip">
<polygon points="0 0 -400 -15 -400 15">
<animateTransform
attributeType="XML"
attributeName="transform"
type="rotate"
from="0"
to="360"
dur="10s"
repeatCount="indefinite"
>
</animateTransform>
</polygon>
</clipPath>
</defs>
<!-- 注意两个l ellipse -->
<!-- 注意clip-path="url(#)" -->
<ellipse cx="0" cy="0" rx="300" ry="100" fill="url(#light)" clip-path="url(#light-clip)"></ellipse>
<circle cx="0" cy="0" r="2" fill="white"></circle>
<!-- 注意fill 里 需要写 url(# ) -->
<polygon points="0 0 5 50 -5 50" fill="url(#tower)"></polygon>
</g>
<g id="moon-group">
<mask id="moon-mask">
<circle r="50" cx="-300" cy="-100" fill="white"></circle>
<circle r="50" cx="-270" cy="-120" fill="black"></circle>
</mask>
<circle r="50" cx="-300" cy="-100" fill="yellow" mask="url(#moon-mask)"></circle>
</g>
</g>
<g id="reflect" mask="url(#fading)">
<defs>
<linearGradient id="fade" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="rgba(255,255,255,0.3)"></stop>
<stop offset="0.5" stop-color="rgba(255,255,255,0)"></stop>
</linearGradient>
<mask id="fading">
<rect x="-400" y="50" width="800" height="300" fill="url(#fade)"></rect>
</mask>
</defs>
<use xlink:href="#real" transform="translate(0 100) scale(1,-1)"></use>
</g>
<line x1="-400" y1="50" x2="400" y2="50" stroke="white"></line>
</svg>
<script>
var SVG_NS = 'http://www.w3.org/2000/svg';
var XLINK_NS = 'http://www.w3.org/1999/xlink'
renderStar();
function use (origin) {
var _use = document.createElementNS(SVG_NS, 'use');
// 注意这里是 XLINK_NS
_use.setAttributeNS(XLINK_NS, 'xlink:href', '#' + origin.id)
return _use
}
function random (min, max) {
return min + (max - min) * Math.random();
}
function renderStar () {
var starRef = document.getElementById('star');
var starGroup = document.getElementById('star-group');
var starCount = 500;
var star;
while (starCount--) {
star = use(starRef);
star.setAttribute('opacity', random(0.1, 0.4));
star.setAttribute('transform',
'translate(' + random(-400, 400) + ',' + random(-300, 40) + ')' +
'scale(' + random(0.1, 0.6) + ')');
starGroup.appendChild(star)
}
}
</script>
</body>
</html>
六、SVG 动画
animate
<svg xmlns="http://www.w3.org/2000/svg">
<rect x="100" y="100" width="100" height="100" fill="red">
<!-- attributeType 可取值 auto XML CSS ,auto时浪费性能,最好直接指定 -->
<!-- fill 默认值remove,代表结束后移除,回到开始位置。可选值freeze,动画结束后停在那里 -->
<!-- repeatCount 动画循环次数,取值为数字,可选值 indefinite 代表无限循环 -->
<!-- begin 动画开始的条件 -->
<animate id="goright"
attributeType="XML"
attributeName="x"
begin="0;goleft.end + 1s"
from="100"
to="500"
dur="3s"
fill="freeze"
>
</animate>
<animate id="goleft"
attributeType="XML"
attributeName="x"
begin="goright.end + 1s"
from="500"
to="100"
dur="3s"
fill="freeze"
>
</animate>
<animate
attributeType="XML"
attributeName="fill"
from="red"
to="yellow"
dur="6s"
fill="freeze"
></animate>
</rect>
</svg>
animateTransform
<svg viewBox="-400 -400 800 800" xmlns="http://www.w3.org/2000/svg">
<rect x="100" y="100" width="100" height="100" fill="red">
<!-- type 是transform的值 -->
<animateTransform
id="scale"
attributeType="XML"
attributeName="transform"
type="scale"
from="1"
to="1.5"
dur="3s"
fill="freeze"
>
</animateTransform>
</rect>
</svg>
animateMotion
<mpath xlink:href="#pathId"></mpath>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
html, svg, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<svg viewBox="-400 -400 800 800">
<rect x="-25" y="-25" width="50" height="50" fill="rgba(0,255,255,0.6)">
<!-- 可在 animateMotion 上 加 path 属性,代表移动轨迹 -->
<!--
<animateMotion
path="M 0 0L 100 100A200 200 0 1 0 0 -100"
rotate="auto"
dur="3s"
fill="freeze"
>
</animateMotion>
-->
<!-- 注意 mpath 上 xlink:href 里不带url -->
<animateMotion
rotate="auto"
dur="3s"
fill="freeze"
>
<mpath xlink:href="#motion-path"></mpath>
</animateMotion>
</rect>
<path id="motion-path" d="M 0 0L 100 100A200 200 0 1 0 0 -100" stroke="gray" fill="none"></path>
</svg>
</body>
</html>
自己做的小demo
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Title</title>
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
#svg {
width: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 1;
}
#btn {
position: absolute;
z-index: 10;
}
</style>
</head>
<body>
<button onclick="run()" id="btn">冲鸭</button>
<svg xmlns="http://www.w3.org/2000/svg" id="svg" viewBox="0 0 251 440">
<!-- 路径 -->
<g>
<path id="motion-path" class="st0" d="M208,290.5c-10.4,6.9-19.8,12.5-27.5,17c-4.3,2.5-11.9,6.5-27,14.5c-19.7,10.5-21.7,11.3-25.5,11.5
c-3.7,0.2-6.2-0.5-27.5-10c-15.9-7.1-24-10.7-28.5-13.5c-12.8-7.8-11.8-10-23.5-17c-16.8-10-27-10.2-27-14.5c0-3.8,8.1-5,28.5-15
c9.4-4.6,17.6-9.2,22.5-12c20.8-11.7,21.1-14,26.5-14c7.1,0,15,6.1,30.5,18.5c14.5,11.6,15.5,14.5,21,15c6.1,0.6,8.3-2.6,29.5-15.5
c11.7-7.1,17.6-10.7,23.5-14c18-10.1,22.5-11,23-15c0.6-5.3-6.9-7.7-23.5-22c-7.8-6.7-5.4-5.5-23-22c-9.1-8.5-14.5-13.5-21.5-19.5
c-7.1-6-17.1-14.1-30-23" stroke="red" fill="none"/>
</g>
<!-- 辅助点 -->
<defs>
<circle cx="0" cy="0" r="3" fill="black" stroke="black" id="sign"></circle>
</defs>
<g id="sign_wrap"></g>
<!-- 需要运动的小球 -->
<circle cx="0" cy="0" r="5" fill="red" stroke="red" class="ball"></circle>
</svg>
<script>
var SVG_NS = 'http://www.w3.org/2000/svg';
var XLINK_NS = 'http://www.w3.org/1999/xlink';
var ball = document.querySelector('.ball');
var path = document.querySelector('#motion-path')
var len = path.getTotalLength();
var pointNum = 20; // 需要设置的点的总个数
var endPosition = 0; // 当前位置
var target = 0; // 随机走步数后的目前位置
var oneLen = len / (pointNum - 1) // 两个点之间的间隔长度
var step = oneLen / 20 // 每一步的长度
createPoint() // 生成参考点
frame(); // 小球运动动画
function random (min, max) {
return min + (max - min) * Math.random();
}
function createUse (target) {
var _use = document.createElementNS(SVG_NS, 'use')
_use.setAttributeNS(XLINK_NS, 'xlink:href', '#' + target.id)
return _use
}
function run () {
var max = Math.ceil((len - endPosition) / oneLen)
max = max > 4 ? 4 : (max >= 1 ? max : '');
if (max) {
target = endPosition + parseInt(random(1, max)) * oneLen;
frame();
}
}
function frame () {
var point = path.getPointAtLength(endPosition);
ball.setAttribute('cx', point.x)
ball.setAttribute('cy', point.y)
var diff = target - endPosition
if (diff > 0) {
endPosition += diff > step ? step : diff
requestAnimationFrame(frame);
}
}
function createPoint () {
var sign = document.getElementById('sign')
var pointsWrap = document.getElementById('sign_wrap')
var cSign;
for (var i = 0; i < pointNum; i++) {
var point = path.getPointAtLength(len * i / (pointNum - 1));
cSign = createUse(sign)
cSign.setAttribute('transform', 'translate(' + point.x + ',' + point.y + ')');
pointsWrap.appendChild(cSign)
}
}
</script>
</body>
</html>