简体   繁体   English

如何动画 D3 SVG 贝塞尔线以获得微妙的波浪效果

[英]How animate D3 SVG bezier line for subtle wave effect

I'm able to animate a curved line in like so:我可以像这样为曲线设置动画:

 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>

But how can I take these line points, or generate some line points, and then continually shift them subtly to create an animated wave effect?但是我怎样才能获取这些线点,或者生成一些线点,然后不断地巧妙地移动它们以创建动画波浪效果呢?

PS The line doesn't have to look exactly like that either - it could be mathematically generated to be some kind of wavy line similar to the example (actually that'd be neat). PS 这条线也不必看起来完全一样 - 它可以通过数学方式生成为类似于示例的某种波浪线(实际上那会很整洁)。

Something similar to this but more subtle range of movement and slower - https://codesandbox.io/s/threejs-meshline-custom-spring-3-forked-og1f7?file=/src/index.js类似于此但更微妙的运动范围和更慢 - https://codesandbox.io/s/threejs-meshline-custom-spring-3-forked-og1f7?file=/src/index.js

I don't know exactly what you mean by wave, but you can add a little bit of random offset to the line and redraw it infinitely many times.我不知道你说的波浪到底是什么意思,但是你可以给这条线添加一点随机偏移量并无限次地重绘它。 You can combine this with the growth animation by having named transitions - those can live side by side.您可以通过命名过渡将其与成长动画结合起来 - 这些过渡可以并排存在。

I assigned the data points using .datum() so I could access them inside the transition.我使用.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>


Edit to make the line actually behave like a wave instead of pulsing, you might do something like this.编辑以使线条实际上表现得像波浪而不是脉动,您可以这样做。 It generates waves using a sine function, with random amplitudes, periods, and a random offset on the mean so it looks more chaotic.它使用正弦函数生成波,具有随机幅度、周期和均值的随机偏移,因此看起来更混乱。 You can tweak the variables yourself, because it looks very different in the inline viewer than it does when editing - which I think has something to do with the width/height ratio.您可以自己调整变量,因为它在内嵌查看器中看起来与编辑时非常不同 - 我认为这与宽/高比有关。

In contrast to your example, it does not have waves extinguishing, only rolling in and out of view.与您的示例相反,它没有波浪熄灭,只是在视野中滚动。 However, this should be easy to implement, now that the wave generation has been taken care of.然而,这应该很容易实现,现在已经处理了波浪生成。

 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(dxmap(function(v, i) { // We store some additional variables at the end of y, // we don't want to show yet return [v, dy[dxlength - 1 - i]]; })); }) .datum(function(d) { const y = dy; // 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 < dxlength) { generateSine(y, d.step, d.mean); } return d; }) .attr("transform", function(d) { const step = dx[1] - dx[0]; return `translate(${-scaleX(step)})` }) .transition("wave") .duration(function(d) { return 5000 / dxlength; }) .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>

You can just reinitialize path points in the loop and transition between them.您可以重新初始化循环中的路径点并在它们之间进行转换。

I've just added changing of Y coordinates.我刚刚添加了 Y 坐标的变化。 You could play with round robin loop of X coordinates say to shift for 10% until it loops 10 times (and you reset the global counter variable back to 0 etc).你可以玩 X 坐标的循环循环,比如移动 10% 直到它循环 10 次(并且你将全局计数器变量重置回 0 等)。 That would make your desired wave (eg appearing to move to the right).这将使您想要的波浪(例如,似乎向右移动)。

 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>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM