Вопрос инициирован отличным ответом на вопрос по созданию полукруглых надписей в SVG.
Ниже авторский код генератора кривых:
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<input value="Quick brown fox jumps over the lazy dog."
onkeyup="d3.select('textPath').html(this.value)">
<input id="result">
<svg>
<text>
<textPath href="#path">
Quick brown fox jumps over the lazy dog.
</textPath>
</text>
</svg>
<script>
let points = [[50,50],[300,100],[550,50]];
let dragged = null;
let selected = points[points.length-1];
let line = d3.line().curve(d3.curveCardinal);
let svg = d3.select("svg");
let path = svg.append("path").datum(points).attr('id', 'path');
svg.on("mousemove", mousemove).on("mousedown", mousedown)
d3.select(window).on("mouseup", mouseup).on("resize", adjustSize);
window.oncontextmenu = () => false;
adjustSize();
redraw();
function adjustSize() {
let w = window.innerWidth;
let h = window.innerHeight;
svg.attr("width", w).attr("height", h)
.attr("viewBox", `0 0 ${w} ${h}`);
}
function redraw() {
svg.select("path").attr("d", line);
d3.select('input#result').attr('value', svg.select("path").attr('d'))
var circle = svg.selectAll("circle.knob")
.data(points, d => d);
circle.exit().remove();
let newNodes = circle.enter()
.append("circle")
.classed('knob', true)
.attr("r", 1e-6)
.on("dblclick", deletePoint)
.on("mousedown", d => redraw(selected = dragged = d))
.transition()
.duration(250)
.attr("r", 6.5);
circle.merge(newNodes)
.classed("selected", d => d === selected)
.attr("cx", d => d[0])
.attr("cy", d => d[1]);
if (d3.event) {
d3.event.preventDefault();
d3.event.stopPropagation();
}
}
function mousemove() {
if (!dragged) return;
let m = d3.mouse(svg.node());
dragged[0] = m[0];
dragged[1] = m[1];
redraw();
}
function mouseup() {
if (!dragged) return;
mousemove();
dragged = null;
}
function deletePoint(d) {
if (!selected)
return;
let i = points.indexOf(selected);
points.splice(i, 1);
selected = points.length ?
points[i > 0 ? i - 1 : 0] : null;
redraw();
}
function mousedown() {
if (d3.event.button !== 0)
return;
points.push(selected = dragged =
d3.mouse(svg.node()));
redraw();
}
</script>
<style>
body, svg {
position: absolute;
margin: 0;
width: 100vw;
height: 100vh;
overflow: hidden;
user-select: none;
}
path {
fill: none;
stroke: red;
}
circle {
stroke: red;
fill: #fff;
fill-opacity: .4;
}
.selected {fill: #ff7f0e}
text {font-size:30px}
input {width:100%}
</style>
Генератор позволяет добавлять текст и создавать разнообразные формы кривых с помощью перетаскивания узловых точек.
В textarea
динамически выводится формула path
Есть возможность добавлять новые узловые точки
Можно ли внести некоторые улучшения в функционал генератора?
path
Добавляем точку, как бы в середину кривой
Но новая точка добавилась в конец кривой
Firefox
на локальном ПКЯ сделялъ :)
Кликами точки можно добавлять, а даблкликами - удалять.
Тут есть несколько фокусов:
<path>
меняется - я строю диаграмму Вороного d3.voronoi()
для точек лежащих на получившемся пути с фиксированным шагом.Такое разбиение можно увидеть если убрать вот этот стиль
path.voronoi {
fill: transparent;
stroke: none;
}
и вот в этой строчке поставить 20 а не 2
.data(voronoi.polygons(sample(path.node(), 2)))
Проверка попадания точки в ячейку диаграммы делает браузер за счет механизма mouseover.
d3.curveBasis
например, по-этому другие типы линий пока что убраны из этой поделки. Сейчас для генерации кривой по опорным точкам используется алгоритм d3.curveCardinal
.
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<input value="Quick brown fox jumps over the lazy dog." onkeyup="changeText(this.value)">
<input id="result">
<select style="width:20%;display:none" onchange="changeProperties()">
<option value="curveCardinal">cardinal</option>
<option value="curveCardinalClosed">cardinal closed</option>
<option value="curveBasis">basis</option>
<option value="curveBasisClosed">basis closed</option>
</select>
<input id="tension" type="range" style="width:20%" min="-100" max="100" value="50" onchange="changeProperties()">
<br>
<svg>
<text><textPath href="#path"></textPath></text>
<g class="voronoi"></g>
<g class="path"><path id="path"></path></g>
<g class="anchors"></g>
<g class="sticky"><circle r="3" cx="-100" cy="-100"></circle></g>
</svg>
<style>
body, svg {
position: absolute;
margin: 0;
width: 100vw;
height: 100vh;
overflow: hidden;
user-select: none;
}
path {
fill: none;
stroke: red;
}
path.voronoi {
fill: transparent;
stroke: none;
}
circle {
stroke: red;
fill: #fff;
fill-opacity: .4;
cursor:pointer;
}
g.sticky circle {
fill: none;
pointer-events:none;
}
.selected {
fill: #ff7f0e;
}
text {
font-size: 30px;
}
input {
width: 100%;
}
input[type=range] {
height:6px;
}
input[type=range]::-webkit-slider-runnable-track {
height:23px;
}
</style>
<script>
var voronoi, dragged;
let points = [[50, 50], [300, 100], [550, 50]];
let selected = points[points.length - 1];
let line = d3.line()
.curve(d3.curveCardinal);
let svg = d3.select("svg");
let path = svg.select("path#path")
.datum(points);
let stick = d3.select('g.sticky circle');
svg.on("mousemove", mousemove)
.on("mousedown", mousedown);
d3.select(window)
.on("mouseup", mouseup)
.on("resize", adjustSize);
window.oncontextmenu = () => false;
adjustSize();
redraw();
resample();
changeText();
function adjustSize() {
let w = window.innerWidth, h = window.innerHeight;
svg.attr("width", w)
.attr("height", h)
.attr("viewBox", `0 0 ${w} ${h}`);
voronoi = d3.voronoi()
.x(d => d.x)
.y(d => d.y)
.size([w, h])
resample();
}
function redraw() {
let datum = path.attr("d", line).attr('d');
datum = datum.replace(/\d+\.\d+/g, s => parseFloat(s).toFixed())
path.attr("d", datum)
d3.select('input#result')
.attr('value', datum);
var circle = svg.select("g.anchors")
.selectAll("circle.knob")
.data(points, d => d);
circle.exit().remove();
let newNodes = circle.enter()
.append("circle")
.classed('knob', true)
.attr("r", 1e-6)
.on("dblclick", deletePoint)
.on("mousedown", d => redraw(selected = dragged = d))
.on("mousemove", () => stick.attr('opacity', 0))
.transition()
.duration(250)
.attr("r", 6.5);
circle.merge(newNodes)
.classed("selected", d => d === selected)
.attr("cx", d => d[0])
.attr("cy", d => d[1]);
if (d3.event) {
d3.event.preventDefault();
d3.event.stopPropagation();
}
}
function mousemove() {
if (!dragged) return;
let m = d3.mouse(svg.node());
dragged[0] = m[0];
dragged[1] = m[1];
redraw();
}
function mouseup() {
if (!dragged) return;
mousemove();
dragged = null;
resample();
}
function resample() {
try{
let samples = sample(path.node(), 2);
svg.selectAll("path.voronoi").remove()
svg.select('g.voronoi')
.selectAll("path.voronoi")
.data(voronoi.polygons(samples))
.enter()
.append("path")
.attr('class', 'voronoi')
.attr("d", d =>`M${d.join('L')}Z`)
.on('mouseover', function(d) {
let m = d3.mouse(svg.node());
stick.attr('opacity', dist(m, d.data) < 5 ? 1 : 0)
.datum({x: d.data.x, y: d.data.y})
.attr('cx', d => d.x)
.attr('cy', d => d.y)
});
} catch {}
}
function deletePoint(d) {
if (!selected) return;
let i = points.indexOf(selected);
points.splice(i, 1);
selected = points.length ? points[i > 0 ? i - 1 : 0] : null;
redraw();
resample();
}
function mousedown() {
if (d3.event.button !== 0) return;
let pt = [stick.datum().x, stick.datum().y];
let m = d3.mouse(svg.node());
if (dist(m, pt)<5) {
points.splice(calcIndex(pt), 0, selected = dragged = pt)
} else {
points.push(selected = dragged = m);
}
redraw();
resample();
setTimeout(()=>stick.attr('opacity', 0),100)
}
function dist(p1, p2){
let x = (p1.x || p1[0]) - (p2.x || p2[0]);
let y = (p1.y || p1[1]) - (p2.y || p2[1]);
return Math.sqrt(x*x + y*y);
}
function calcIndex(pt) {
let pathNode = path.node();
let total = pathNode.getTotalLength();
let index = 1;
let minDistToAnchorPoint = Number.MAX_VALUE;
let minDistToStickPoint = Number.MAX_VALUE;
for (let l = 0; l <= total; l++) {
let p = pathNode.getPointAtLength(l);
let distToCurrentAnchorPoint = dist(p, points[index]);
if (distToCurrentAnchorPoint < minDistToAnchorPoint) {
minDistToAnchorPoint = distToCurrentAnchorPoint;
} else {
index++
minDistToAnchorPoint = Number.MAX_VALUE;
}
if (dist(p, pt)<2)
break;
}
return index
}
function changeProperties(){
let type = document.querySelector('select').value;
let v = document.querySelector('#tension').value/100;
type = d3[type];
type.tension && (type = type.tension(v))
line = d3.line().curve(type);
redraw();
resample();
}
function sample(pathNode, precision) {
let total = pathNode.getTotalLength();
let samples = [];
for (let l = 0; l <= total; l += precision) {
samples.push(pathNode.getPointAtLength(l));
}
return samples;
}
function changeText(text) {
d3.select('textPath').html(text)
}
</script>
Кофе для программистов: как напиток влияет на продуктивность кодеров?
Рекламные вывески: как привлечь внимание и увеличить продажи
Стратегії та тренди в SMM - Технології, що формують майбутнє сьогодні
Выделенный сервер, что это, для чего нужен и какие характеристики важны?
Современные решения для бизнеса: как облачные и виртуальные технологии меняют рынок
Подскажите способы, как можно реализовать смену темы на сайтеНапример есть светлая и темная тема
Господа, помогите новичку в первых шагахХочу научится отображать курсы валют/нефти/крипты и прочего
Есть код для фиксированной шапки, в первом положении блок находится за контейнером, после прокрутки страницы на 100px добавляется класс где...