Looping through elements is not working - javascript

I am trying to add an extra path to each SVG on a webpage. I set up my for loop and it is working, but once it loops through it tells me appendChild() is not a function. If I take everything out of the loop appendChild() works. What am I doing incorrectly here?
var svg = document.getElementsByClassName('curve-position-bottom'); //Get svg element
for (var i = 0; i < svg.length; i++) {
console.log(svg.length);
function addPath() {
console.log('working');
var newElement = document.createElementNS("http://www.w3.org/2000/svg", 'path'); //Create a path in SVG's namespace
newElement.setAttribute("d", "M0 0 C50 100 50 100 100 0 "); //Set path's data
newElement.style.stroke = "#005780"; //stroke colour
newElement.style.strokeWidth = "13px"; //Set stroke width
newElement.style.fill = "none"; //Set stroke width
svg.appendChild(newElement);
}
addPath();
}
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 100 100" version="1.1" preserveAspectRatio="none" height="45px" class="engle-curve curve-position-bottom" style="fill:#e6e2af">
<path stroke-width="0" d="M0 0 C50 100 50 100 100 0 L100 100 0 100"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 100 100" version="1.1" preserveAspectRatio="none" height="45px" class="engle-curve curve-position-bottom" style="fill:#e6e2af">
<path stroke-width="0" d="M0 0 C50 100 50 100 100 0 L100 100 0 100"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 100 100" version="1.1" preserveAspectRatio="none" height="45px" class="engle-curve curve-position-bottom" style="fill:#e6e2af">
<path stroke-width="0" d="M0 0 C50 100 50 100 100 0 L100 100 0 100"></path>
</svg>

The return value of document.getElementsByClassName is a NodeList, not an individual element. To use your defined SVG, you'll need to use svg[i].
Also, unrelated to your question, but it would be a good idea to move that function definition out of the loop (for performance and scoping reasons) and to call it with an SVG element as a parameter. It would look more like this:
var svg = document.getElementsByClassName('curve-position-bottom'); //Get svg elements
function addPath(svg) {
console.log('working');
var newElement = document.createElementNS("http://www.w3.org/2000/svg", 'path'); //Create a path in SVG's namespace
newElement.setAttribute("d", "M0 0 C50 100 50 100 100 0 "); //Set path's data
newElement.style.stroke = "#005780"; //stroke colour
newElement.style.strokeWidth = "13px"; //Set stroke width
newElement.style.fill = "none"; //Set stroke width
svg.appendChild(newElement);
}
for (var i = 0, length = svg.length; i < length; i++) {
addPath(svg[i]);
}

Instead
svg.appendChild(newElement);
try:
svg[i].appendChild(newElement);

Related

Moving SVG object on guideline using javascript

