css 如何确定JavaScript中特定Y坐标的svg路径的长度

xghobddn  于 5个月前  发布在  Java
关注(0)|答案(1)|浏览(54)

例如,我有一个复杂的svg路径,其中我知道某个Y坐标,我需要找到整个路径到这个坐标的长度。

我能想到的最有效的方法是使用二分搜索来找到给定Y坐标的直线的线段。

function binarySearchLengthByYCoord(path, yCoord) {
        const pathTotalLength = +path.getTotalLength();
        let low = 0;
        let high = pathTotalLength;
        let result = 0;

        while (low <= high) {
            const mid = Math.floor((low + high) / 2);
            const point = path.getPointAtLength(mid);

            if (point.y < yCoord) {
                low = mid + 1;
                result = mid;
            } else {
                high = mid - 1;
            }
        }
        return pathTotalLength - result;
    }

字符串
这段代码工作正常,但每次触发滚动事件时都必须调用它,因此在弱设备和手机上,有明显的性能问题。我需要获得与我已经拥有的here类似的效果,并使用更高效的代码。

scrollLineAnimation();
function scrollLineAnimation() {
  const decorParent = document.querySelector("[data-line-animation]"),
    lines = decorParent.querySelectorAll("svg");

  if (lines.length) {
    const line = lines[0],
      path = line.getElementsByTagName("path")[0],
      pathTotalLength = +path.getTotalLength(),
      lastYCoord = path.getPointAtLength(pathTotalLength).y,
      offsetHeight = decorParent.dataset.lineAnimation
        ? decorParent.dataset.lineAnimation
        : 0.8;

    line.style.strokeDasharray = pathTotalLength;
    line.style.strokeDashoffset = pathTotalLength;
    line.style.display = "block";

    let position = 0;
    let prcPostion = 0;

    updateStrokeDashoffset();
    window.addEventListener("scroll", updateStrokeDashoffset);
    window.addEventListener("resize", updateStrokeDashoffset);

    // Function for getting the Y coordinate and finding the line segment
    function updateStrokeDashoffset() {
      const scrollPosition = window.scrollY,
        windowHeight = window.innerHeight,
        svgHeight = line.getBoundingClientRect().height,
        yCoord =
          (scrollPosition + windowHeight * offsetHeight) *
          (lastYCoord / svgHeight),
        newStrokeDashoffsetValue = binarySearchLengthByYCoord(path, yCoord);

      prcPostion = (newStrokeDashoffsetValue / pathTotalLength) * 100;
    }

    // Function for smooth animation
    function setStrokeDashoffset() {
      let dist = prcPostion - position;
      position = position + (dist * 70) / 1000;

      line.style.strokeDashoffset = Math.round(
        (position * pathTotalLength) / 100
      );

      requestAnimationFrame(setStrokeDashoffset);
    }

    requestAnimationFrame(setStrokeDashoffset);
  }

  function binarySearchLengthByYCoord(path, yCoord) {
    const pathTotalLength = +path.getTotalLength();
    let low = 0;
    let high = pathTotalLength;
    let result = 0;

    while (low <= high) {
      const mid = Math.floor((low + high) / 2);
      const point = path.getPointAtLength(mid);

      if (point.y < yCoord) {
        low = mid + 1;
        result = mid;
      } else {
        high = mid - 1;
      }
    }

    return pathTotalLength - result;
  }
}
body {
  max-width: 100%;
  position: relative;
  padding: 0;
  margin: 0;
  min-height: 6000px;
}
.decor {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  max-width: 1440px;
}

.decor svg {
  position: absolute;
  display: none;
  left: 0;
  top: 0;
  width: 100%;
  height: auto;
  min-height: 100%;
}
<div data-line-animation="0.8" class="decor">
  <svg preserveAspectRatio="none" width="1440" height="5141" viewBox="0 0 1440 5141" fill="none" xmlns="http://www.w3.org/2000/svg">
    <path d="M886.805 0.0743205C986.888 -1.4439 1187.91 19.063 1065.22 176.635C911.848 373.6 1455.21 527.592 1367.57 373.601C1279.93 219.61 613.874 395.088 1152.86 656.514C1584.04 865.655 736.569 920.328 258.934 921.522C91.767 929.283 -164.804 1007.13 146.247 1256.43C535.061 1568.06 1238.55 1700.59 1195.7 1890.43C1152.86 2080.27 -384.754 2905.95 146.247 2905.95C1314.52 2905.95 250.102 3383.41 709.046 3545.41C849.007 3594.81 1440.94 3511.17 1410.12 3824.66C1352.7 4408.75 -60.5479 4131.44 95.6015 4451.98C290.788 4852.65 1410.12 5002.4 1440 5140" stroke="#2BB32A" stroke-width="1.5" stroke-linecap="square" />
  </svg>
