当我调整屏幕大小时,如何向下滚动 pathIcon 移动屏幕中心并沿着 path-svg 移动?

问题描述 投票:0回答:1

目前,我的pathIcon移动遵循中心屏幕的path-svg。

我的问题:当我调整屏幕大小时,path-svg(注意:我设置了witdh,svg的高度是vw单位以方便调整大小)具有新的大小,然后当我调整屏幕大小时,pathIcon不会沿着path-svg移动向下滚动,但它沿着旧路径移动(我认为是这样)。

我想要什么:我希望路径图标在第一次重新加载时始终位于中线。当我调整屏幕大小时,我向下滚动 pathIcon 总是沿着 path-svg 移动。

  let svg = document.querySelector(".svg-path");
      let mPath = document.getElementById("Path_440");
      let strokePath = document.getElementById("theFill");
      let pathIcon = document.getElementById("pathIcon");

      // add offset path based on svg
      pathIcon.style.offsetPath = `path('${mPath.getAttribute("d")}')`;

      // steps for pathlength lookup
      let precision = 1000;

      // get transform matrix to translate svg units to screen coordinates
      let matrix = svg.getScreenCTM();

      function getLengthLookup(path, precision = 100) {
        //create pathlength lookup
        let pathLength = path.getTotalLength();
        let lengthLookup = {
          yArr: [],
          lengthArr: [],
          pathLength: pathLength,
        };

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

        for (let l = 0; l < pathLength; l += step) {
          let pt = SVGToScreen(matrix, path.getPointAtLength(l));
          let y = pt.y;
          lengthLookup.yArr.push(y);
          lengthLookup.lengthArr.push(l);
        }
        return lengthLookup;
      }

      const lengthLookup = getLengthLookup(mPath, precision);
      const { lengthArr, yArr, pathLength } = lengthLookup;
      const maxHeight =
        document.documentElement.scrollHeight - window.innerHeight;

      window.addEventListener("scroll", (e) => {
        let scrollPosMid = getViewportMiddleY();
        midline.style.top = scrollPosMid + "px";

        // get y pos length
        let found = false;

        for (let i = 0; i < yArr.length && !found; i++) {
          // find next largest y in lookup
          let y = yArr[i];
          if (y >= scrollPosMid) {
            let length = lengthArr[i];

            // adjust length via interpolated approximation
            let yPrev = yArr[i - 1] ? yArr[i - 1] : yArr[i];
            let lengthPrev = lengthArr[i - 1] ? lengthArr[i - 1] : length;
            let ratioL = (1 / lengthArr[i]) * lengthPrev;
            let ratioY = (1 / y) * scrollPosMid;
            let ratio = Math.max(ratioL, ratioY);

            let dashLength = lengthArr[i] * ratio;

            // calculate offsetDistance
            let offsetDist = (100 / pathLength) * dashLength;
            pathIcon.style.offsetDistance = offsetDist + "%";

            // change dasharray
            strokePath.setAttribute(
              "stroke-dasharray",
              `${dashLength} ${pathLength}`
            );

            // stop loop
            found = true;
          }
        }
      });

      /**
       * Get the absolute center/middle y-coordinate
       * of the current scroll viewport
       */
      function getViewportMiddleY() {
        const viewportHeight = window.innerHeight;
        const scrollY = window.scrollY || window.pageYOffset;
        const element = document.documentElement;
        const elementOffsetTop = element.offsetTop;
        const middleY = scrollY + viewportHeight / 2 + elementOffsetTop;
        return middleY;
      }


      function SVGToScreen(matrix, pt) {
        let p = new DOMPoint(pt.x, pt.y);
        p = p.matrixTransform(matrix);
        return p;
      }
    .svg-path {
      overflow: visible;
      width: 93vw;
      height: 159vw;
    }

    #pathIcon {
      position: absolute;
      inset: 0;
      width: 100px;
      height: 200px;
      background-size: 25px;
      offset-rotate: 0rad;
      transition: 0.2s;
      offset-distance: 0%;
    }

    #Path_440 {
      stroke-width: 2;
      stroke: #001d36;
    }

    #midline {
      display: block;
      position: absolute;
      width: 100%;
      height: 1px;
      border-top: 1px solid orange;
    }
    <div style="height: 175px"></div>
    <div id="scrollDiv" style="position: relative">
      <svg class="svg-path" viewBox="0 0 1783 3038" fill="none">
        <defs>
          <path
            id="Path_440"
            d="M1292 1C1292 1 1276 75.4667 1196 92.5C1104.55 111.972 887.329 90.0678 771.5 99.5C544.5 99.5 301.61 124.941 278 294.5C267 373.5 264.112 418.83 267 502C269.888 585.17 268.896 644.646 304 740C345.771 853.462 769 837.5 1093.5 831C1365 841.5 1675.02 791 1765 1010C1802.18 1100.5 1765 1456.5 1765 1492.5C1765 1560 1695 1612.5 1617 1626.5C1464 1626.5 1187.11 1631.5 1002.5 1631.5C731.5 1631.5 368.526 1604.69 311 1716.5C270.612 1795 275 1919 275 1981C276.117 2073.28 271.553 2143.17 311 2257C338.857 2337.39 720.155 2323.5 980 2323.5C1296 2323.5 1676.34 2306.5 1738.5 2402.5C1792.57 2486 1771.73 2615.18 1771.73 2764C1771.73 2892 1769.73 2996.5 1651 3031C1611.67 3042.43 1236 3031 892 3031C404.5 3031 1 3029 1 3029"
            stroke="#020878"
            stroke-width="2"
            stroke-miterlimit="10"
            stroke-linecap="round"
            stroke-dasharray="20 20"
          />
        </defs>
        <use href="#Path_440" stroke-width="10" stroke-dasharray="20 10"></use>
        <use
          id="theFill"
          href="#Path_440"
          stroke-dasharray="1991.82, 9259.88"
          stroke-width="10"
          stroke="#4cacff"
        ></use>
      </svg>
      <svg
        id="pathIcon"
        fill="none"
        xmlns="http://www.w3.org/2000/svg"
        xmlns:xlink="http://www.w3.org/1999/xlink"
      >
        <rect width="100" height="200" fill="url(#pattern0)" />
        <defs>
          <pattern
            id="pattern0"
            patternContentUnits="objectBoundingBox"
            width="1"
            height="1"
          >
            <use
              xlink:href="#image0_873_8619"
              transform="scale(0.00353357 0.00176678)"
            />
          </pattern>
          <image
            id="image0_873_8619"
            width="283"
            height="566"
            xlink:href="data:image/png;base64,iVBORw0KGgoAAAA"
          />
        </defs>
      </svg>
    </div>

    <div id="midline"></div>

当我调整屏幕大小时,我尝试计算新的path-svg,但它并不能完全满足我的需要。我不确定我发现的问题是否正确。

请帮助我。你应该整页看看我说的:(

更新(24/03/2024):成功调整大小,但我有新问题

我按照这篇文章在此处输入链接描述,并成功确保每次调整屏幕大小时路径图标始终保留在 SVG 路径上。但是,我的滚动代码出现了意外问题;当我滚动时,路径图标不再沿中线移动。我不知道为什么会这样。

这是我添加功能滚动后的代码,它不会移动屏幕中心

let svg = document.querySelector(".svg-path");
      let mPath = document.getElementById("Path_440");
      let strokePath = document.getElementById("theFill");
      let pathIcon = document.getElementById("pathIcon");

      document.addEventListener("DOMContentLoaded", function () {
        // auto adjust viewBox
        let svgRect = svg.getBBox();
        let width = svgRect.width;
        let height = svgRect.height;
        svg.setAttribute("viewBox", `0 0 ${width} ${height}`);

        // update offset path
        defineNewOffsetPath();
      });

      function defineNewOffsetPath() {
        // retrieve the current scale from SVG transformation matrix
        let matrix = svg.getCTM();
        let scale = matrix.a;

        // parse path data
        let d = mPath.getAttribute("d");
        let pathData = parsePathData(d);

        //scale pathdata
        pathData = scalePathData(pathData, scale);

        // apply scaled pathdata as stringified d attribute value
        d = pathDataToD(pathData);

        pathIcon.style.offsetPath = `path('${d}')`;
      }

      // recalculate offset path on resize
      window.addEventListener("resize", (e) => {
        defineNewOffsetPath();
        matrix = svg.getScreenCTM();
        updateLengthLookup();
      });

      // just for illustration
      resizeObserver();
      function resizeObserver() {
        defineNewOffsetPath();
      }
      new ResizeObserver(resizeObserver).observe(scrollDiv);
      /**
       * sclae path data proportional
       */
      function scalePathData(pathData, scale = 1) {
        let pathDataScaled = [];
        pathData.forEach((com, i) => {
          let { type, values } = com;
          let comT = {
            type: type,
            values: [],
          };

          switch (type.toLowerCase()) {
            // lineto shorthands
            case "h":
              comT.values = [values[0] * scale]; // horizontal - x-only
              break;
            case "v":
              comT.values = [values[0] * scale]; // vertical - x-only
              break;

            // arcto
            case "a":
              comT.values = [
                values[0] * scale, // rx: scale
                values[1] * scale, // ry: scale
                values[2], // x-axis-rotation: keep it
                values[3], // largeArc: dito
                values[4], // sweep: dito
                values[5] * scale, // final x: scale
                values[6] * scale, // final y: scale
              ];
              break;

            /**
             * Other point based commands: L, C, S, Q, T
             * scale all values
             */
            default:
              if (values.length) {
                comT.values = values.map((val, i) => {
                  return val * scale;
                });
              }
          }
          pathDataScaled.push(comT);
        });
        return pathDataScaled;
      }

      /**
       * parse stringified path data used in d attribute
       * to an array of computable command data
       */
      function parsePathData(d) {
        d = d
          // remove new lines, tabs an comma with whitespace
          .replace(/[\n\r\t|,]/g, " ")
          // pre trim left and right whitespace
          .trim()
          // add space before minus sign
          .replace(/(\d)-/g, "$1 -")
          // decompose multiple adjacent decimal delimiters like 0.5.5.5 => 0.5 0.5 0.5
          .replace(/(\.)(?=(\d+\.\d+)+)(\d+)/g, "$1$3 ");

        let pathData = [];
        let cmdRegEx = /([mlcqazvhst])([^mlcqazvhst]*)/gi;
        let commands = d.match(cmdRegEx);

        // valid command value lengths
        let comLengths = {
          m: 2,
          a: 7,
          c: 6,
          h: 1,
          l: 2,
          q: 4,
          s: 4,
          t: 2,
          v: 1,
          z: 0,
        };
        commands.forEach((com) => {
          let type = com.substring(0, 1);
          let typeRel = type.toLowerCase();
          let isRel = type === typeRel;
          let chunkSize = comLengths[typeRel];

          // split values to array
          let values = com
            .substring(1, com.length)
            .trim()
            .split(" ")
            .filter(Boolean);

          /**
           * A - Arc commands
           * large arc and sweep flags
           * are boolean and can be concatenated like
           * 11 or 01
           * or be concatenated with the final on path points like
           * 1110 10 => 1 1 10 10
           */
          if (typeRel === "a" && values.length != comLengths.a) {
            let n = 0,
              arcValues = [];
            for (let i = 0; i < values.length; i++) {
              let value = values[i];

              // reset counter
              if (n >= chunkSize) {
                n = 0;
              }
              // if 3. or 4. parameter longer than 1
              if ((n === 3 || n === 4) && value.length > 1) {
                let largeArc = n === 3 ? value.substring(0, 1) : "";
                let sweep =
                  n === 3 ? value.substring(1, 2) : value.substring(0, 1);
                let finalX = n === 3 ? value.substring(2) : value.substring(1);
                let comN = [largeArc, sweep, finalX].filter(Boolean);
                arcValues.push(comN);
                n += comN.length;
              } else {
                // regular
                arcValues.push(value);
                n++;
              }
            }
            values = arcValues.flat().filter(Boolean);
          }

          // string  to number
          values = values.map(Number);

          // if string contains repeated shorthand commands - split them
          let hasMultiple = values.length > chunkSize;
          let chunk = hasMultiple ? values.slice(0, chunkSize) : values;
          let comChunks = [
            {
              type: type,
              values: chunk,
            },
          ];

          // has implicit or repeated commands – split into chunks
          if (hasMultiple) {
            let typeImplicit = typeRel === "m" ? (isRel ? "l" : "L") : type;
            for (let i = chunkSize; i < values.length; i += chunkSize) {
              let chunk = values.slice(i, i + chunkSize);
              comChunks.push({
                type: typeImplicit,
                values: chunk,
              });
            }
          }
          comChunks.forEach((com) => {
            pathData.push(com);
          });
        });

        /**
         * first M is always absolute/uppercase -
         * unless it adds relative linetos
         * (facilitates d concatenating)
         */
        pathData[0].type = "M";
        return pathData;
      }

      /**
       * serialize pathData array to
       * d attribute string
       */
      function pathDataToD(pathData, decimals = 3) {
        let d = ``;
        pathData.forEach((com) => {
          d += `${com.type}${com.values
            .map((val) => {
              return +val.toFixed(decimals);
            })
            .join(" ")}`;
        });
        return d;
      }
      // steps for pathlength lookup
      let precision = 1000;

      // get transform matrix to translate svg units to screen coordinates
      let matrix = svg.getScreenCTM();

      function getLengthLookup(path, precision = 100) {
        //create pathlength lookup
        let pathLength = path.getTotalLength();
        let lengthLookup = {
          yArr: [],
          lengthArr: [],
          pathLength: pathLength,
        };

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

        for (let l = 0; l < pathLength; l += step) {
          let pt = SVGToScreen(matrix, path.getPointAtLength(l));
          let y = pt.y;
          lengthLookup.yArr.push(y);
          lengthLookup.lengthArr.push(l);
        }
        return lengthLookup;
      }

      const lengthLookup = getLengthLookup(mPath, precision);
      const { lengthArr, yArr, pathLength } = lengthLookup;
      const maxHeight =
        document.documentElement.scrollHeight - window.innerHeight;
      function updateLengthLookup() {
        lengthLookup = getLengthLookup(mPath, precision);
        lengthArr = lengthLookup.lengthArr;
        yArr = lengthLookup.yArr;
        pathLength = lengthLookup.pathLength;
      }

      window.addEventListener("scroll", (e) => {
        scrollPathicon();
      });
      function scrollPathicon() {
        let scrollPosMid = getViewportMiddleY();
        midline.style.top = scrollPosMid + "px";

        // get y pos length
        let found = false;

        for (let i = 0; i < yArr.length && !found; i++) {
          // find next largest y in lookup
          let y = yArr[i];
          if (y >= scrollPosMid) {
            let length = lengthArr[i];

            // adjust length via interpolated approximation
            let yPrev = yArr[i - 1] ? yArr[i - 1] : yArr[i];
            let lengthPrev = lengthArr[i - 1] ? lengthArr[i - 1] : length;
            let ratioL = (1 / lengthArr[i]) * lengthPrev;
            let ratioY = (1 / y) * scrollPosMid;
            let ratio = Math.max(ratioL, ratioY);

            let dashLength = lengthArr[i] * ratio;

            // calculate offsetDistance
            let offsetDist = (100 / pathLength) * dashLength;
            pathIcon.style.offsetDistance = offsetDist + "%";

            // change dasharray
            strokePath.setAttribute(
              "stroke-dasharray",
              `${dashLength} ${pathLength}`
            );

            // stop loop
            found = true;
          }
        }
      }

      /**
       * Get the absolute center/middle y-coordinate
       * of the current scroll viewport
       */
      function getViewportMiddleY() {
        const viewportHeight = window.innerHeight;
        const scrollY = window.scrollY || window.pageYOffset;
        const element = document.documentElement;
        const elementOffsetTop = element.offsetTop;
        const middleY = scrollY + viewportHeight / 2 + elementOffsetTop;
        return middleY;
      }

      function SVGToScreen(matrix, pt) {
        let p = new DOMPoint(pt.x, pt.y);
        p = p.matrixTransform(matrix);
        return p;
      }
 html {
        margin: 0;
        padding: 0;
      }

      .svg-path {
        overflow: visible;
        width: 100%;
      }

      #pathIcon {
        position: absolute;
        inset: 0;
        width: 5vw;
        height: 5vw;
        offset-rotate: 0deg;
        offset-distance: 10%;
      }

      #scrollDiv {
       resize:both;
       overflow:auto;
       border: 1px solid #ccc;
       margin:10px;
      }
      #midline {
        display: block;
        position: absolute;
        width: 100%;
        height: 1px;
        border-top: 1px solid orange;
      }
