#javascript #d3.js
#javascript #d3.js
Вопрос:
Я могу анимировать изогнутую линию примерно так:
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 amp; 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>
Но как я могу взять эти точки линии или сгенерировать несколько точек линии, а затем постоянно слегка сдвигать их, чтобы создать анимированный эффект волны?
PS Линия также не обязательно должна выглядеть точно так же — ее можно сгенерировать математически, чтобы она была какой-то волнистой линией, похожей на пример (на самом деле это было бы аккуратно).
Что-то похожее на это, но более тонкий диапазон движения и медленнее — https://codesandbox.io/s/threejs-meshline-custom-spring-3-forked-og1f7?file=/src/index.js
Ответ №1:
Я не знаю точно, что вы подразумеваете под волной, но вы можете добавить немного случайного смещения к строке и перерисовывать ее бесконечно много раз. Вы можете объединить это с анимацией роста, указав именованные переходы — они могут существовать бок о бок.
Я назначил точки данных, используя .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 amp; 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 amp; 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>
Комментарии:
1. 1 Спасибо за исходный код, и это вроде как работает, но думаю, мне придется попробовать какое-то решение, которое перемещает точки данных, используя некоторую математику, чтобы создать скорее волнообразное движение, чем случайное
2. Если вы обновите свой вопрос, чтобы быть более конкретным, я был бы рад обновить свой ответ
3. Я не совсем уверен, как это сформулировать, но я больше жду, чтобы волна двигалась, как в океане, поэтому волна будет перемещаться по линии и, вероятно, уменьшаться в размерах, как это происходит. Думаю, это довольно сложный вопрос, поскольку он будет включать некоторую математическую логику типа синусоидальной волны / генератора
4. например codesandbox.io/s /… (но более тонко и медленно)
5. Значит, текущие точки линии не имеют значения?
Ответ №2:
Вы можете просто повторно инициализировать точки пути в цикле и переход между ними.
Я только что добавил изменение координат 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 amp; 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>
Комментарии:
1. Хорошая идея, думаю, я буду использовать и адаптировать это 👍