如何动画D3 SVG边框线微妙的波浪效果



我可以在中动画化一条曲线

const svg = d3.select("#line-svg");
const lineWidth = 6;
// Scale.
const scaleX = d3.scaleLinear()
.domain([0, 300])
.range([0, parseFloat(svg.style("width"))]);
const scaleY = d3.scaleLinear()
.domain([0, 120])
.range([0, parseFloat(svg.style("height")) - lineWidth]);
// Curved line interpolator.
const bezierLine = d3.line()
.curve(d3.curveBasis)
.x((d) => scaleX(d[0]))
.y((d) => scaleY(d[1]));
// Draw line & animate.
svg
.append("path")
.attr(
"d",
bezierLine([
[0, 40],
[25, 70],
[50, 100],
[100, 50],
[150, 20],
[200, 130],
[300, 120]
])
)
.attr("stroke", "url(#b1xGradient)")
.attr("stroke-width", lineWidth)
.attr("fill", "none")
.transition()
.duration(900)
.attrTween("stroke-dasharray", function () {
const len = this.getTotalLength();
return (t) => d3.interpolateString("0," + len, len + ",0")(t);
});
body {
background: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg id="line-svg" width="100%" height="150">
<defs>
<linearGradient id="b1xGradient">
<stop offset="0%" style="stop-color: #18e589;" />
<stop offset="100%" style="stop-color: #2870f0;" />
</linearGradient>
</defs>
</svg>

但是,我如何获取这些线点,或者生成一些线点,然后不断地巧妙地移动它们,以创建动画波浪效果?

附言:这条线也不必看起来完全像那样——它可以在数学上被生成为类似于示例的某种波浪线(实际上这会很整洁(。

类似的东西,但更微妙的运动范围和更慢-https://codesandbox.io/s/threejs-meshline-custom-spring-3-forked-og1f7?file=/src/index.js

我不知道你所说的波浪到底是什么意思,但你可以在线条上添加一点随机偏移,并无限多次地重新绘制。您可以通过命名过渡将其与生长动画相结合,这些过渡可以并排存在。

我使用.datum()分配了数据点,这样我就可以在转换中访问它们。

const svg = d3.select("#line-svg");
const lineWidth = 6;
// Scale.
const scaleX = d3.scaleLinear()
.domain([0, 300])
.range([0, parseFloat(svg.style("width"))]);
const scaleY = d3.scaleLinear()
.domain([0, 120])
.range([0, parseFloat(svg.style("height")) - lineWidth]);
// Curved line interpolator.
const bezierLine = d3.line()
.curve(d3.curveBasis)
.x((d) => scaleX(d[0]))
.y((d) => scaleY(d[1]));
// Draw line & animate.
const line = svg
.append("path")
.datum([
[0, 40],
[25, 70],
[50, 100],
[100, 50],
[150, 20],
[200, 130],
[300, 120]
])
.attr("stroke", "url(#b1xGradient)")
.attr("stroke-width", lineWidth)
.attr("fill", "none")
.attr("d", function(d) { return bezierLine(d); });
line
.transition("grow")
.duration(900)
.attrTween("stroke-dasharray", function () {
const len = this.getTotalLength();
return (t) => d3.interpolateString("0," + len, len + ",0")(t);
})
function wave() {
line
.transition("wave")
.duration(900)
.ease(d3.easeLinear)
.attr("d", function(d) { 
// Add a little offset to each coordinate
const offsetCoords = d.map(function(e) {
return [
e[0] - 3 + Math.random() * 6,
e[1] - 2 + Math.random() * 2
];
});
return bezierLine(offsetCoords);
})
// Repeat
.on("end", wave);
}
wave();
body {
background: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg id="line-svg" width="100%" height="150">
<defs>
<linearGradient id="b1xGradient">
<stop offset="0%" style="stop-color: #18e589;" />
<stop offset="100%" style="stop-color: #2870f0;" />
</linearGradient>
</defs>
</svg>


编辑以使线实际表现为波浪而不是脉冲,您可以这样做。它使用正弦函数生成波,具有随机振幅、周期和平均值的随机偏移,因此看起来更混乱。您可以自己调整变量,因为它在内联查看器中看起来与编辑时非常不同——我认为这与宽度/高度比有关。

与您的示例相反,它没有波浪熄灭,只有在视图内外滚动。然而,既然波浪产生已经得到了处理,这应该很容易实现。

const svg = d3.select("#line-svg");
const lineWidth = 6;
// Scale.
const scaleX = d3.scaleLinear()
.domain([0, 300])
.range([0, parseFloat(svg.style("width"))]);
const scaleY = d3.scaleLinear()
.domain([0, 120])
.range([0, parseFloat(svg.style("height")) - lineWidth]);
// Curved line interpolator.
const bezierLine = d3.line()
.curve(d3.curveBasis)
.x((d) => scaleX(d[0]))
.y((d) => scaleY(d[1]));
// Create a sine wave. Each wave is completes a full number of periods
// before being replaced by another one
// if varyMean is true, add a little bit of noise to the mean of the function
function generateSine(y, step, mean, varyMean) {
const sine = {
amplitude: Math.random() * 5 + 20, // [5, 25]
period: Math.random() * 0.25 + 0.05, // [0.05, 0.3]
repeats: 1 + Math.round(Math.random() * 3), // [1, 4]
meanOffset: varyMean ? Math.random() * 50 - 25 : 0 // [-25, 25]
};
// Calculate a gradual decrease or increase the mean
function offset(i) {
return Math.min(i, 2 * Math.PI) * sine.meanOffset;
}
const offsetX = y.length * step;
let runningX = 0;
while (runningX < 2 * Math.PI * sine.repeats) {
const m = mean + offset(runningX);
y.push(m + sine.amplitude * Math.sin(runningX + offsetX));
runningX += 2 * Math.PI * step / sine.period;
}
}
// Draw line & animate.
const line = svg
.append("path")
.datum(function() {
const domain = scaleX.domain();
const nPoints = 50;
const points = d3.range(nPoints).map(function(v) {
return v / (nPoints - 1);
});
const step = points[1] - points[0];
const x = points.map(function(v) {
return domain[0] + v * (domain[1] - domain[0]);
});
const xStep = x[1] - x[0];

// Draw two points just before and just after the visible part of the wave
// to make the lines run smoothly
x.unshift(x[0] - xStep); x.push(x[x.length - 1] + xStep);
const y = [];
const mean = d3.sum(scaleY.domain()) / 2;
while(y.length < x.length) {
generateSine(y, step, mean, true);
}
return {
x: x,
y: y,
mean: mean,
step: step
};
})
.attr("stroke", "url(#b1xGradient)")
.attr("stroke-width", lineWidth)
.attr("fill", "none");
line
.transition("grow")
.duration(900)
.attrTween("stroke-dasharray", function() {
const len = this.getTotalLength() * 2;
return (t) => d3.interpolateString("0," + len, len + ",0")(t);
})
function wave() {
line
.attr("d", function(d) {
return bezierLine(d.x.map(function(v, i) {
// We store some additional variables at the end of y,
// we don't want to show yet
return [v, d.y[d.x.length - 1 - i]];
}));
})
.datum(function(d) {
const y = d.y;

// Remove the y value that was just moved out of view
y.shift();      
// See if we still have enough y values left, otherwise, generate some
while(y.length < d.x.length) {
generateSine(y, d.step, d.mean);
}
return d;
})
.attr("transform", function(d) {
const step = d.x[1] - d.x[0];
return `translate(${-scaleX(step)})`
})
.transition("wave")
.duration(function(d) { return 5000 / d.x.length; })
.ease(d3.easeLinear)
.attr("transform", "translate(0)")
.on("end", function() {
// Repeat
wave();
});
}
wave();
body {
background: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.js"></script>
<svg id="line-svg" width="100%" height="150">
<defs>
<linearGradient id="b1xGradient">
<stop offset="0%" style="stop-color: #18e589;" />
<stop offset="100%" style="stop-color: #2870f0;" />
</linearGradient>
</defs>
</svg>

您只需重新初始化循环中的路径点并在它们之间转换即可。

我刚刚添加了Y坐标的更改。你可以玩X坐标的循环循环,比如说移动10%,直到循环10次(然后你将全局计数器变量重置回0等(。这会使你想要的波浪(例如,看起来向右移动(。

const svg = d3.select("#line-svg");
const lineWidth = 6;
// Scale.
const scaleX = d3.scaleLinear()
.domain([0, 300])
.range([0, parseFloat(svg.style("width"))]);
const scaleY = d3.scaleLinear()
.domain([0, 120])
.range([0, parseFloat(svg.style("height")) - lineWidth]);
// Curved line interpolator.
const bezierLine = d3.line()
.curve(d3.curveBasis)
.x((d) => scaleX(d[0]))
.y((d) => scaleY(d[1]));
const randBezierLine = d3.line()
.curve(d3.curveBasis)
.x((d) => scaleX(d[0]))
.y((d) => scaleY(d[1]*(1-(Math.random()+0.3)/5)));
const points = [
[0, 40],
[25, 70],
[50, 100],
[100, 50],
[150, 20],
[200, 130],
[300, 120]
];
var lenTotal = 1200;// just more than gathered length
// Draw line & animate.
svg
.append("path")
.attr("id", "animLine")
.attr(
"d",
bezierLine([
[0, 40],
[25, 70],
[50, 100],
[100, 50],
[150, 20],
[200, 130],
[300, 120]
])
)
.attr("stroke", "url(#b1xGradient)")
.attr("stroke-width", lineWidth)
.attr("fill", "none")
.transition()
.duration(900)
.attrTween("stroke-dasharray", function () {
const len = this.getTotalLength();
return (t) => d3.interpolateString("0," + len, len + ",0")(t);
});
function Transition() {
d3.select("#animLine")
.transition()
.duration(500)
.ease(d3.easeLinear)
.attr("d", randBezierLine(points))
.attr("stroke-dasharray", lenTotal + ",0")
.on("end", function() { Transition(); });
}
setTimeout(Transition, 2000);
body {
background: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg id="line-svg" width="100%" height="150">
<defs>
<linearGradient id="b1xGradient">
<stop offset="0%" style="stop-color: #18e589;" />
<stop offset="100%" style="stop-color: #2870f0;" />
</linearGradient>
</defs>
</svg>