<div id="scrollDiv" style="position: relative">
      <svg class="svg-path" viewBox="0 0 0 0" fill="none">
        <defs>
          <path
            id="Path_440"
            d="M1293 2 s-16 74.47-96 91.5c-91.45 19.47-308.67-2.43-424.5 7-227 0-469.89 25.44-493.5 195-11 79-13.89 124.33-11 207.5s1.9 142.65 37 238c41.77 113.46 465 97.5 789.5 91 271.5 10.5 581.52-40 671.5 179 37.18 90.5 0 446.5 0 482.5 0 67.5-70 120-148 134-153 0-429.89 5-614.5 5-271 0-633.97-26.81-691.5 85-40.39 78.5-36 202.5-36 264.5 1.12 92.28-3.45 162.17 36 276 27.86 80.39 409.15 66.5 669 66.5 316 0 696.34-17 758.5 79 54.07 83.5 33.23 212.68 33.23 361.5 0 128-2 232.5-120.73 267-39.33 11.43-415 0-759 0-487.5 0-891-2-891-2"
            stroke="#020878"
            stroke-width="2"
            stroke-miterlimit="10"
            stroke-linecap="round"
            stroke-dasharray="20 20"
          />
        </defs>
        <use
          class="stroke"
          href="#Path_440"
          stroke="#ccc"
          stroke-width="10"
          stroke-dasharray="20 10"
        ></use>
        <use
          class="stroke"
          id="theFill"
          href="#Path_440"
          stroke-dasharray="925.988 9259.88"
          stroke-width="10"
          stroke="#4cacff"
        ></use>
      </svg>
      <svg id="pathIcon" fill="none">
        <rect width="100" height="100" fill="red" fill-opacity="0.5" />
      </svg>
      <div id="midline"></div>
    </div>

