javascript - 在 JavaScript 中创建随机 SVG 曲线同时避免急转弯

标签 javascript algorithm svg random curve

下面的代码片段在文档上绘制了一定次数的随机连接曲线:

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>

除了某些曲线的锐度之外,这是期望的行为。半径太小, Angular 太锐。我们想要更宽更平滑的曲线。例如,在此屏幕截图中,问题区域已被圈出。

在下图中,请注意红色圆圈具有非常尖锐的曲线,而绿色圆圈是更宽更平滑的曲线:

enter image description here

有没有办法我们可以使用 JavaScript 来防止创建尖锐的曲线(红色圆圈)并且让算法只创建更宽的曲线(绿色圆圈)?

最佳答案

我添加了一些函数来检查最后两点与下一个点之间的 Angular 是否不小于 MIN_ANGLE。现在是 60 度,但可以更宽以获得更大的曲线半径。

我还添加了 MIN_DISTANCE,因为两点之间的距离太短也会提供陡峭的曲线。

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>

我已经清理了代码,添加了 MAX_DISTANCE 来检查:

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>

关于javascript - 在 JavaScript 中创建随机 SVG 曲线同时避免急转弯,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68512066/

相关文章:

javascript - 如何允许网页安全地修改用户的 svg 文件?

css - 创建可以使用 html、css 填充颜色的自定义图形

javascript - window.location 在 javascript 中不起作用

javascript - 需要一些帮助解码 meteor 包安装中的 npm 错误消息

algorithm - 如何求解线性矩阵方程 : AX-XA=B efficiently?

python - 实现中位数为三的快速排序

c++ - QT 中的自定义外观 svg GUI 小部件,性能非常差

javascript - Mongoose - 删除选定的元素

javascript - 平滑滚动到 anchor 偏移像素,除了一个特定的 anchor ?

python - 具有最大池化的卷积神经网络 (CNN)