js+svg实现可配置修改参数的麻绳打结动画效果代码

代码语言:html

所属分类:动画

代码描述:js+svg实现可配置修改参数的麻绳打结动画效果代码

代码标签: js svg 配置 修改 参数 麻绳 打结 动画

下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开


<!DOCTYPE html>
<html lang="en" >

<head>

  <meta charset="UTF-8">
  

  
  
  
<style>
* {
  margin: 0;
  padding: 0;
}

body {
  padding: 50px 20px;
  font-family: Helvetica, Arial, sans-serif;
  font-size: 16px;
}

input {
  accent-color: #4394e5;
}

a,
a:visited {
  display: inline-block;
  font-size: 20px;
  line-height: 1;
  margin: 20px 0;
  color: black;
  border-bottom: 1px solid #ccc;
  outline-offset: 5px;
  transition: all 250ms;
  text-decoration: none;
}
a:hover,
a:visited:hover {
  border-bottom-color: #4394e5;
}
a:active,
a:visited:active {
  transform: translateY(1px);
}

.demo {
  max-width: 1000px;
  margin: 0 auto;
  padding: 40px 20px;
}

.demo-svg {
  overflow: visible;
}

path {
  stroke-width: 1.5;
}

.path {
  stroke: #4394e5;
}

.normals path {
  stroke: #4394e5;
}

.points {
  stroke: none;
  fill: #4394e5;
}

.segment path,
.polygons-rounded path,
.polygons path {
  stroke: #4394e5;
}

.demo-controls {
  display: flex;
  flex-wrap: wrap;
  max-width: 750px;
}

.demo-controls-column {
  flex: auto 0 0;
  margin-right: 40px;
}

.demo-title {
  font-weight: bold;
  color: #4394e5;
  margin: 20px 0;
  font-size: 20px;
}

.demo-control {
  display: flex;
  margin-bottom: 10px;
  max-width: 200px;
  flex-wrap: wrap;
}
.demo-control input {
  display: block;
  flex: 100%;
}

.demo-label {
  display: flex;
  margin-bottom: 10px;
}
.demo-label input {
  margin-right: 10px;
}

.demo-control label,
.demo-label {
  font-weight: bold;
}

.demo-value {
  margin-left: 6px;
}
</style>


</head>

<body >
  <div class="demo">
  <svg class="demo-svg" viewBox="-40 -30 580 230"></svg>

  <a target="_blank" href="https://muffinman.io/blog/draw-svg-rope-using-javascript/">Read the blog post &raquo;</a>
  
  <div class="demo-controls">
    <div class="demo-controls-column">
      <div class="demo-title">Options</div>
      <label class="demo-label">
        <input class="demo-checkbox-animate" type="checkbox" checked />
        Animate
      </label>
      <div class="demo-control">
        <label for="demo-step">Segment width</label>
        <span class="demo-value"></span>
        <input data-key="step" type="range" min="3" max="50" step="1" value="20" id="demo-step" />
      </div>
      <div class="demo-control">
        <label for="demo-thickness">Rope thickness</label>
        <span class="demo-value"></span>
        <input data-key="thickness" type="range" min="3" max="50" step="1" value="40" id="demo-thickness" />
      </div>
      <div class="demo-control">
        <label for="demo-angle">Skew angle</label>
        <span class="demo-value"></span>
        <input data-key="angle" type="range" min="0" max="90" step="1" value="45" id="demo-angle" />
      </div>
    </div>
    <div class="demo-controls-column">
      <div class="demo-title">Render</div>
      <label class="demo-label">
        <input data-key="path" class="demo-checkbox" type="checkbox" />
        Path
      </label>
      <label class="demo-label">
        <input data-key="points" class="demo-checkbox" type="checkbox" />
        Points
      </label>
      <label class="demo-label">
        <input data-key="normals" class="demo-checkbox" type="checkbox" />
        Normals
      </label>
      <label class="demo-label">
        <input data-key="polygons" class="demo-checkbox" type="checkbox" />
        Square-ish segments
      </label>
      <label class="demo-label">
        <input data-key="polygonsRounded" class="demo-checkbox" type="checkbox" />
        Rounded square-ish segments
      </label>
      <label class="demo-label">
        <input data-key="segments" class="demo-checkbox" type="checkbox" />
        Segments
      </label>
      <label class="demo-label">
        <input data-key="rope" class="demo-checkbox" type="checkbox" checked />Rounded segments (finished rope)
      </label>
    </div>
    <div class="demo-controls-column">
      <div class="demo-title">Colors</div>
      <label class="demo-label">
        <input class="demo-radio" type="radio" name="demo-colors" value="transparent" />
        Transparent
      </label>
      <label class="demo-label">
        <input class="demo-radio" type="radio" name="demo-colors" value="white" />
        White
      </label>
      <label class="demo-label">
        <input class="demo-radio" type="radio" checked name="demo-colors" value="natural" />
        Natural
      </label>
      <label class="demo-label">
        <input class="demo-radio" type="radio" name="demo-colors" value="rainbow" />
        Rainbow
      </label>
    </div>
  </div>
