使用GSAP沿SVG路径梯度
#javascript #教程 #svg #gsap

如果您遇到过,您会知道我在说什么。在SVG中处理这种类型的梯度真的很烦人。

gradient along SVG path preview

查看最终编码器或遵循分步过程。

天然SVG方法

svg <path>,以及其他SVG笔触,例如<polygon><polyline>,只能使用linearGradientradialGradient进行颜色。

<!-- Here and below we're using the same pre-defined path -->
<svg width="0" height="0" viewBox="0 0 250 250">
    <defs>
        <path id="gradient-path" d="M36.5,91.2C-7.5,185.5,99.3,224.4,170,203.1c55-16.6,57.8-87.4,1.6-104C71,69.5,9.4,207.7,46,228.6c62.7,35.8,189.7-116,133-211"/>
    </defs>
</svg>

<!-- Coloring the path with native SVG gradient -->
<svg id="demo" viewBox="0 0 500 250">
    <defs>
        <linearGradient id="linear-grad">
            <stop offset="0" stop-color="#f7ff00"/>
            <stop offset="1" stop-color="#db36a4"/>
        </linearGradient>
        <radialGradient id="radial-grad">
            <stop offset="0" stop-color="#f7ff00"/>
            <stop offset="1" stop-color="#db36a4"/>
        </radialGradient>
    </defs>
    <use xlink:href="#gradient-path" stroke="url(#linear-grad)" fill="none" stroke-width="15"/>
    <use xlink:href="#gradient-path" stroke="url(#radial-grad)" x="250" fill="none" stroke-width="15"/>
</svg>

虽然两种梯度类型都有lots of settings,但颜色分布始终遵循直线。

native gradients preview

如果我们希望颜色沿曲线变化,我们需要CSS或JavaScript的帮助。这是一个普遍的问题,我们有一些解决方案。 D3.js库的创建者Mike Bostock已共享this方法。帕特里克·卡森(Patrick Cason)创建了一个基于Mike解决方案的small library,但没有D3.js依赖性。其他人涵盖了更具体的情况。例如,阿米特·希恩(Amit Sheen)发布了有关仅CSS的trick,以沿着圆形路径构建和动画梯度。


用GSAP创建梯度

作为A Big Fan GSAP的经常用户,我也喜欢贡献。

我们将使用GSAP及其MotionPathPlugin沿给定的<path>分发<circle>元素,并为这些圆圈涂色以组成梯度。

定义了参考<path>并添加both GSAP files后,我们创建元素,沿路径定位,然后对其应用梯度颜色。

<svg viewBox="0 0 250 250">
    <defs>
        <path id="gradient-path" d="M36.5,91.2C-7.5,185.5,99.3,224.4,170,203.1c55-16.6,57.8-87.4,1.6-104C71,69.5,9.4,207.7,46,228.6c62.7,35.8,189.7-116,133-211"/>
    </defs>
    <g class="dots">
        // to hold all the circles
    </g>
</svg>
const strokeWidth = 15;
const colors = ["#f7ff00", "#db36a4"];
const gradientPath = document.querySelector("#gradient-path");
// const numberOfDots = Math.ceil(gradientPath.getTotalLength() / strokeWidth); // for circles to be placed back-to-back
const dotsDensity = .5 * strokeWidth;
const numberOfDots = Math.ceil(dotsDensity * gradientPath.getTotalLength() / strokeWidth);

要找出可以使用getTotalLength()的圆数。该天然JS方法检索路径的总长度。如果我们将gradientPath.getTotalLength() / strokeWidth作为许多点,它们会背靠背。通过增加dotsDensity,我们可以获得一条平滑的线:

gsap gradients along the path

我们使用本机JS appendChild方法创建圆圈,并使用motionPath plugin定义圆的位置。

const dotsGroup = document.querySelector(".dots");
createBasicGradient(dotsGroup);

function createBasicGradient(g) {
    for (let idx = 0; idx < numberOfDots; idx++) {
        const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
        g.appendChild(circle);

        gsap.set(circle, {
            motionPath:
                path: gradientPath, // the target path
                start: idx / numberOfDots, // the position on target path
                end: idx / numberOfDots,
            },
            attr: {
                cx: 0, // the position is defined by transform attribute, so we keep circle center on (0, 0) point
                cy: 0,
                r: .5 * strokeWidth, // to compose strokeWidth
                fill: gsap.utils.interpolate(colors, (idx / numberOfDots)) // linear interpolation between 2 (or more!) given colors
            }
        });
    }
}

