第 8 章 图案和渐变

图案

要使用图案,首先要定义一个水平或者垂直方向重复的图形对象,然后用它填充另一个对象或作为画笔使用。这个图形对象称为 tile (瓷砖),因为使用图案填充对象的行为很像在地面上铺瓷砖的过程

本节使用如下曲线作为图案

<!-- @format -->

<svg>
    <path d="M 0 0 Q 5 20 10 10 T 20 20" style="stroke: black; fill: none" />
    <path d="M 0 0 h20 v20 h-20 z" style="stroke: gray; fill: none" />
</svg>

patternUnits

要创建一个图案,必须使用 <pattern> 元素包裹描述图案的 <path> 元素,然后设置相应属性

用于设置如何排列图案

  • 设置其值为 objectBoundingBox(默认值),指定图案左上角坐标,以及宽高(百分比或者 0-1 之间的小数)。结果:当被填充对象(此处为矩形)的尺寸不足时,会发生裁剪,当尺寸过大时,会由空隙
  • 设置其值为 userSpaceOnUse,指定图案左上角坐标,以及宽高(按用户单位)。结果:图案之间会紧密贴合,裁剪只发生在被填充对象(此处为矩形)的边缘

objectBoundingBox 效果

userSpaceOnUse 效果

patternContentUnits

确定用什么单位表达图案数据本身

  • userSpaceOnUse:默认值
  • objectBoundingBox:路径本身的数据点会被基于被填充的对象来确定

objectBoundingBox 效果

注意下面源码中的小数

        <pattern
            id="tile"
            patternUnits="objectBoundingBox"
            patternContentUnits="objectBoundingBox"
            x="0"
            y="0"
            width=".2"
            height=".2"
        >
            <path
                d="M 0 0 Q .05 .20 .10 .10 T .20 .20"
                style="stroke: black; fill: none; stroke-width: 0.01"
            />
            <path
                d="M 0 0 h 0.2 v 0.2 h-0.2z"
                style="stroke: black; fill: none; stroke-width: 0.01"
            />
        </pattern>
  • 如果使用 userSpaceOnUse,则图案的边界框左上角应该在原点位置
  • 如果使用 objectBoundingBox,则需要在图案中减小 stroke-width 的值。图案的宽度也会以被填充对象的边界框作为参考,而不会使用用户单位,因此 stroke-width 为 1 会覆盖整个图案。在本例中,笔画宽度被设置为 0.01,也就是被填充对象边界框宽度和高度均值的 1%

结论:patternUnitspatternContentUnits 都使用 userSpaceOnUse 是最方便且符合常规认知的做法

如果想缩小现有的图形对象当作图案

  • 使用 viewBox 属性来缩放更容易。指定 viewBox 会覆盖任何 patternContentUnits 信息
  • 设置 preserveAspectRatio 属性

图案嵌套

图案也可以嵌套,和标记嵌套完全不同(很少需要嵌套标记),如果不使用图案嵌套,某些效果很难实现

渐变

可以使用渐变填充对象,可以是线性也可以是径向

linearGradient 元素

在特定的位置指定想要的颜色,被称为渐变点(gradient stop)。渐变点是渐变结构的一部分,颜色是表现的一部分

<!-- @format -->

<svg>
    <defs>
        <linearGradient id="two_hues">
            <stop offset="0%" style="stop-color: #ffcc00" />
            <stop offset="100%" style="stop-color: #0099cc" />
        </linearGradient>
    </defs>
    <rect
        x="20"
        y="20"
        width="200"
        height="100"
        style="fill: url(#two_hues); stroke: black"
    />
</svg>

<stop> 元素

  • offset:必须。用于确定线上哪个点的颜色应该等于 stop-color。使用百分数或 0-1 之间的小数表示。0% 和 100% 位置设置渐变点不是必须的,但是通常都会设置
  • stop-color:必须。被指定在 style 中,但是也可以指定为独立属性
  • stop-opacity:可选。用于指定颜色的不透明度

定义渐变方向

默认是沿着水平线从左往右,若想改变方法,可设置渐变的起终点坐标,默认情况下也是使用百分数或小数指定

  • x1:起点 x 坐标
  • y1:起点 y 坐标
  • x2:终点 x 坐标
  • y2:终点 y 坐标
<!-- @format -->