</div>
 
  
      <script>
// ----- VECTORS ----- //

function multiplyVector(v, scalar) {
  return {
    x: v.x * scalar,
    y: v.y * scalar };

}

function getVector(a, b) {
  return {
    x: b.x - a.x,
    y: b.y - a.y };

}

function addVectors(a, b) {
  return {
    x: a.x + b.x,
    y: a.y + b.y };

}

// ----- MATH ----- //

function getPointOnLine(start, end, ratio) {
  const vector = getVector(start, end);
  const v = multiplyVector(vector, ratio);
  return {
    x: start.x + v.x,
    y: start.y + v.y };

}

function getAngleBetweenThreePoints(a, b, c) {
  const vectorBA = getVector(a, b);
  const vectorBC = getVector(c, b);

  const angle =
  Math.atan2(vectorBC.y, vectorBC.x) - Math.atan2(vectorBA.y, vectorBA.x);

  return angle;
}

// ----- CHAIKIN ----- //

function cut(start, end, ratio) {
  const r1 = {
    x: start.x * (1 - ratio) + end.x * ratio,
    y: start.y * (1 - ratio) + end.y * ratio };

  const r2 = {
    x: start.x * ratio + end.x * (1 - ratio),
    y: start.y * ratio + end.y * (1 - ratio) };

  return [r1, r2];
}

function chaikin(curve, iterations = 1, closed = false, ratio = 0.25) {
  if (ratio > 0.5) {
    ratio = 1 - ratio;
  }

  for (let i = 0; i < iterations; i++) {
    let refined = [];
    refined.push(curve[0]);

    for (let j = 1; j < curve.length; j++) {
      let points = cut(curve[j - 1], curve[j], ratio);
      refined = refined.concat(points);
    }

    if (closed) {
      refined.shift();
      refined = refined.concat(cut(curve[curve.length - 1], curve[0], ratio));
    } else {
      refined.push(curve[curve.length - 1]);
    }

    curve = refined;
  }
  return curve;
}

// ----- ROPE ----- //

function getPathPoints(d, step = 10) {
  // For potential NodeJS version
  // https://www.npmjs.com/package/svg-path-properties
  const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
  path.setAttribute("d", d);

  const length = path.getTotalLength();

  const count = length / step;

  const points = [];

  for (let i = 0; i < count + 1; i++) {
    const n = i * step;
    points.push(path.getPointAtLength(n));
  }

  const vectorStart = getVector(points[1], points[0]);
  const vectorEnd = getVector(
  points[points.length - 2],
  points[points.length - 1]);


  return [
  // Add helper points at the start
  addVectors(points[0], vectorStart),
  ...points,
  // and end
  addVectors(points[points.length - 1], vectorEnd)];

}

// Takes three points and returns two points.
// Points are located at the end of a vector which is bisector of the angle between these three points.
// The distance is "thickness" param
/*
                          • outerPoint[0]
                         /
                        /
             v1 •------• v2
                      / \
                     /   • v3
      outerPoint[1] •
  */
function getOuterPoints(v1, v2, v3, thickness, angleOffset = 0) {
  /*
             v1 •------• v2
               angle1 / \
                     /   • v3
  */
  let angle1 = getAngleBetweenT.........完整代码请登录后点击上方下载按钮下载查看

网友评论0