I'm trying to move SVG object on <path> guideline I created, my code is based on this one, but I can't figure out why mine doesn't work and the codepen I shared does.
function positionTheElement() {
var html = document.documentElement;
var body = document.body;
var scrollPercentage = (html.scrollTop + body.scrollTop) / (html.scrollHeight - html.clientHeight);
var path = document.getElementById("trace");
var pathLen = path.getTotalLength();
var pt = path.getPointAtLength(scrollPercentage * pathLen );
var element = document.getElementById("etoile");
element.setAttribute("transform", "translate("+ pt.x + "," + pt.y + ")");
};
window.addEventListener("scroll", positionTheElement);
positionTheElement();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1920 4000" style="enable-background:new 0 0 1920 4000;" xml:space="preserve">
<g>
<path class="st12" id="trace" d="M1491.8,3999l-7.8-365c0,0-1156-14-1240.5-1.3S155,3552,155,3552s3-594-0.3-688.1
c-3.3-94.1,94.3-81.9,94.3-81.9l689,5c0,0,645,5,751.9,6s90.1-93,90.1-93s20-468,14-576s-96.2-89.6-96.2-89.6S288,2039,208,2034.4
S120,1931,120,1931s13-1156,6-1184s4-60,10-89c0-33,141-28,141-28s764-1,867,0s154-11,169-19c36.8-10.9,26.8-27.7,28-53l2.5-525.5"
stroke="tomato" fill="none" stroke-width="4"/>
<polygon class="st11" id="etoile" stroke="tomato" fill="none" stroke-width="4" points="1367.7,59.8 1345.4,49.8 1324.5,62.3 1327.3,38.3 1308.8,22.4 1332.8,17.6 1342.3,-4.7 1354.3,16.3
1378.7,18.4 1362.2,36.2 "/>
</g>
</svg>
How can I animate the SVG polygon on the path guideline correctly?
You have 2 errors:
the star should have the center in the origin of the svg canvas (x=0; y=0).
the path trace has to be reversed. The way it's drawn (from the bottom up) would make the star begin it's movement from the bottom of the document to the top.
function positionTheElement() {
var html = document.documentElement;
var body = document.body;
var scrollPercentage = (html.scrollTop + body.scrollTop) / (html.scrollHeight - html.clientHeight);
var path = document.getElementById("trace");
var pathLen = path.getTotalLength();
var pt = path.getPointAtLength(scrollPercentage * pathLen );
var element = document.getElementById("etoile");
element.setAttribute("transform", "translate("+ pt.x + "," + pt.y + ")");
};
window.addEventListener("scroll", positionTheElement);
positionTheElement();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<svg viewBox="0 0 1920 4000" >
<g>
<path class="st12" id="trace" d="M1343.5,32.5
L1341,558C1339.8,583.3 1349.8,600.1 1313,611C1298,619 1247,631 1144,630C1041,629 277,630 277,630C277,630 136,625 136,658C130,687 119,719 126,747C133,775 120,1931 120,1931C120,1931 128,2029.8000000000002 208,2034.4C288,2039 1697.8,2034.4 1697.8,2034.4C1697.8,2034.4 1788,2016 1794,2124C1800,2232 1780,2700 1780,2700C1780,2700 1796.8000000000002,2794 1689.9,2793C1583,2792 938,2787 938,2787L249,2782C249,2782 151.39999999999998,2769.8 154.7,2863.9C158,2958 155,3552 155,3552C155,3552 159,3645.3999999999996 243.5,3632.7C328,3620 1484,3634 1484,3634L1491.8,3999"
stroke="tomato" fill="none" stroke-width="4"/>
<path class="st11" id="etoile" stroke="tomato" fill="none" stroke-width="4" d="M25,33.5l-22.3,-10l-20.9,12.5l2.8,-24l-18.5,-15.9l24,-4.8l9.5,-22.3l12,21l24.4,2.1l-16.5,17.8z"/>
</g>
</svg>

How do I generate random positions of hexagon using svg?

How do I generate hexagons in random positions on the svg canvas?
Currently, my code uses
.attr("points", "50,25 86,45.83 86,87.5 50,108.3 14,87.53 14,45.83")
which hardcodes the current position of the hexagon. How can I generate other hexagons in different positions while maintaining the hexagonal shape?
Y would create a symbol with a viewBox attribute:
<symbol id="poly" viewBox="14 25 72 83.3">
<polygon points="50,25 86,45.83 86,87.5 50,108.3 14,87.53 14,45.83" />
</symbol>
Since the symbol has a viewBox attribute you can reuse the symbol with <use> and you can specify the position of the hexagon (x and y attributes) and it's size (width and height attributes)
svg{border:1px solid}
<svg viewBox="0 0 500 250">
<symbol id="poly" viewBox="14 25 72 83.3">
<polygon points="50,25 86,45.83 86,87.5 50,108.3 14,87.53 14,45.83" />
</symbol>
<use xlink:href="#poly" x="20" y="20" width="50" height="57.85" />
<use xlink:href="#poly" x="200" y="120" width="100" height="115.7" />
</svg>
Of course the x and y can be random. Also the width or the height can be random. However keep in mind that the other size should be proportional.
This is how I would create the use element with a random x y and width attributes:
const SVG_NS = 'http://www.w3.org/2000/svg';
const SVG_XLINK = "http://www.w3.org/1999/xlink";
//create a new use element
let use = document.createElementNS(SVG_NS, 'use');
// set the value for 'xlink:href' of the new use element
use.setAttributeNS(SVG_XLINK, 'xlink:href', '#poly');
//the random width
let w = Math.random()*50;
// the proportiopnal height
let h = w*83.3 / 75;
//set the position and the size of the use element
use.setAttributeNS(null, 'x', Math.random()*(500 - w));
use.setAttributeNS(null, 'y', Math.random()*(250 - h));
use.setAttributeNS(null, 'width', w);
use.setAttributeNS(null, 'height', h);
//Append the use element
svg.appendChild(use);
svg{border:1px solid}
<svg id="svg" viewBox="0 0 500 250">
<symbol id="poly" viewBox="14 25 72 83.3">
<polygon tran points="50,25 86,45.83 86,87.5 50,108.3 14,87.53 14,45.83" />
</symbol>
</svg>
Create a function drawHex(x,y) where you pass x and y as starting coordinates. On that function the you draw your points relative to x and y:
..."x+50,y+25 x+86,y+45.83 ... x+14,y+45.83";
Finally, create a loop that randomly generates x and y and calls the drawHex function. I'm recently working on something similar. You can take a look and my source code at this P5js experiment and then go to creaPuerta() function on https://zoada.com/lpa/js/parametrica.js
Based on Robert Longson's comment, you could do it like that:
const btn = document.getElementById('btn');
const poly = document.getElementById('poly');
btn.onclick = () => {
const transform = `translate(${getRandomArbitrary(0, 100)} ${getRandomArbitrary(0, 100)}) scale(${getRandomArbitrary(1, 5)} ${getRandomArbitrary(1, 5)})`;
poly.setAttribute('transform', transform);
};
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
button {
display: block;
}
<button id="btn">Random</button>
<svg width=500 height=500>
<polygon id="poly" points="50,25 86,45.83 86,87.5 50,108.3 14,87.53 14,45.83"></polygon>
</svg>