<svg width="100%" height="200">
    <defs>
        <linearGradient id="three_stops">
            <stop offset="0%" style="stop-color: #ffcc00" />
            <stop offset="33.3%" style="stop-color: #cc6699" />
            <stop offset="100%" style="stop-color: #66cc99" />
        </linearGradient>
        <linearGradient
            id="right_to_left"
            xlink:href="#three_stops"
            x1="100%"
            y1="0%"
            x2="0%"
            y2="0%"
        />
        <linearGradient
            id="down"
            xlink:href="#three_stops"
            x1="0%"
            y1="0%"
            x2="0%"
            y2="100%"
        />
        <linearGradient
            id="up"
            xlink:href="#three_stops"
            x1="0%"
            y1="100%"
            x2="0%"
            y2="0%"
        />
        <linearGradient
            id="diagonal"
            xlink:href="#three_stops"
            x1="0%"
            y1="0%"
            x2="100%"
            y2="100%"
        />
    </defs>
    <rect
        x="40"
        y="20"
        width="200"
        height="40"
        style="fill: url(#three_stops); stroke: black"
    />
    <rect
        x="40"
        y="70"
        width="200"
        height="40"
        style="fill: url(#right_to_left); stroke: black"
    />

    <rect
        x="250"
        y="20"
        width="40"
        height="200"
        style="fill: url(#down); stroke: black"
    />
    <rect
        x="300"
        y="20"
        width="40"
        height="200"
        style="fill: url(#up); stroke: black"
    />
    <rect
        x="40"
        y="120"
        width="200"
        height="100"
        style="fill: url(#diagonal); stroke: black"
    />
</svg>

如果想要使用用户坐标空间而不是百分比指定渐变方向,设置 gradientUnitsuserSpaceOnUse,而不是默认值 objectBoundingBox 即可

spreadMethod 属性

指定过渡方向不一定要从一角到另一角,如果指定从 (20%, 30%)(40%, 80%),对于指定范围之外的颜色,则可以通过设置 spreadMethod 属性的值来选择不同的效果

  • pad:起始和结束渐变点会扩展到对象的边缘
  • repeat:渐变会重复起点到终点的过程,直到填充满整个对象
  • reflect:渐变会按终点到起点、起点到终点的排列重复,直到填充满整个对象
<!-- @format -->

<svg width="100%">
    <defs>
        <linearGradient id="partial" x1="20%" y1="30%" x2="40%" y2="80%">
            <stop offset="0%" style="stop-color: #ffcc00" />
            <stop offset="33.3%" style="stop-color: #cc6699" />
            <stop offset="100%" style="stop-color: #66cc99" />
        </linearGradient>
        <linearGradient id="padded" xlink:href="#partial" spreadMethod="pad" />
        <linearGradient
            id="repeated"
            xlink:href="#partial"
            spreadMethod="repeat"
        />
        <linearGradient
            id="reflected"
            xlink:href="#partial"
            spreadMethod="reflect"
        />
        <line
            id="show-line"
            x1="20"
            y1="30"
            x2="40"
            y2="80"
            style="stroke: white"
        />
    </defs>
    <rect
        x="20"
        y="20"
        width="100"
        height="100"
        style="fill: url(#padded); stroke: black"
    />
    <use xlink:href="#show-line" transform="translate(20, 20)" />
    <rect
        x="130"
        y="20"
        width="100"
        height="100"
        style="fill: url(#repeated); stroke: black"
    />
    <use xlink:href="#show-line" transform="translate(130, 20)" />
    <rect
        x="240"
        y="20"
        width="100"
        height="100"
        style="fill: url(#reflected); stroke: black"
    />
    <use xlink:href="#show-line" transform="translate(240, 20)" />
</svg>

radialGradient 元素

每一个渐变点表示一个圆形路径,从中心点向外扩散,如果填充对象的边界框不是正方形,过渡路径会变成椭圆来匹配边界框的长宽比,它的设置方式和线性渐变大致相同

<!-- @format -->

<svg width="100%">
    <defs>
        <radialGradient id="three_stops">
            <stop offset="0%" style="stop-color: #f96" />
            <stop offset="50%" style="stop-color: #9c9" />
            <stop offset="100%" style="stop-color: #906" />
        </radialGradient>
    </defs>
    <rect
        x="20"
        y="20"
        width="100"
        height="100"
        style="fill: url(#three_stops); stroke: black"
    />
</svg>

