The snippet below draws random connected curves over the document a certain number of times:
function createPath() {
const
dimensions = getWindowDimensions(), svg = document.querySelector( `svg` ),
path = document.createElementNS( `http://www.w3.org/2000/svg`, `path` );
dimensions[0] = dimensions[0]; dimensions[1] = dimensions[1];
svg.appendChild( path );
path.setAttribute(
`d`,
`M ` +
`${getRandomNumber(dimensions[0])} `+`${getRandomNumber(dimensions[1])} `+
`C `+
`${getRandomNumber(dimensions[0])} `+`${getRandomNumber( dimensions[1])}, `+
`${getRandomNumber(dimensions[0])} `+`${getRandomNumber( dimensions[1])}, `+
`${getRandomNumber(dimensions[0])} `+`${getRandomNumber( dimensions[1])} `
)
for( let i = 0; i < 100; i++ ) {
path.setAttribute(
`d`,
path.getAttribute( `d` ) +
`S `+`${getRandomNumber(dimensions[0])} `+`${getRandomNumber(dimensions[1])},`+
`${getRandomNumber(dimensions[0])} `+`${getRandomNumber(dimensions[1])} `
)
}
}
setInterval( setSVGDimensions, 10 ); setInterval( positionPath, 10 );
createPath();
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
display: flex; justify-content: center; align-items: center;
}
svg {
border-radius: 1rem; background-color: rgba( 95%,95%,95%,0.5 );
filter: blur( 1rem );
animation-name: blur; animation-duration: 1s;
animation-timing-function: ease-in-out; animation-fill-mode: forwards;
stroke-linecap: round; stroke-linejoin: round;
stroke-width: 0.25rem; fill: none;
}
@keyframes blur {
100% { filter: blur( 0rem ); }
}
path {
animation-name: grow;
animation-duration: 2500s;
animation-timing-function: cubic-bezier( 0.75,0.25,0.25,1 );
stroke-dasharray: 1000000;
stroke-dashoffset: 1000000;
stroke: rgba( 0%,100%,75%,0.75 );
}
@keyframes grow {
100% { stroke-dashoffset: 0; }
}
<svg></svg>
<script>
function getRandomNumber( max ) { return Math.floor( Math.random() * max ); }
function getWindowDimensions() {
const
dimensions = [],
windowWidth = document.documentElement.clientWidth,
windowHeight = document.documentElement.clientHeight;
dimensions.push( windowWidth, windowHeight );
return dimensions;
}
function setSVGDimensions() {
const
dimensions = getWindowDimensions(), svg = document.querySelector( `svg` );
svg.style.width = dimensions[0] - 10; svg.style.height = dimensions[1] - 10;
}
function positionPath() {
const
dimensions = getWindowDimensions(), path = document.querySelector( `path` );
path.setAttribute(
`transform`,
`
scale( 0.5 ) translate( ${ dimensions[0] / 2 },${ dimensions[1] / 3 } )
`
)
}
</script>
This is the desired behavior except for the sharpness of some curves. The radius is too small, the angle is too acute. We want wider smoother curves. For example in this screenshot the problem areas are circled.
In the picture below notice the red circles have very sharp curves whereas the green circled are wider smoother curves:
Is there a way we could use JavaScript to prevent the creation of the sharp curves ( circled in red ) and have the algorithm only create wider curves ( circled in green )?
I've added some functions to check if the angle between last two points and the next one is not less than the MIN_ANGLE
. Now it is 60 degrees, but it can be wider to get bigger radius of curves.
I've also added MIN_DISTANCE
because too short distance between two points provides sharp curves too.
let lastTwoPoints = [];
const MIN_ANGLE = 60;
const MIN_DISTANCE = (Math.min(...getWindowDimensions()))/10;
function getPoint(){
let point = [getRandomNumber(getWindowDimensions()[0]),getRandomNumber(getWindowDimensions()[1])];
if(lastTwoPoints.length < 2){
lastTwoPoints.push(point);
} else {
if(getAngle(...lastTwoPoints, point) < MIN_ANGLE || getDistance(lastTwoPoints[1],point) < MIN_DISTANCE){
point = getPoint();
} else {
lastTwoPoints.shift();
lastTwoPoints.push(point);
}
}
return point;
}
function pointString(){
let point = getPoint();
return `${point[0]} ${point[1]} `;
}
function getDistance(pointA, pointB){
return Math.sqrt((pointA[0] - pointB[0])**2 + (pointA[1] - pointB[1])**2);
}
function getAngle(pointA, pointB, pointC){ // angle to pointB
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 createPath() {
const
dimensions = getWindowDimensions(), svg = document.querySelector( `svg` ),
path = document.createElementNS( `http://www.w3.org/2000/svg`, `path` );
dimensions[0] = dimensions[0]; dimensions[1] = dimensions[1];
svg.appendChild( path );
path.setAttribute(
`d`,
`M ` +
`${pointString()}`+
`C `+
`${pointString()}`+
`${pointString()}`+
`${pointString()}`
)
for( let i = 0; i < 100; i++ ) {
path.setAttribute(
`d`,
path.getAttribute( `d` ) +
`S `+`${pointString()}`+
`${pointString()}`
)
}
}
setInterval( setSVGDimensions, 10 ); setInterval( positionPath, 10 );
createPath();
function getRandomNumber( max ) { return Math.floor( Math.random() * max ); }
function getWindowDimensions() {
const
dimensions = [],
windowWidth = document.documentElement.clientWidth,
windowHeight = document.documentElement.clientHeight;
dimensions.push( windowWidth, windowHeight );
return dimensions;
}
function setSVGDimensions() {
const
dimensions = getWindowDimensions(), svg = document.querySelector( `svg` );
svg.style.width = dimensions[0] - 10; svg.style.height = dimensions[1] - 10;
}
function positionPath() {
const
dimensions = getWindowDimensions(), path = document.querySelector( `path` );
path.setAttribute(
`transform`,
`
scale( 0.5 ) translate( ${ dimensions[0] / 2 },${ dimensions[1] / 3 } )
`
)
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
display: flex; justify-content: center; align-items: center;
}
svg {
border-radius: 1rem; background-color: rgba( 95%,95%,95%,0.5 );
filter: blur( 1rem );
animation-name: blur; animation-duration: 1s;
animation-timing-function: ease-in-out; animation-fill-mode: forwards;
stroke-linecap: round; stroke-linejoin: round;
stroke-width: 0.25rem; fill: none;
}
@keyframes blur {
100% { filter: blur( 0rem ); }
}
path {
animation-name: grow;
animation-duration: 2500s;
animation-timing-function: cubic-bezier( 0.75,0.25,0.25,1 );
stroke-dasharray: 1000000;
stroke-dashoffset: 1000000;
stroke: rgba( 0%,100%,75%,0.75 );
}
@keyframes grow {
100% { stroke-dashoffset: 0; }
}
<svg></svg>
I've cleaned up the code, added MAX_DISTANCE
to check:
let lastTwoPoints = [];
const W = document.documentElement.clientWidth;
const H = document.documentElement.clientHeight;
const MIN_ANGLE = 60;
const MIN_DISTANCE = (Math.min(W,H))/20;
const MAX_DISTANCE = (Math.min(W,H))/4;
let svg = document.querySelector('svg');
let path = document.querySelector('path');
svg.style.width = W;
svg.style.height = H;
createPath();
function getPoint(){
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 = getPoint();
} else {
lastTwoPoints.shift();
lastTwoPoints.push(point);
}
}
return point;
}
function pointString(){
let point = getPoint();
return `${point[0]} ${point[1]} `;
}
function getDistance(pointA, pointB){
return Math.sqrt((pointA[0] - pointB[0])**2 + (pointA[1] - pointB[1])**2);
}
function getAngle(pointA, pointB, pointC){ // angle to pointB
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 createPath() {
let path_string = `M ${pointString()} C ${pointString()} ${pointString()} ${pointString()}`;
for( let i = 0; i < 100; i++ ) {
path_string += `S ${pointString()} ${pointString()} `
}
path.setAttribute('d', path_string);
}
function getRandomNumber(max) { return Math.floor( Math.random() * max ); }
<svg fill="none" stroke="black">
<path d=""/>
</svg>