Change SVG fill in a loop

I have simple SVG illustration, is there is any way to change it's color constantly ? like a loop non-stop random color change.
here is my svg
<svg width="533" height="499" viewBox="0 0 533 499" fill="none" xmlns="http://www.w3.org/2000/svg">
This is how I would do it: I'm using colors hsl for the fill and I'm animating the hue of the colors using requestAnimationFrame. I hope it helps.
let p1 = document.querySelectorAll("path")[0];
let p2 = document.querySelectorAll("path")[1]
let h = 0;
function changeColor(){
window.requestAnimationFrame(changeColor);
h+=.5;
h2=210+h;
p1.setAttributeNS(null,"fill", `hsl(${~~h},100%,50%)`);
p2.setAttributeNS(null,"fill", `hsl(${~~h2},100%,50%)`);
}
changeColor()
<svg width="533" height="499" viewBox="0 0 533 499" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M258.089 59.6035C309.803 -3.94652 379.363 78.1818 407.679 127.19C352.338 67.4782 301.718 129.7 287.076 167.787C272.435 205.874 233.694 210.043 205.199 217.679C187.359 222.459 146.446 248.26 128.6 264.085C109.864 289.466 48.3081 292.846 41.8378 268.698C27.0852 213.64 95.5238 148.37 137.644 123.97C163.705 101.458 206.375 123.154 258.089 59.6035Z" fill="blue"/>
<path d="M448.323 394.788C427.389 384.661 420.75 356.279 420.047 343.354C441.009 284.421 527.63 350.762 528.167 368.218C528.703 385.674 474.491 407.447 448.323 394.788Z" fill="red"/>
</svg>
Select the element and recursively call a function that sets the fill attribute of the SVG element you want to recolor with a random hex.
const recolor = element => {
const randomColor = '#'+Math.floor(Math.random()*16777215).toString(16)
circle.setAttribute('fill', randomColor)
setTimeout(() => recolor(element), 600)
}
recolor(document.querySelector('#circle'))
svg circle {
transition: fill .5s linear;
}
<svg height="100" width="100">
<circle id="circle" cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
</svg>
fill on <svg> doesnot work. Its for its elements. You can change its background
function random_rgba() {
var o = Math.round, r = Math.random, s = 255;
return 'rgba(' + o(r()*s) + ',' + o(r()*s) + ',' + o(r()*s) + ',' + r().toFixed(1) + ')';
}
function changeColor(){
document.querySelector('svg').style.background = random_rgba();
}
setInterval(changeColor,200);
<svg width="533" height="499" viewBox="0 0 533 499" fill="none" xmlns="http://www.w3.org/2000/svg">