</div>

你能告诉我一个更有效的方法来找到一个给定的Y坐标的svg路径的长度吗?

slwdgvem

slwdgvem1#

getPointAtLength()非常昂贵,因此您应该尝试减少采样点计算量。
一种方法是先创建一个查找对象,稍后可以使用它将y位置转换为路径长度。

let svg = document.querySelector("svg");
let precision = 100;

function getLengthLookup(path, precision = 100) {
  let pathLength = path.getTotalLength();
  let { x, y, width, height } = path.getBBox();

  //create pathlength lookup
  let lengthLookup = {
    x: x,
    y: y,
    width: width,
    height: height,
    pathLength: pathLength
  };
  let lastY = 0;
  let lastLength = 0;

  // sample point to calculate Y at pathLengths
  let step = Math.floor(pathLength / precision);

  for (let i = 0; i < pathLength; i += step) {
    let pt = path.getPointAtLength(i);
    let y = +pt.y.toFixed(0);
    let yRel = Math.ceil((precision / height) * y);

    /**
     * percentage based y values were skipped
     * interpolate pathlengths in between
     */
    let diffY = Math.abs(yRel - lastY);
    if (diffY) {
      let diffL = (i - lastLength) / diffY;
      let newY = yRel - diffY;
      for (let d = 0; d < diffY; d++) {
        lengthLookup[newY] = lastLength + diffL * d;
        newY++;
      }
    }
    lengthLookup[yRel] = i;
    lastY = yRel;
    lastLength = i;
  }
  return lengthLookup;
}

let lengthLookup = getLengthLookup(path, precision);
// console.log(lengthLookup);

inpHeight.setAttribute("max", precision);
inpHeight.addEventListener("input", (e) => {
  let val = +e.currentTarget.value;
  
  line.setAttribute("y1", (lengthLookup.height / precision) * val);
  line.setAttribute("y2", (lengthLookup.height / precision) * val);
  let dashLength = lengthLookup[val];
  path2.setAttribute(
    "stroke-dasharray",
    `${dashLength} ${lengthLookup.pathLength}`
  );
});
svg {
  border: 1px solid #ccc;
  height: 90vh;
  width: auto;
}

path {
  transition: 1s stroke-dasharray;
}
<p><label>relative height</label><input id="inpHeight" type="range" value="0" min="0" max="100" step="1"></p>
<svg preserveAspectRatio="none" viewBox="0 0 1440 5141" xmlns="http://www.w3.org/2000/svg">
  <line id="line" x1="0" x2="100%" y1="0" y2="0" stroke="purple" stroke-width="2"></line>
  <path id="path" d="M886.805 0.0743205C986.888 -1.4439 1187.91 19.063 1065.22 176.635C911.848 373.6 1455.21 527.592 1367.57 373.601C1279.93 219.61 613.874 395.088 1152.86 656.514C1584.04 865.655 736.569 920.328 258.934 921.522C91.767 929.283 -164.804 1007.13 146.247 1256.43C535.061 1568.06 1238.55 1700.59 1195.7 1890.43C1152.86 2080.27 -384.754 2905.95 146.247 2905.95C1314.52 2905.95 250.102 3383.41 709.046 3545.41C849.007 3594.81 1440.94 3511.17 1410.12 3824.66C1352.7 4408.75 -60.5479 4131.44 95.6015 4451.98C290.788 4852.65 1410.12 5002.4 1440 5140" fill="none" stroke="#2BB32A" stroke-width="3" stroke-linecap="square" />

  <path id="path2" d="M886.805 0.0743205C986.888 -1.4439 1187.91 19.063 1065.22 176.635C911.848 373.6 1455.21 527.592 1367.57 373.601C1279.93 219.61 613.874 395.088 1152.86 656.514C1584.04 865.655 736.569 920.328 258.934 921.522C91.767 929.283 -164.804 1007.13 146.247 1256.43C535.061 1568.06 1238.55 1700.59 1195.7 1890.43C1152.86 2080.27 -384.754 2905.95 146.247 2905.95C1314.52 2905.95 250.102 3383.41 709.046 3545.41C849.007 3594.81 1440.94 3511.17 1410.12 3824.66C1352.7 4408.75 -60.5479 4131.44 95.6015 4451.98C290.788 4852.65 1410.12 5002.4 1440 5140" fill="none" stroke="red" stroke-linecap="square" stroke="red" stroke-width="10" stroke-dasharray="0 20000" />

</svg>

