SVG Как генерировать случайные пути?
Есть вот такой простой пример.
const btn = document.getElementById('generator_btn');
const textarea = document.getElementById('textarea_generator');
const generatorField = document.getElementById('generator_field');
const generatorColor = document.getElementById('generator_color');
// Массив возможных путей для фигур ... это надо переделать, но не знаю как.
const paths = [
'<path class="st1" d="M1.8 23s15.2-7.7 24-7.9 14.4-.5 23.7 7.5c9.3 8 3.6 17.9-1 20-2.7 1.3-7.9.6-6.2-5.5 1.6-5.6 7.7-9.1 16.6-12.4 8.9-3.3 28.9-8.5 32.9-12.9 2.3-2.6 2.8-6-.3-7-2-.6-5.8.6-8.1 2.9C81.3 10 78.3 15.3 79 39c.5 19-20 22.2-25.1 17.8-6.1-5.2 1.5-15.4 8.7-18.7 7.2-3.3 14.7-5.9 21.1-3.1 6.4 2.8 10.8 5 13.5 5.3 2.7.2 9.3 0 15.4-6.9"/><path class="st1" d="m110.9 40.6 4.4-9.8-10.5 3.6"/>',
'<path class="st1" d="M1.8 40.1s4.9-10 10.8-14.8c5.9-4.8 14.4-6.3 17.6-5.8 5.9.8 12.7 6.2 19.3 12.7 6.7 6.5 14.8 14.2 28.7 6.1 7.2-4.2 15.3-17.1 7.2-25.5-6.4-6.7-18 2.9-21.7 11.7-1.9 4.6-5 20 2.7 25.7 7.8 5.8 17.2 3.1 27.4-4.4 10.2-7.6 22.6-24 24-27"/><path class="st1" d="m112.6 21.1 5.6-3.9.5 8.7"/>',
'<path class="st1" d="M.8 41.8s14-21 28.3-14.3c14.4 6.7 7.2 20.3 1.7 18.5-5.9-1.9-7-20 9.4-21.8s23.6 5 32.7 8.5c9.1 3.5 33.8 6.6 44.3-10.2"/><path class="st1" d="m111.2 25.9 6.2-4-.8 6"/>'
];
generatorColor.addEventListener('input', function() {
includeSvg();
});
btn.addEventListener('click', function() {
includeSvg();
});
function includeSvg() {
let currentColor = generatorColor.value;
//
let randomPath = paths[Math.floor(Math.random() * paths.length)];
let svgCode = generateSVG(currentColor, randomPath);
textarea.value = svgCode;
generatorField.innerHTML = svgCode;
}
function generateSVG(currentColor, path) {
let startFigure = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="120" height="60" x="0" y="0" viewBox="0 0 120 64"><style>.st1{fill:none;stroke:' + currentColor + ';stroke-width:2;stroke-linecap:round;stroke-linejoin:round}</style>';
let endFigure = '</svg>';
return startFigure + path + endFigure;
}
body {
margin: 0;
}
#generator_btn {
margin: 0;
display: inline-block;
padding: 10px 15px;
font-weight: 700;
vertical-align: top;
}
#generator_color {
height: 40px;
}
#textarea_generator {
display: block;
width: 100%;
max-width: 90vw;
margin: 10px 0;
}
<div class="gen_block">
<button id="generator_btn">Generator SVG</button>
<input type="color" id="generator_color" name="generator_color" value="#FE6C3B" />
</div>
<textarea id="textarea_generator" name="generator_svg" rows="4"></textarea>
<div id="generator_field"></div>
Идея состоит в том, что б сделать по, нажатию на кнопку, генератор случайных вот таких путей.... кракозябр, но я не могу понять как это реализовать. По сути svg элемент состоит из двух путей В первом - каким-то образом запутаная кривая, (но там должен быть плавный переход), а во втором стрелочка которая заканчиваеться в этом пути. Ну и динамическая разукрашка сгенерированного пути, что б можно было назначить цвет svg элементу перед сохранением. Подскажите, пожалуйста как создавать эти случайные пути в приделах этой области элемента ?
Ответы (4 шт):
Генератор случайных путей благодаря варианту @XelaNimed получилось сделать. Но качество меня не устраивает... Не получаеться с петлями, только рендомные прямые у путей. Может кто-то знает, как улучшить что б оно смотрелось более плавно и не вылазило за приделы области SVG?
const btn = document.getElementById('generator_btn');
const textarea = document.getElementById('textarea_generator');
const generatorField = document.getElementById('generator_field');
const generatorColor = document.getElementById('generator_color');
const W = 120;
const H = 64;
const MIN_ANGLE = 60;
const MIN_DISTANCE = (Math.min(W, H)) / 20;
const MAX_DISTANCE = (Math.min(W, H)) / 4;
let lastTwoPoints = [];
generatorColor.addEventListener('input', function() {
let currentSvg = textarea.value;
let strokeValue = /stroke:[^;]+;/.exec(currentSvg)[0];
let updatedSvg = currentSvg.replace(strokeValue, 'stroke:' + generatorColor.value + ';');
textarea.value = updatedSvg;
generatorField.innerHTML = updatedSvg;
});
btn.addEventListener('click', function() {
includeSvg();
});
function includeSvg() {
let currentColor = generatorColor.value;
let randomPath = generateRandomPath();
let svgCode = generateSVG(currentColor, randomPath);
textarea.value = svgCode;
generatorField.innerHTML = svgCode;
}
function generateRandomPath() {
const startX = getRandomNumber(W * 0.6) + W * 0.2;
const startY = getRandomNumber(H * 0.6) + H * 0.2;
let pathString = `<path d="M${startX} ${startY} `;
for (let i = 0; i < 10; i++) {
const controlX1 = getRandomNumber(W * 0.6) + W * 0.2;
const controlY1 = getRandomNumber(H * 0.6) + H * 0.2;
const controlX2 = getRandomNumber(W * 0.6) + W * 0.2;
const controlY2 = getRandomNumber(H * 0.6) + H * 0.2;
const endX = getRandomNumber(W * 0.6) + W * 0.2;
const endY = getRandomNumber(H * 0.6) + H * 0.2;
pathString += `${controlX1} ${controlY1} ${controlX2} ${controlY2} ${endX} ${endY}`;
}
return pathString + '"/>';
}
function getDistance(pointA, pointB) {
return Math.sqrt((pointA[0] - pointB[0]) ** 2 + (pointA[1] - pointB[1]) ** 2);
}
function getAngle(pointA, pointB, pointC) {
let a = getDistance(pointA, pointB);
let b = getDistance(pointB, pointC);
let c = getDistance(pointC, pointA);
return Math.acos((a * a + b * b - c * c) / (2 * a * b)) * (180 / Math.PI);
}
function getRandomPoint() {
let x = getRandomNumber(W * 0.6) + W * 0.2;
let y = getRandomNumber(H * 0.6) + H * 0.2;
let point = [x, y];
if (lastTwoPoints.length < 2) {
lastTwoPoints.push(point);
} else {
if (getAngle(...lastTwoPoints, point) < MIN_ANGLE ||
getDistance(lastTwoPoints[1], point) < MIN_DISTANCE ||
getDistance(lastTwoPoints[1], point) > MAX_DISTANCE) {
point = getRandomPoint();
} else {
lastTwoPoints.shift();
lastTwoPoints.push(point);
}
}
return `${point[0]} ${point[1]}`;
}
function generateSVG(currentColor, path) {
let startFigure = '<svg xmlns="http://www.w3.org/2000/svg" width="240" height="128" viewBox="0 0 120 64"><style>path{fill:none;stroke:' + currentColor + ';stroke-width:2;stroke-linecap:round;stroke-linejoin:round}</style>';
let endFigure = '</svg>';
return startFigure + path + endFigure;
}
function getRandomNumber(max) {
return Math.floor(Math.random() * max);
}
body {
margin: 0;
}
#generator_btn {
margin: 0;
display: inline-block;
padding: 10px 15px;
font-weight: 700;
vertical-align: top;
}
#generator_color {
height: 40px;
}
#textarea_generator {
display: block;
width: 100%;
max-width: 90vw;
margin: 10px 0;
}
<div class="gen_block">
<button id="generator_btn">Generator SVG</button>
<input type="color" id="generator_color" name="generator_color" value="#FE6C3B" />
</div>
<textarea id="textarea_generator" name="generator_svg" rows="10"></textarea>
<div id="generator_field"></div>
для понимания синтаксиса, ознакомьтесь с SVG path оператором для кубических кривых Безье тут:
https://developer.mozilla.org/ru/docs/Web/SVG/Tutorial/Paths
остальная инфа впрочем тоже не помешает, но это главное в рамках данного примера.
p.s. дополнял фактически одну строку и то методом тыка, потому и результат такой...)))
// за основу взят ваш же ответ
пометка для прохожих:
если хотите изобрести себе подпись для паспорта, то что вышло идеально подходит :3
UPD 15 01 2024 / 19:25
и вьюбокс растянул, и кракозябру увеличил, и рандом сделал повеселее, ну и заспойлерил чтоб не мешалось при скроле :3
UPD 16 01 2024 / 23:35
добавил стрелку - маркер
источник https://developer.mozilla.org/en-US/docs/Web/SVG/Element/marker
p.s. в сниппете из-за увеличения смотреть бестолку, разворачивайте на весь экран
const btn = document.getElementById('generator_btn');
const textarea = document.getElementById('textarea_generator');
const generatorField = document.getElementById('generator_field');
const generatorColor = document.getElementById('generator_color');
const W = 150;
const H = 125; /*!!!!!!!!!!*/
const MIN_ANGLE = 60;
const MIN_DISTANCE = (Math.min(W, H)) / 20;
const MAX_DISTANCE = (Math.min(W, H)) / 4;
let lastTwoPoints = [];
generatorColor.addEventListener('input', function() {
let currentSvg = textarea.value;
let strokeValue = /stroke:[^;]+;/.exec(currentSvg)[0];
let updatedSvg = currentSvg.replace(strokeValue, 'stroke:' + generatorColor.value + ';');
textarea.value = updatedSvg;
generatorField.innerHTML = updatedSvg;
});
btn.addEventListener('click', function() {
includeSvg();
});
function includeSvg() {
let currentColor = generatorColor.value;
let randomPath = generateRandomPath();
let svgCode = generateSVG(currentColor, randomPath);
textarea.value = svgCode;
generatorField.innerHTML = svgCode;
}
function generateRandomPath() {
const startX = getRandomNumber(H * 0.8);
const startY = getRandomNumber(H * 0.8);
let pathString = `M${startX} ${startY} `;
var t=document.getElementById('t');
let x1, x2, x3, x4, y1, y2, y3, y4;
let q = 1.3; // коэфицент отклонения контрольной точки
for (let i = 0; i < 2; i++) {
x1 = getRandomNumber(H * 0.35 + ((i+i)*15));
y1 = getRandomNumber(H * 0.35);
x2 = getRandomNumber(H * 0.75);
y2 = getRandomNumber(H * 0.35);
x3 = getRandomNumber(H * 0.75);
y3 = getRandomNumber(H * 0.75);
x4 = getRandomNumber(H * 0.35);
y4 = getRandomNumber(H * 0.75);
pathString += '\n' + (i == 0 ? 'Q' : 'T') +
` ${x1} ${y1}, ${x1/q} ${y1/q}
T ${x2} ${y2}, ${x2*q} ${y2/q}
T ${x3} ${y3}, ${x3*q} ${y3*q}
T ${x4} ${y4}, ${x4/q} ${y4*q}`;
}
t.value=pathString;
return '<path marker-end="url(#arrow)" d="' + pathString + '"/>';
}
function getDistance(pointA, pointB) {
return Math.sqrt((pointA[0] - pointB[0]) ** 2 + (pointA[1] - pointB[1]) ** 2);
}
function getAngle(pointA, pointB, pointC) {
let a = getDistance(pointA, pointB);
let b = getDistance(pointB, pointC);
let c = getDistance(pointC, pointA);
return Math.acos((a * a + b * b - c * c) / (2 * a * b)) * (180 / Math.PI);
}
function getRandomPoint() {
let x = getRandomNumber(W * 0.6) + W * 0.2;
let y = getRandomNumber(H * 0.6) + H * 0.2;
let point = [x, y];
if (lastTwoPoints.length < 2) {
lastTwoPoints.push(point);
} else {
if (getAngle(...lastTwoPoints, point) < MIN_ANGLE ||
getDistance(lastTwoPoints[1], point) < MIN_DISTANCE ||
getDistance(lastTwoPoints[1], point) > MAX_DISTANCE) {
point = getRandomPoint();
} else {
lastTwoPoints.shift();
lastTwoPoints.push(point);
}
}
return `${point[0]} ${point[1]}`;
}
function generateSVG(currentColor, path) {
let startFigure = '<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 500 500"><style>path{fill:none;stroke:' + currentColor + ';stroke-width:2;stroke-linecap:round;stroke-linejoin:round}</style>';
let endFigure = '</svg>';
let arrow = ` <defs>
<!-- A marker to be used as an arrowhead -->
<marker
id="arrow"
viewBox="0 0 10 10"
refX="5"
refY="5"
markerWidth="6"
markerHeight="6"
orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" />
</marker>
</defs>`;
/********************************************/
return startFigure + path + arrow + endFigure;
}
function getRandomNumber(max) {
return (Math.floor(Math.random() * max)) + 225;/*!!!!!!*/
}
body {margin: 0;}
#generator_color {height: 40px;}
#textarea_generator, #t{width: 47%;}
#generator_btn {
margin: 0;
display: inline-block;
padding: 10px 15px;
font-weight: 700;
vertical-align: top;
}
<div class="gen_block">
<button id="generator_btn">Generator SVG</button>
<input type="color" id="generator_color" name="generator_color" value="#FE6C3B" />
</div>
<textarea id='t'></textarea>
<textarea id="textarea_generator" name="generator_svg"></textarea>
<div id="generator_field"></div>
В код добавлена кнопка показывающая контрольные отрезки, по которым строится кривая. Эти отрезки не полностью случайные: первый отрезок начинается на левом краю рисунка, затем несколько отрезков опираются на верхний и нижний края рисунка, последний отрезок заканчивается на правом краю.
Концы всех контрольных отрезков лежат внутри или на краю рисунка. Это гарантирует что кривая тоже целиком внутри.
const scale = (v, f) => {
const [x, y] = v;
return [f * x, f * y];
};
const normalize = v => scale(v, 1 / Math.hypot(...v));
const rotate = (v, a) => {
const [x, y] = v;
const c = Math.cos(a);
const s = Math.sin(a);
return [c * x - s * y, s * x + c * y];
};
const round = (v, f) => {
const [x, y] = v;
return [Math.round(f * x) / f, Math.round(f * y) / f];
};
const curveD = (segments, withArrow) => {
const n = segments.length;
let d = '';
const [x1, y1] = segments[0][0];
d += `M ${x1} ${y1}`;
for (let i = 1; i < n - 1; ++i) {
let [x2, y2] = segments[i - 1][1];
let [[x3, y3], [x4, y4]] = segments[i];
d += ` C ${x2} ${y2}, ${x3} ${y3}, ${(x3 + x4) / 2} ${(y3 + y4) / 2}`;
}
let [x2, y2] = segments[n - 2][1];
let [[x3, y3], [x4, y4]] = segments[n - 1];
d += ` C ${x2} ${y2}, ${x3} ${y3}, ${x4} ${y4}`;
if (withArrow) {
const v = scale(normalize([x3 - x4, y3 - y4]), 10);
const [rx, ry] = round(rotate(v, Math.PI / 8), 100);
const [lx, ly] = round(rotate(v, -Math.PI / 8), 100);
d += ` l ${rx} ${ry} m ${-rx} ${-ry} l ${lx} ${ly}`;
}
return d;
};
const controlsD = segments => {
const n = segments.length;
let d = '';
const r = 2;
const drawPoint = (x, y) => {
d += ` M ${x + r} ${y} a ${r} ${r} 0 0 0 ${-2 * r} 0`;
d += ` M ${x - r} ${y} a ${r} ${r} 0 0 0 ${2 * r} 0`;
};
for (let i = 0; i < n; ++i) {
const [[x1, y1], [x2, y2]] = segments[i];
d += ` M ${x1} ${y1} L ${x2} ${y2}`;
drawPoint(x1, y1);
if (0 < i && i < n - 1) {
drawPoint((x1 + x2) / 2, (y1 + y2) / 2);
}
drawPoint(x2, y2);
}
return d;
};
const drawRandomCurve = (svg, curve, controls, textarea) => {
const width = svg.getAttribute('width');
const height = svg.getAttribute('height');
const randomX = () => Math.floor(width * Math.random());
const randomY = () => Math.floor(height * Math.random());
const randomControlSegments = n => {
const segments = [];
segments.push([
[0, randomY()],
[width - 1 - Math.floor(0.8 * randomX()), randomY()]
]);
let y = (height - 1) * Math.floor(2 * Math.random());
for (let i = 1; i < n - 1; ++i) {
const p1 = [randomX(), y];
y = height - 1 - y;
const p2 = [randomX(), y];
segments.push([p1, p2]);
}
segments.push([
[Math.floor(0.8 * randomX()), randomY()],
[width - 1, randomY()]
]);
return segments;
};
const segments = randomControlSegments(5);
const d = curveD(segments, true);
curve.setAttribute('d', d);
textarea.value = d;
controls.setAttribute('d', controlsD(segments));
};
const doDraw = () => drawRandomCurve(
document.getElementById('board'),
document.getElementById('curve'),
document.getElementById('controls'),
document.getElementById('text')
);
document.getElementById('draw!').onclick = doDraw;
doDraw();
const showControls = () => {
const show = document.getElementById('show_controls').checked;
document.getElementById('controls').setAttribute(
'visibility',
(show) ? 'visible' : 'hidden'
);
};
document.getElementById('show_controls').onclick = showControls;
showControls();
<button id="draw!">Обновить кривую</button>
<br>
<textarea id="text" cols="60", rows="3"></textarea>
<br>
<svg id="board" width="120" height="60" style="border: 1px solid gray;">
<path id="curve" stroke="black" stroke-width="2" fill="transparent"></path>
<path id="controls" stroke="red" stroke-width="1" fill="transparent" visibility="hidden"></path>
<br>
<input type="checkbox" id="show_controls" name="show_controls" />
<label for="show_controls">показать контрольные отрезки</label>
</svg>