- 提示#1
我使用GSAP interpolate实用程序在两种颜色之间使用简单的线性插值。您可以轻松地为colors数组添加更多颜色,或构建自定义功能以计算(idx / numberOfDots)值的颜色。

,除非我们使用圆形linecap,否则我们可能需要修复行程技巧。

Masked and unmasked gradient

有不同的方法。例如,我们可以用原始路径掩盖点。

<path id="gradient-path" d="M36.5,91.2C-7.5,185.5,99.3,224.4,170,203.1c55-16.6,57.8-87.4,1.6-104C71,69.5,9.4,207.7,46,228.6c62.7,35.8,189.7-116,133-211"/>
<mask id="gradient-path-clip">
    <use xlink:href="#gradient-path" stroke-width="15" fill="none" stroke="white"/>
</mask>
...
<g mask="url(#gradient-path-clip)" class="dots">
</g>

沿行程的基本梯度完成了! ð

- 提示#2
您可以轻松地用另一种形状替换<circle>,并用矩形或其他形状构成梯度。如果这样做,您还将沿路径的粒子对齐:

    motionPath: {
        ...
        align: gradientPath,
        alignOrigin: [.5, .5],
        ...
    }

如果您用原始路径掩盖圆圈,并且路径不会相交,则可以增加点半径并降低点密度。表现更好。
increased radius size


高级技术

处理中风交叉点

假设我们希望我们的道路看起来像是一个适当的结,而中风会在底部“下方”。
我们可以重新排序点,并将“背”点附加到其他人面前。然后,我们使用重新装置索引代替原始顺序应用颜色和位置。

remapped

for (let idx = 0; idx < numberOfDots; idx++) {
    // append circles in normal order
    const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
    g.appendChild(circle);

    // remap the index to move the some of the back dots to the middle of stroke length
    let idxRemapped = idx;
    if (idx < .1 * numberOfDots) {
        idxRemapped += Math.ceil(.7 * numberOfDots); // [ .0 .. .1 ] to [ .7 .. .8 ]
    } else {
        if (idx < .8 * numberOfDots) {
            idxRemapped -= Math.ceil(.1 * numberOfDots); // [ .1 .. .8 ] to [ .0 .. .7 ]
        }
    }

    // apply position and color using idxRemapped
    gsap.set(circle, {
        // ...
    });
}

索引重新映射是特定路径的特定的,但希望您能得到这个想法:)

不同的中风宽度

构建动态路径宽度非常简单,我们只需要根据索引更改点大小即可。

dynamic path width

gsap.set(circle, {
    attr: {
        // ...
        r: .5 * strokeWidth + .02 * idx,
    }
})

,当然,我们可以使用更多的精美功能来计算点大小。

动画渐变

GSAP是一个动画平台,MotionPathplugin的设计是沿路径移动的事物。因此,我们可以轻松地对其进行动画动画,而不是设置点位置。

animated gradients

for (let idx = 0; idx < numberOfDots; idx++) {
    // create dot and set static attributes like before
    const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
    g.appendChild(circle);
    gsap.set(circle, {
        // motionPath: {
        //     path: gradientPath,
        //     start: idxRemapped / numberOfDots,
        //     end: idxRemapped / numberOfDots,
        // },
        attr: {
            cx: 0,
            cy: 0,
            r: .5 * strokeWidth,
            fill: gsap.utils.interpolate(colors, (idxRemapped / numberOfDots))
        }
    });

    // add position-on-path animation
    gsap.to(circle, {
        motionPath: {
            path: gradientPath // position along the path
        },
        duration: 2, // the time each dot takes to travel the whole path
        ease: "none",
        repeat: -1 // loop the animation
    }).progress(idx / numberOfDots); // each dot start moving from their own position
}

很容易将梯度动画与其他效果相结合。在某些情况下,您可能需要增加点密度或添加掩蔽。


,你有! ð
我收集了单个Codepen上的所有示例,订阅更新!