沿 svg 路径移动元素,同时将其放置在中心视口中

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

我正在尝试创建一个滚动动画,为此我创建了一个 svg 路径并添加了一个当用户滚动时沿着路径移动的元素

有什么方法可以使该元素在滚动时沿路径移动时垂直保持在视口的中心吗?

我尝试了下面的代码,但是当我最初向下滚动时,即使元素沿着路径移动,元素也不会进入视图,在滚动的中途它进入视图,我可以看到元素正在沿着路径移动

const pathLength = Path_440.getTotalLength();

function clamp(min, val, max) {
  return Math.min(Math.max(min, val), max);
}

function updatePath() {
  const docElt = document.documentElement;
  const pathBox = theFill.getBoundingClientRect();
  const scrollProgress = clamp(
    0, -pathBox.y / (pathBox.height - docElt.clientHeight),
    1
  );

  pathIcon.style.offsetDistance = `${scrollProgress * 100}%`;

  // These lines fill in the dashes as you scroll down.
  const drawLength = pathLength * scrollProgress;
  const rest = pathLength - drawLength;
  theFill.style.strokeDasharray = `${drawLength}px ${rest}px`;
}

updatePath();
window.addEventListener("scroll", () => updatePath());
#pathIcon2 {
  position: absolute;
  inset: 0;
  width: 10px;
  height: 10px;
  background-size: 25px;
}

body {
  display: grid;
  align-items: center;
  align-content: center;
  justify-content: center;
  justify-items: center;
}

#pathIcon {
  position: absolute;
  inset: 0;
  width: 10px;
  height: 10px;
  background-size: 25px;
  offset-rotate: 0rad;
}

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

#Path_444 {
  stroke-width: 4;
  stroke: #001d36
}

#theFill {
  stroke-width: 4;
  stroke: #55b0ff
}
<div style="height: 175px"></div>
<div style="position: relative">
  <svg width="543" height="7907" viewBox="0 0 543 7907" fill="none">
        <defs>
          <path
            id="Path_440"
            d="M125.5 1V1C125.5 79.173 188.872 142.545 267.045 142.545H393.878C419.836 142.545 445.339 149.367 467.83 162.327V162.327C513.721 188.769 542 237.703 542 290.666V313.599L539.827 333.733C534.083 386.937 499.558 432.677 449.965 452.783L424.708 463.022C419.644 465.075 415.15 468.319 411.607 472.48V472.48C406.374 478.625 403.5 486.433 403.5 494.505V504.159V504.159C403.5 517.05 393.05 527.5 380.159 527.5H11.5C5.70101 527.5 1 532.201 1 538V538V943.5V943.5C1 949.299 5.701 954 11.5 954H389.5V954C397.232 954 403.5 960.268 403.5 968V1352.34V2115.19C403.5 2149.43 375.742 2177.19 341.5 2177.19V2177.19C307.258 2177.19 279.5 2204.95 279.5 2239.19V2420C279.5 2427.18 273.68 2433 266.5 2433V2433H17C10.3726 2433 5 2438.37 5 2445V2445V2845V2845C5 2853.84 12.1634 2861 21 2861H266.5V2861C273.68 2861 279.5 2866.82 279.5 2874V4003.55C279.5 4057.53 235.736 4101.3 181.75 4101.3V4101.3C127.764 4101.3 84 4145.06 84 4199.05V4332.5V4332.5C84 4335.81 86.6863 4338.5 90 4338.5H182.5V4338.5C185.814 4338.5 188.5 4341.19 188.5 4344.5V4762C188.5 4764.76 186.261 4767 183.5 4767V4767H91C87.134 4767 84 4770.13 84 4774V4774V5105.66C84 5183.53 147.128 5246.66 225 5246.66H261.159C319.061 5246.66 366 5293.6 366 5351.5V5351.5V5456V5456C366 5463.46 359.956 5469.5 352.5 5469.5H112.5C103.94 5469.5 97 5476.44 97 5485V5485V5861.5V5861.5C97 5871.44 105.059 5879.5 115 5879.5H348V5879.5C357.941 5879.5 366 5887.56 366 5897.5V6447C366 6456.94 357.941 6465 348 6465V6465H111C104.096 6465 98.5 6470.6 98.5 6477.5V6477.5V6858.5V6858.5C98.5 6867.61 105.887 6875 115 6875H348V6875C357.941 6875 366 6883.06 366 6893V7574.93C366 7627.95 323.019 7670.93 270 7670.93V7670.93C216.981 7670.93 174 7713.91 174 7766.93V7906.5"