Why <marker> doesn't orientate as the <path>

I'm trying to create a curved arrow with svg. I'm using d3.line() to generate the path.
let points = [
[400,100],
[450,200],
[350,200],
[385,275]
]
let path = d3.line().curve(d3.curveCardinal)(points)
console.log(path)
// -> M400,100C400,100,458.3333333333333,183.33333333333334,450,200C441.6666666666667,216.66666666666666,360.8333333333333,187.5,350,200C339.1666666666667,212.5,385,275,385,275
But when I try to use this result in a svg:
<svg width="1200" height="1200" viewBox="0 0 1200 1200" xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<marker id="Triangle" viewBox="0 0 10 10" refX="1" refY="5" markerWidth="6" markerHeight="6" orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z" />
</marker>
</defs>
<path d="M400,100C400,100,458.3333333333333,183.33333333333334,450,200C441.6666666666667,216.66666666666666,360.8333333333333,187.5,350,200C339.1666666666667,212.5,385,275,385,275"
stroke-width="2" stroke="lightblue" fill="none" style="marker-end: url(#Triangle);"></path>
</svg>
And here is the SVG result
.
I can't figure out why the marker doesn't orientate. Is there a better library to generate path to resolve this?
That's the expected behaviour. The issue is that in a cardinal spline...
Two additional points are required on either end of the curve.
And those points seem to interfere with the marker orientation (which is indeed the case, see LeBeau's answer).
You can easily see this if you change the curve. For instance, using curveBasis:
let points = [
[400,100],
[450,200],
[350,200],
[385,275]
]
let path = d3.line().curve(d3.curveBasis)(points)
d3.select("#myPath").attr("d", path);
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="1200" height="1200" viewBox="0 0 1200 1200" xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<marker id="Triangle" viewBox="0 0 10 10" refX="1" refY="5" markerWidth="6" markerHeight="6" orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z" />
</marker>
</defs>
<path id="myPath" stroke-width="2" stroke="lightblue" fill="none" style="marker-end: url(#Triangle);"></path>
</svg>
In your case, a solution (arguably a hack) may be adding a final line to the path, just 1px away from the final point:
path = path + "L387,277";
Here is the demo:
let points = [
[400,100],
[450,200],
[350,200],
[385,275]
]
let path = d3.line().curve(d3.curveCardinal)(points)
path = path + "L387,277";
d3.select("#myPath").attr("d", path);
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="1200" height="1200" viewBox="0 0 1200 1200" xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<marker id="Triangle" viewBox="0 0 10 10" refX="1" refY="5" markerWidth="6" markerHeight="6" orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z" />
</marker>
</defs>
<path id="myPath" stroke-width="2" stroke="lightblue" fill="none" style="marker-end: url(#Triangle);"></path>
</svg>
This is because the last control point and the end-point of your path have the same coordinates: (385,275).
SVG uses the control point vector to work out what the curve direction is at that point. If your control point vector is from (385,275) to (385,275), then it can't determine the angle. So it defaults to an angle of 0 degrees.
First, the ref attributes are sort of correct but can be better I think, make the refX 0 since you using the full viewBox.
I think the marker's orientation is correct and updated. But based on the ending of the path, the interpolation of the orientation might look incorrect. So you can verify this behavior by cutting your pathstring from the last C... curve and will see that the orientation is correct.
I further tested it to see if it is correct, at least for line segments, here is a fiddle and i didn't even use d3:
https://jsfiddle.net/ibowankenobi/L8x19rco/2/
var path = document.querySelector("path[stroke]");
var arr = Array.apply(null,Array(path.getTotalLength()/4 << 0)).map(function(d,i){
var p = this.getPointAtLength(i*4);
return [p.x,p.y];
},path);
var length = arr.length;
animate();
function animate(index){
if(index >= length){
return;
}
var index = index || 0;
path.setAttribute("d","M"+arr.slice(1,Math.min(++index+1,length)).join("L"));
window.requestAnimationFrame(function(){animate(index);});
}

Animate the drawing of a dashed svg line

I'm trying to animate an arrow with a dashed line, something like this ------->, but with a horizontal and vertical path, using svg and css animations.
I've tried a couple of different things, animating height and width, using bottom and top, but each way has had something that doesn't quite look good or work well.
I managed to get a path drawn with svg, but the animation will actually remove the dashes and just draw a solid line.
No animation: http://jsfiddle.net/ehan4/2/
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="252px" height="396px" viewBox="0 0 252 396" enable-background="new 0 0 252 396" xml:space="preserve">
<path stroke="#000" stroke-width="1.5" fill="none" d="M50.887,170.063v-53.488h55.814" stroke-dasharray="5,5" stroke-dashoffset="0.00" />
With animation: http://jsfiddle.net/ehan4/1/
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="252px" height="396px" viewBox="0 0 252 396" enable-background="new 0 0 252 396" xml:space="preserve">
<path stroke="#000" stroke-width="1.5" fill="none" d="M50.887,170.063v-53.488h55.814" stroke-dasharray="5,5" stroke-dashoffset="0.00" />
var path = document.querySelector('path');
var length = path.getTotalLength();
path.style.transition = path.style.WebkitTransition = 'none';
path.style.strokeDasharray = length + ' ' + length;
path.style.strokeDashoffset = length;
path.getBoundingClientRect();
path.style.transition = path.style.WebkitTransition = 'stroke-dashoffset 2s ease-in-out';
path.style.strokeDashoffset = '0';
Any thoughts or ideas would be greatly appreciated!
Z
This function can animate the drawing of dashed path:
function drawPath(path, options) {
options = options || {}
var duration = options.duration || 5000
var easing = options.easing || 'ease-in-out'
var reverse = options.reverse || false
var undraw = options.undraw || false
var callback = options.callback || function () {}
var length = path.getTotalLength()
var dashOffsetStates = [length, 0]
if (reverse) {
dashOffsetStates = [length, 2 * length]
}
if (undraw) {
dashOffsetStates.reverse()
}
// Clear any previous transition
path.style.transition = path.style.WebkitTransition = 'none';
var dashArray = path.style.strokeDasharray || path.getAttribute("stroke-dasharray");
if (dashArray != '') {
// dashed path case
// repeats dash pattern as many times as needed to cover path length
var dashLength = dashArray.split(/[\s,]/).map(function (a) {
return parseFloat(a) || 0
}).reduce(function (a, b) {
return a + b
})
var dashCount = length / dashLength + 1
var a = new Array(Math.ceil(dashCount)).join(dashArray + " ")
path.style.strokeDasharray = a + '0' + ' ' + length
} else {
// non dashed path case
path.style.strokeDasharray = length + ' ' + length;
}
path.style.strokeDashoffset = dashOffsetStates[0];
path.getBoundingClientRect();
path.style.transition = path.style.WebkitTransition =
'stroke-dashoffset ' + duration + 'ms ' + easing;
path.style.strokeDashoffset = dashOffsetStates[1]
setTimeout(function() {
path.style.strokeDasharray = dashArray //set back original dash array
callback()
}, duration)
}
It repeats dash pattern as many times as needed to cover path length and then adds empty dash with length of path. It also animates dash offset, as you do in your example.
Working demo: http://jsfiddle.net/u88bm18b/
Duplicated the path as a solid color so it covers the dashed line. Then, animated the solid color line out, revealing the dashed line below.
http://jsfiddle.net/ehan4/4/
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="252px" height="396px" viewBox="0 0 252 396" enable-background="new 0 0 252 396" xml:space="preserve">
<path stroke="#000" stroke-width="1.5" fill="none" d="M50.887,170.063v-53.488h55.814" stroke-dasharray="5,5" stroke-dashoffset="0.00"/>
<path id="top" stroke="#fff" stroke-width="3" fill="none" d="M50.887,170.063v-53.488h55.814"/>
</svg>
<script>
var path = document.querySelector('#top');
var length = path.getTotalLength();
path.style.transition = path.style.WebkitTransition = 'none';
path.style.strokeDasharray = length + ' ' + length;
path.style.strokeDashoffset = '0';
path.getBoundingClientRect();
path.style.transition = path.style.WebkitTransition = 'stroke-dashoffset 2s ease-in-out';
path.style.strokeDashoffset = length;
</script>
SVG has a stroke attribute. Use CSS to change the stroke type to dashed, then change its offset in a javascript loop

Categories