定义径向渐变的范围

  • cx:中心点 x 坐标。默认值为 50%(对象边界框的水平中心点)
  • cy:中心点 y 坐标。默认值为 50%(对象边界框的垂直中心点)
  • r:半径。。默认值为 50%(对象边界框宽度/高度的一半)

所有这些属性值都是对象外边框的百分比,默认值都为 50%

<!-- @format -->

<svg width="100%">
    <defs>
        <radialGradient id="center_origin" cx="0%" cy="0%" r="141%">
            <stop offset="0%" style="stop-color: #f96" />
            <stop offset="50%" style="stop-color: #9c9" />
            <stop offset="100%" style="stop-color: #906" />
        </radialGradient>
    </defs>
    <rect
        x="20"
        y="20"
        width="100"
        height="100"
        style="fill: url(#center_origin); stroke: black"
    />
</svg>

上面例子中,radialGradientr 被设置为 141%,而不是 100%,是因为用来测量半径的单位是对象边界框宽度和高度的平均值,而不是框的对角线,正方形对角线与边长的比例是 2 的平方根,即 1.41

  • fxfy:焦点坐标。默认值和cxcy一样,0% 点所在位置,也被称为焦点,默认为 100% 处渐变点所在圆的圆心
<!-- @format -->

<svg width="100%">
    <defs>
        <radialGradient
            id="focal_set"
            cx="0%"
            cy="0%"
            fx="50%"
            fy="50%"
            r="100%"
        >
            <stop offset="0%" style="stop-color: #f96" />
            <stop offset="50%" style="stop-color: #9c9" />
            <stop offset="100%" style="stop-color: #906" />
        </radialGradient>
    </defs>
    <rect
        x="20"
        y="20"
        width="100"
        height="100"
        style="fill: url(#focal_set); stroke: black"
    />
</svg>

如果想用用户空间坐标而不是百分比确定圆的范围,要设置 gradientUnitsuserSpaceOnUse,而不是默认值 objectBoundingBox

径向渐变的 spreadMethod 属性

与线性渐变的一样

变换图案和渐变

有时候可能需要斜切、拉伸、旋转图案或者渐变,此时无需变换填充对象,而是变换用来填充对象的图案或者渐变。可以使用 gradientTransformpatternTransform 属性来实现

<!-- @format -->

<svg width="100%">
    <defs>
        <pattern
            id="tile"
            x="0"
            y="0"
            width="20%"
            height="20%"
            patternUnits="objectBoundingBox"
        >
            <path
                d="M 0 0 Q 5 20 10 10 T 20 20"
                style="stroke: black; fill: none"
            />
            <path
                d="M 0 0 h 20 v 20 h -20 z"
                style="stroke: gray; fill: none"
            />
        </pattern>
        <pattern
            id="skewed-tile"
            patternTransform="skewY(15)"
            xlink:href="#tile"
        />
        <linearGradient id="plain">
            <stop offset="0%" style="stop-color: #ffcc00" />
            <stop offset="33.3%" style="stop-color: #cc6699" />
            <stop offset="100%" style="stop-color: #66cc99" />
        </linearGradient>
        <linearGradient
            id="skewed-gradient"
            gradientTransform="skewX(10)"
            xlink:href="#plain"
        />
    </defs>
    <rect
        x="20"
        y="10"
        width="100"
        height="100"
        style="fill: url(#tile); stroke: black"
    />
    <rect
        x="135"
        y="10"
        width="100"
        height="100"
        style="fill: url(#skewed-tile); stroke: black"
    />
    <rect
        x="20"
        y="120"
        width="200"
        height="50"
        style="fill: url(#plain); stroke: black"
    />
    <rect
        x="20"
        y="190"
        width="200"
        height="50"
        style="fill: url(#skewed-gradient); stroke: black"
    />
</svg>

渐变和图案也可以应用于 stroke,这样子就生成了彩色或图案描边,此时通常会设置 stroke-width 为大于 1 的数字

在添加 stroke 的时候,大小是基于 objectBoundingBox 来计算的。因为水平线和垂直线的边界框的宽度或高度默认为0,所以当图案和渐变作用于这些线条的时候,使用 objectBoundingBox 单位的渐变或者图案会被忽略,意味着它们不会被绘制出来,除非在 style 中指定备选笔画值,如 style="stroke: url(#rainbow) red;"

如果图案或渐变定义在独立的文件中,设置备用填充和画笔是好习惯,以防文件不能加载或者 SVG 阅读器不支持外部引用

Last Updated:
Contributors: af