走进SVG-学习笔记

一、基本图形

<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) 从当前位置绘制弧线到指定位置
  1. 区分大小写:大写表示坐标参数为绝对位置,小写则为相对位置
  2. 最后的参数表示最终要到达的位置
  3. 上一个命令结束的位置就是下一个命令开始的位置
  4. 命令可以重复参数表示重复执行同一条命令
贝塞尔曲线

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>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容