javascript html css svg scroll
1个回答
1
投票
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Scroll Path Icon</title>
<style>
  .svg-path {
    overflow: visible;
    width: 93vw;
    height: 159vw;
  }

  #pathIcon {
    position: absolute;
    inset: 0;
    width: 100px;
    height: 200px;
    background-size: 25px;
    offset-rotate: 0rad;
    transition: 0.2s;
    offset-distance: 0%;
  }

  #Path_440 {
    stroke-width: 2;
    stroke: #001d36;
  }

  #midline {
    display: block;
    position: absolute;
    width: 100%;
    height: 1px;
    border-top: 1px solid orange;
  }
</style>
</head>
<body>
<div style="height: 175px"></div>
<div id="scrollDiv" style="position: relative">
  <svg class="svg-path" viewBox="0 0 1783 3038" fill="none">
    <defs>
      <path
        id="Path_440"
        d="M1292 1C1292 1 1276 75.4667 1196 92.5C1104.55 111.972 887.329 90.0678 771.5 99.5C544.5 99.5 301.61 124.941 278 294.5C267 373.5 264.112 418.83 267 502C269.888 585.17 268.896 644.646 304 740C345.771 853.462 769 837.5 1093.5 831C1365 841.5 1675.02 791 1765 1010C1802.18 1100.5 1765 1456.5 1765 1492.5C1765 1560 1695 1612.5 1617 1626.5C1464 1626.5 1187.11 1631.5 1002.5 1631.5C731.5 1631.5 368.526 1604.69 311 1716.5C270.612 1795 275 1919 275 1981C276.117 2073.28 271.553 2143.17 311 2257C338.857 2337.39 720.155 2323.5 980 2323.5C1296 2323.5 1676.34 2306.5 1738.5 2402.5C1792.57 2486 1771.73 2615.18 1771.73 2764C1771.73 2892 1769.73 2996.5 1651 3031C1611.67 3042.43 1236 3031 892 3031C404.5 3031 1 3029 1 3029"
        stroke="#020878"
        stroke-width="2"
        stroke-miterlimit="10"
        stroke-linecap="round"
        stroke-dasharray="20 20"
      />
    </defs>
    <use href="#Path_440" stroke-width="10" stroke-dasharray="20 10"></use>
    <use
      id="theFill"
      href="#Path_440"
      stroke-dasharray="1991.82, 9259.88"
      stroke-width="10"
      stroke="#4cacff"
    ></use>
  </svg>
  <svg
    id="pathIcon"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink"
  >
    <rect width="100" height="200" fill="url(#pattern0)" />
    <defs>
      <pattern
        id="pattern0"
        patternContentUnits="objectBoundingBox"
        width="1"
        height="1"
      >
        <use
          xlink:href="#image0_873_8619"
          transform="scale(0.00353357 0.00176678)"
        />
      </pattern>
      <image
        id="image0_873_8619"
        width="283"
        height="566"
        xlink:href="data:image/png;base64,iVBORw0KGgoAAAA"
      />
    </defs>
  </svg>
