在多个节点之间过渡单个节点的梯度填充

时间:2019-03-23 01:43:51

标签: javascript animation d3.js svg gradient

假设我有一个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,它会更改结点/圆的 所有 (因为您要更改{ {1}}。

我在做什么错了?

1 个答案:

答案 0 :(得分:3)

转场的插值

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

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

我们还可以插入字符串:

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

问题

现在,让我们看一下您的情况:您想从中进行插值:

  • 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))
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

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

可能的解决方案

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

因此,天真的修复方法是创建多个渐变,每个圆具有一个渐变,并具有唯一的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>