在上面的例子中,我们在总路径长度的每一个百分比处检索点,并保存相对于路径高度的y值。
这个过程不会返回一个连续的相对y值列表(例如0-100)。
当一个范围丢失时,我们根据开始和结束路径长度插值这些值。
显然这只会返回近似值。特别是有自相交环的路径段不能精确计算,因为我们在y位置有多个长度。
100d 1xx 1c 1d 1x的字符串
但是我们可以通过增加样本点的数量来提高精度,例如增加到200。
用于测试see codepen

示例2:滚动动画

let svg = document.querySelector("svg");
let precision = 300;

function getLengthLookup(path, precision = 100) {
  let pathLength = path.getTotalLength();
  let { x, y, width, height } = path.getBBox();

  //create pathlength lookup
  let lengthLookup = {
    x: x,
    y: y,
    width: width,
    height: height,
    pathLength: pathLength
  };
  let lastY = 0;
  let lastLength = 0;

  // sample point to calculate Y at pathLengths
  let step = Math.floor(pathLength / precision);

  for (let i = 0; i < pathLength; i += step) {
    let pt = path.getPointAtLength(i);
    let y = +pt.y.toFixed(0);
    let yRel = Math.ceil((precision / height) * y);

    /**
     * percentage based y values were skipped
     * interpolate pathlengths in between
     */
    let diffY = Math.abs(yRel - lastY);
    if (diffY) {
      let diffL = (i - lastLength) / diffY;
      let newY = yRel - diffY;
      for (let d = 0; d < diffY; d++) {
        lengthLookup[newY] = lastLength + diffL * d;
        newY++;
      }
    }
    lengthLookup[yRel] = i;
    lastY = yRel;
    lastLength = i;
  }
  return lengthLookup;
}

let lengthLookup = getLengthLookup(path, precision);

window.addEventListener("scroll", (e) => {
  let maxHeight = document.documentElement.scrollHeight - window.innerHeight;
  let windowOffset = window.pageYOffset;
  let scrollPosRel = Math.floor((windowOffset * precision) / maxHeight);    
  line.setAttribute("y1", (lengthLookup.height / precision) * scrollPosRel);
  line.setAttribute("y2", (lengthLookup.height / precision) * scrollPosRel);
  let dashLength = lengthLookup[scrollPosRel];
  path2.setAttribute(
    "stroke-dasharray",
    `${dashLength} ${lengthLookup.pathLength}`
  );

});

x

svg {
  border: 1px solid #ccc;
  height: 300vh;
  width: auto;
}

path {
  transition: 1s stroke-dasharray;
}
<svg preserveAspectRatio="none" viewBox="0 0 1440 5141" xmlns="http://www.w3.org/2000/svg">
  <line id="line" x1="0" x2="100%" y1="0" y2="0" stroke="purple" stroke-width="2"></line>
  <path id="path" d="M886.805 0.0743205C986.888 -1.4439 1187.91 19.063 1065.22 176.635C911.848 373.6 1455.21 527.592 1367.57 373.601C1279.93 219.61 613.874 395.088 1152.86 656.514C1584.04 865.655 736.569 920.328 258.934 921.522C91.767 929.283 -164.804 1007.13 146.247 1256.43C535.061 1568.06 1238.55 1700.59 1195.7 1890.43C1152.86 2080.27 -384.754 2905.95 146.247 2905.95C1314.52 2905.95 250.102 3383.41 709.046 3545.41C849.007 3594.81 1440.94 3511.17 1410.12 3824.66C1352.7 4408.75 -60.5479 4131.44 95.6015 4451.98C290.788 4852.65 1410.12 5002.4 1440 5140" fill="none" stroke="#2BB32A" stroke-width="3" stroke-linecap="square" />
  <path id="path2" d="M886.805 0.0743205C986.888 -1.4439 1187.91 19.063 1065.22 176.635C911.848 373.6 1455.21 527.592 1367.57 373.601C1279.93 219.61 613.874 395.088 1152.86 656.514C1584.04 865.655 736.569 920.328 258.934 921.522C91.767 929.283 -164.804 1007.13 146.247 1256.43C535.061 1568.06 1238.55 1700.59 1195.7 1890.43C1152.86 2080.27 -384.754 2905.95 146.247 2905.95C1314.52 2905.95 250.102 3383.41 709.046 3545.41C849.007 3594.81 1440.94 3511.17 1410.12 3824.66C1352.7 4408.75 -60.5479 4131.44 95.6015 4451.98C290.788 4852.65 1410.12 5002.4 1440 5140" fill="none" stroke="red" stroke-linecap="square" stroke="red" stroke-width="10" stroke-dasharray="0 20000" />
</svg>

的一个字符串

相关问题