</div>
<div id="midline"></div>

<script>
  let svg = document.querySelector(".svg-path");
  let mPath = document.getElementById("Path_440");
  let strokePath = document.getElementById("theFill");
  let pathIcon = document.getElementById("pathIcon");
  let midline = document.getElementById("midline");

  // Recalculate offset path based on svg
  function calculateOffsetPath() {
    pathIcon.style.offsetPath = `path('${mPath.getAttribute("d")}')`;
  }
  calculateOffsetPath();

  // Calculate path length lookup
  let precision = 1000;
  let matrix = svg.getScreenCTM();

  function getLengthLookup(path, precision = 100) {
    let pathLength = path.getTotalLength();
    let lengthLookup = {
      yArr: [],
      lengthArr: [],
      pathLength: pathLength,
    };
    let step = Math.floor(pathLength / precision);

    for (let l = 0; l < pathLength; l += step) {
      let pt = SVGToScreen(matrix, path.getPointAtLength(l));
      let y = pt.y;
      lengthLookup.yArr.push(y);
      lengthLookup.lengthArr.push(l);
    }
    return lengthLookup;
  }

  let lengthLookup = getLengthLookup(mPath, precision);
  let { lengthArr, yArr, pathLength } = lengthLookup;

  function updateLengthLookup() {
    lengthLookup = getLengthLookup(mPath, precision);
    lengthArr = lengthLookup.lengthArr;
    yArr = lengthLookup.yArr;
    pathLength = lengthLookup.pathLength;
  }

  // Recalculate length lookup on window resize
  window.addEventListener("resize", () => {
    matrix = svg.getScreenCTM();
    updateLengthLookup();
    calculateOffsetPath();
  });

  const maxHeight =
    document.documentElement.scrollHeight - window.innerHeight;

  window.addEventListener("scroll", () => {
    let scrollPosMid = getViewportMiddleY();
    midline.style.top = scrollPosMid + "px";
    let found = false;

    for (let i = 0; i < yArr.length && !found; i++) {
      let y = yArr[i];

      if (y >= scrollPosMid) {
        let length = lengthArr[i];
        let yPrev = yArr[i - 1] ? yArr[i - 1] : yArr[i];
        let lengthPrev = lengthArr[i - 1] ? lengthArr[i - 1] : length;
        let ratioL = (1 / lengthArr[i]) * lengthPrev;
        let ratioY = (1 / y) * scrollPosMid;
        let ratio = Math.max(ratioL, ratioY);
        let dashLength = lengthArr[i] * ratio;
        let offsetDist = (100 / pathLength) * dashLength;
        pathIcon.style.offsetDistance = offsetDist + "%";
        strokePath.setAttribute(
          "stroke-dasharray",
          `${dashLength} ${pathLength}`
        );
        found = true;
      }
    }
  });

  function getViewportMiddleY() {
    const viewportHeight = window.innerHeight;
    const scrollY = window.scrollY || window.pageYOffset;
    const element = document.documentElement;
    const elementOffsetTop = element.offsetTop;
    const middleY = scrollY + viewportHeight / 2 + elementOffsetTop;
    return middleY;
  }

  function SVGToScreen(matrix, pt) {
    let p = new DOMPoint(pt.x, pt.y);
    p = p.matrixTransform(matrix);
    return p;
  }
</script>
</body>
</html>
© www.soinside.com 2019 - 2024. All rights reserved.