【问题标题】:Transitioning the gradient fill of a single node among many在多个节点之间转换单个节点的渐变填充
【发布时间】:2019-08-14 01:02:06
【问题描述】:

假设我有一个结构与此类似的 SVG:

<svg>
    <defs>
        <linearGradient id="gradient-red">...</linearGradient>
        <linearGradient id="gradient-blue">...</linearGradient>
    </defs>
    <g class="node">
        <circle r="50" style="fill: url('#gradient-red');"></circle>
    </g>
    <g class="node">
        <circle r="100" style="fill: url('#gradient-red');"></circle>
    </g>
    <g class="node">
        <circle r="150" style="fill: url('#gradient-red');"></circle>
    </g>
    <g class="node">
        <circle r="200" style="fill: url('#gradient-red');"></circle>
    </g>
    <g class="node">
        <circle r="250" style="fill: url('#gradient-red');"></circle>
    </g>
</svg>

我现在有五个带有红色渐变的圆圈。我了解如何更改选定节点的颜色——我只是将其作为目标(通过d3.select)并将其样式更改为'fill', 'url("#gradient-blue")。但是我将如何转换那个节点的渐变填充从红色到蓝色?

这样的事情不会导致补间/过渡,而是会导致即时颜色交换:

d3.transition().duration(1000)
    .tween('start', () => {
        let test = d3.select(currentTarget);
        test.transition().duration(1000).style('fill', 'url("#gradient-blue")');

如果我要转换渐变本身的 stop-color,它会改变 所有 的节点/圆(因为你正在改变 &lt;defs&gt; )。

我做错了什么?

【问题讨论】:

    标签: javascript animation d3.js svg gradient


    【解决方案1】:

    Transition 的插值

    在 D3 中,转换基本上将起始值插入到结束值。如果我们对数字进行插值,这很容易证明。例如,让我们从50 过渡到2000

    const interpolator = d3.interpolate(50, 2000);
    d3.range(0, 1.05, 0.05).forEach(function(d) {
      console.log(interpolator(d))
    })
    &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"&gt;&lt;/script&gt;

    我们还可以插入字符串:

    const interpolator = d3.interpolate("March, 2000", "March, 2020");
    d3.range(0, 1.05, 0.05).forEach(function(d) {
      console.log(interpolator(d))
    })
    &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"&gt;&lt;/script&gt;

    问题

    现在,让我们看看你的情况:你想从中插值:

    • url("#gradient-red")

    到这里:

    • url("#gradient-blue")

    这里有哪些可能的中间体?你能看出这是不可能的吗?证明如下:

    const interpolator = d3.interpolate("url(#gradient-red)", "url(#gradient-blue)");
    d3.range(0, 1.1, 0.1).forEach(function(d) {
      console.log(interpolator(d))
    })
    &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"&gt;&lt;/script&gt;

    如您所见,第一个插值将立即导致最终值。

    可能的解决方案

    最明显的解决方案是插入停止颜色。但是,正如您刚刚发现的那样,这将改变所有圆的渐变。

    因此,天真的解决方法是创建多个渐变,每个圆圈一个,具有唯一的 ID。虽然这可能是 3 或 4 个圆圈的适当解决方案,但如果您有数十或数百个元素,这显然不是一个聪明的解决方案。

    话虽如此,这是我的建议:

    1. 创建一个临时渐变,让我们给它 ID #gradient-temporary,就像红色的一样。
    2. 然后,当您选择(或以某种方式过滤)一个圆圈时,将其填充从"url(#gradient-red)" 更改为"url(#gradient-temporary)"。这种变化是立竿见影的,在屏幕上没有明显的效果。
    3. 在此临时渐变的停止颜色上进行过渡。
    4. 过渡完成后,将圆圈的填充从"url(#gradient-temporary)" 更改为"url(#gradient-blue)"。同样,这是即时的。此外,将临时渐变的停止颜色改回红色。

    这样,你可以有数百个圆圈,但你只需要 3 个渐变来过渡它们。

    这是一个使用这种方法的演示,点击每个圆圈来转换它:

    const circles = d3.selectAll("circle");
    circles.on("click", function() {
      const element = this;
      d3.select(element).style("fill", "url(#gradient-temporary)");
      d3.select("#gradient-temporary").select("stop:nth-child(2)")
        .transition()
        .duration(1000)
        .style("stop-color", "rgb(0,0,255)")
        .on("end", function() {
          d3.select(element).style("fill", "url(#gradient-blue)");
          d3.select("#gradient-temporary").select("stop:nth-child(2)")
            .style("stop-color", "rgb(255,0,0)")
        })
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
    <svg>
        <defs>
            <linearGradient id="gradient-red" x1="0%" y1="0%" x2="100%" y2="0%">
                <stop offset="0%" style="stop-color:rgb(211,211,211);stop-opacity:1" />
                <stop offset="100%" style="stop-color:rgb(255,0,0);stop-opacity:1" />
            </linearGradient>
            <linearGradient id="gradient-temporary" x1="0%" y1="0%" x2="100%" y2="0%">
                <stop offset="0%" style="stop-color:rgb(211,211,211);stop-opacity:1" />
                <stop offset="100%" style="stop-color:rgb(255,0,0);stop-opacity:1" />
            </linearGradient>
            <linearGradient id="gradient-blue" x1="0%" y1="0%" x2="100%" y2="0%">
                <stop offset="0%" style="stop-color:rgb(211,211,211);stop-opacity:1" />
                <stop offset="100%" style="stop-color:rgb(0,0,255);stop-opacity:1" />
            </linearGradient>
        </defs>
        <g class="node">
            <circle r="20" cx="20" cy="70" style="fill: url('#gradient-red');"></circle>
        </g>
        <g class="node">
            <circle r="20" cx="80" cy="70" style="fill: url('#gradient-red');"></circle>
        </g>
        <g class="node">
            <circle r="20" cx="140" cy="70" style="fill: url('#gradient-red');"></circle>
        </g>
        <g class="node">
            <circle r="20" cx="200" cy="70" style="fill: url('#gradient-red');"></circle>
        </g>
        <g class="node">
            <circle r="20" cx="260" cy="70" style="fill: url('#gradient-red');"></circle>
        </g>
    </svg>

    【讨论】:

    • 我对这个答案的质量和全面性感到非常震惊。太棒了,先生,谢谢你。这成功了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-03
    • 1970-01-01
    • 1970-01-01
    • 2017-04-23
    相关资源
    最近更新 更多