/></path>
        </defs>
        <use href="#Path_440" stroke-width="10" stroke-dasharray="20 10"></use>
        <use
          id="theFill"
          href="#Path_440"
          style="stroke-dasharray: 1991.82px, 9259.88px"
          stroke-width="10"
          stroke="#4cacff"
        ></use>
      </svg>
  <div id="pathIcon" stroke="#4cacff" style="
          offset-path: path(
            'M 125.5 1 V 1 C 125.5 79.173 188.872 142.545 267.045 142.545 H 393.878 C 419.836 142.545 445.339 149.367 467.83 162.327 V 162.327 C 513.721 188.769 542 237.703 542 290.666 V 313.599 L 539.827 333.733 C 534.083 386.937 499.558 432.677 449.965 452.783 L 424.708 463.022 C 419.644 465.075 415.15 468.319 411.607 472.48 V 472.48 C 406.374 478.625 403.5 486.433 403.5 494.505 V 504.159 V 504.159 C 403.5 517.05 393.05 527.5 380.159 527.5 H 11.5 C 5.70101 527.5 1 532.201 1 538 V 538 V 943.5 V 943.5 C 1 949.299 5.701 954 11.5 954 H 389.5 V 954 C 397.232 954 403.5 960.268 403.5 968 V 1352.34 V 2115.19 C 403.5 2149.43 375.742 2177.19 341.5 2177.19 V 2177.19 C 307.258 2177.19 279.5 2204.95 279.5 2239.19 V 2420 C 279.5 2427.18 273.68 2433 266.5 2433 V 2433 H 17 C 10.3726 2433 5 2438.37 5 2445 V 2445 V 2845 V 2845 C 5 2853.84 12.1634 2861 21 2861 H 266.5 V 2861 C 273.68 2861 279.5 2866.82 279.5 2874 V 4003.55 C 279.5 4057.53 235.736 4101.3 181.75 4101.3 V 4101.3 C 127.764 4101.3 84 4145.06 84 4199.05 V 4332.5 V 4332.5 C 84 4335.81 86.6863 4338.5 90 4338.5 H 182.5 V 4338.5 C 185.814 4338.5 188.5 4341.19 188.5 4344.5 V 4762 C 188.5 4764.76 186.261 4767 183.5 4767 V 4767 H 91 C 87.134 4767 84 4770.13 84 4774 V 4774 V 5105.66 C 84 5183.53 147.128 5246.66 225 5246.66 H 261.159 C 319.061 5246.66 366 5293.6 366 5351.5 V 5351.5 V 5456 V 5456 C 366 5463.46 359.956 5469.5 352.5 5469.5 H 112.5 C 103.94 5469.5 97 5476.44 97 5485 V 5485 V 5861.5 V 5861.5 C 97 5871.44 105.059 5879.5 115 5879.5 H 348 V 5879.5 C 357.941 5879.5 366 5887.56 366 5897.5 V 6447 C 366 6456.94 357.941 6465 348 6465 V 6465 H 111 C 104.096 6465 98.5 6470.6 98.5 6477.5 V 6477.5 V 6858.5 V 6858.5 C 98.5 6867.61 105.887 6875 115 6875 H 348 V 6875 C 357.941 6875 366 6883.06 366 6893 V 7574.93 C 366 7627.95 323.019 7670.93 270 7670.93 V 7670.93 C 216.981 7670.93 174 7713.91 174 7766.93 V 7906.5'
          );
          offset-distance: 17.7024%;
          background-image: url('https://via.placeholder.com/25x25/FF0000?text=red');
        "></div>
</div>

javascript html css animation scroll
1个回答
0
投票

该点的长度?

在您的情况下,您需要找到一个路径长度值来根据当前滚动 y 位置计算

offset-distance
值。
不幸的是,我们无法根据 x/y 值准确计算路径长度值,因为可能有多个结果,例如如果您的路径是自相交的或具有平坦的段(在某些段上水平或垂直运行)。

但是,我们可以通过

getPointAtLength()
计算一定长度的点坐标。

另一个问题...

getPointAtLength()
在调用数百次、数千次或更多时非常昂贵 - 因此我们不应该在滚动事件处理程序中调用它(这也因严重降低性能而臭名昭著)。

let svg = document.querySelector("svg");
let mPath = document.getElementById('Path_440')
let strokePath = document.getElementById('theFill')

// 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;
}

/** Based on @Paul LeBeau's answer
 * https://stackoverflow.com/questions/48343436/how-to-convert-svg-element-coordinates-to-screen-coordinates#48354404
 */
function SVGToScreen(matrix, pt) {
  let p = new DOMPoint(pt.x, pt.y);
  p = p.matrixTransform(matrix);
  return p
}
svg {
  overflow: visible;
}

body {
  display: grid;
  align-items: center;
  align-content: center;
  justify-content: center;
  justify-items: center;
}

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

#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 width="543" height="7907" viewBox="0 0 543 7907" fill="none">
    <defs>
      <path id="Path_440" d="M125.5 1v0c0 78.2 63.4 141.5 141.5 141.5h126.9c25.9 0 51.4 6.9 73.9 19.8v0c45.9 26.5 74.2 75.4 74.2 128.4v22.9l-2.2 20.1c-5.7 53.2-40.2 99-89.8 119.1l-25.3 10.2c-5.1 2.1-9.6 5.3-13.1 9.5v0c-5.2 6.1-8.1 13.9-8.1 22v9.7v0c0 12.8-10.4 23.3-23.3 23.3h-368.7c-5.8 0-10.5 4.7-10.5 10.5v0v405.5v0c0 5.8 4.7 10.5 10.5 10.5h378v0c7.7 0 14 6.3 14 14v384.3v762.9c0 34.2-27.8 62-62 62v0c-34.2 0-62 27.7-62 62v180.8c0 7.2-5.8 13-13 13v0h-249.5c-6.6 0-12 5.4-12 12v0v400v0c0 8.8 7.2 16 16 16h245.5v0c7.2 0 13 5.8 13 13v1129.6c0 53.9-43.8 97.7-97.7 97.7v0c-54 0-97.8 43.8-97.8 97.8v133.4v0c0 3.3 2.7 6 6 6h92.5v0c3.3 0 6 2.7 6 6v417.5c0 2.8-2.2 5-5 5v0h-92.5c-3.9 0-7 3.1-7 7v0v331.7c0 77.8 63.1 141 141 141h36.2c57.9 0 104.8 46.9 104.8 104.8v0v104.5v0c0 7.5-6 13.5-13.5 13.5h-240c-8.6 0-15.5 6.9-15.5 15.5v0v376.5v0c0 9.9 8.1 18 18 18h233v0c9.9 0 18 8.1 18 18v549.5c0 9.9-8.1 18-18 18v0h-237c-6.9 0-12.5 5.6-12.5 12.5v0v381v0c0 9.1 7.4 16.5 16.5 16.5h233v0c9.9 0 18 8.1 18 18v681.9c0 53-43 96-96 96v0c-53 0-96 43-96 96v139.6" />
      </path>
    </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>
  <div id="pathIcon" stroke="#4cacff" style="
          offset-distance: 0%;
          background-image: url('https://via.placeholder.com/25x25/FF0000?text=red');
        "></div>
</div>

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

将 svg 转换为屏幕坐标

如果您的 svg 尺寸等于它在 HTML 中放置的大小(例如 viewBox 值等于元素 css 宽度和高度并且没有边距),您可以跳过此步骤。
否则我们需要转换值:另请参阅 Paul LeBeaus 的回答:“如何将 svg 元素坐标转换为屏幕坐标?”

创建 Y 长度查找

在运行任何滚动事件相关脚本之前 我们收集并保存查找对象中 y 值的多个长度。
我们正在保存转换为屏幕/HTMLDom 值的 Y 值。这样我们就可以根据滚动事件更新调用中的查找值直接检查当前的中视口坐标 Y 值。

© www.soinside.com 2019 - 2024. All rights reserved.