簡體   English   中英

使用JavaScript創建SVG路徑(形狀變形)

[英]Creating svg paths with javascript(shape morphing)

所以我有一個用於形狀變形的類:

class ShapeOverlays {
  constructor(elm) {
    this.elm = elm;
    this.path = elm.querySelectorAll('path');
    this.numPoints = 18;
    this.duration = 600;
    this.delayPointsArray = [];
    this.delayPointsMax = 300;
    this.delayPerPath = 100;
    this.timeStart = Date.now();
    this.isOpened = false;
    this.isAnimating = false;
  }
    toggle() {
    this.isAnimating = true;
    const range = 4 * Math.random() + 6;
    for (var i = 0; i < this.numPoints; i++) {
      const radian = i / (this.numPoints - 1) * Math.PI;
      this.delayPointsArray[i] = (Math.sin(-radian) + Math.sin(-radian * range) + 2) / 4 * this.delayPointsMax;
    }
    if (this.isOpened === false) {
      this.open();
    } else {
      this.close();
    }
  }
  open() {
    this.isOpened = true;
    this.elm.classList.add('is-opened');
    this.timeStart = Date.now();
    this.renderLoop();
  }
  close() {
    this.isOpened = false;
    this.elm.classList.remove('is-opened');
    this.timeStart = Date.now();
    this.renderLoop();
  }
  updatePath(time) {
    const points = [];
    for (var i = 0; i < this.numPoints + 1; i++) {
      points[i] = ease.cubicInOut(Math.min(Math.max(time - this.delayPointsArray[i], 0) / this.duration, 1)) * 100
    }

    let str = '';
    str += (this.isOpened) ? `M 0 0 V ${points[0]} ` : `M 0 ${points[0]} `;
    for (var i = 0; i < this.numPoints - 1; i++) {
      const p = (i + 1) / (this.numPoints - 1) * 100;
      const cp = p - (1 / (this.numPoints - 1) * 100) / 2;
      str += `C ${cp} ${points[i]} ${cp} ${points[i + 1]} ${p} ${points[i + 1]} `;
    }
    str += (this.isOpened) ? `V 0 H 0` : `V 100 H 0`;
    return str;
  }
  render() {
    if (this.isOpened) {
      for (var i = 0; i < this.path.length; i++) {
        this.path[i].setAttribute('d', this.updatePath(Date.now() - (this.timeStart + this.delayPerPath * i)));
      }
    } else {
      for (var i = 0; i < this.path.length; i++) {
        this.path[i].setAttribute('d', this.updatePath(Date.now() - (this.timeStart + this.delayPerPath * (this.path.length - i - 1))));
      }
    }
  }
  renderLoop() {
    this.render();
    if (Date.now() - this.timeStart < this.duration + this.delayPerPath * (this.path.length - 1) + this.delayPointsMax) {
      requestAnimationFrame(() => {
        this.renderLoop();
      });
    }
    else {
      this.isAnimating = false;
    }
  }
}

(function() {
  const elmHamburger = document.querySelector('.hamburger');
  const gNavItems = document.querySelectorAll('.global-menu__item');
  const elmOverlay = document.querySelector('.shape-overlays');
  const overlay = new ShapeOverlays(elmOverlay);

  elmHamburger.addEventListener('click', () => {
    if (overlay.isAnimating) {
      return false;
    }
    overlay.toggle();
    if (overlay.isOpened === true) {
      elmHamburger.classList.add('is-opened-navi');
      for (var i = 0; i < gNavItems.length; i++) {
        gNavItems[i].classList.add('is-opened');
      }
    } else {
      elmHamburger.classList.remove('is-opened-navi');
      for (var i = 0; i < gNavItems.length; i++) {
        gNavItems[i].classList.remove('is-opened');
      }
    }
  });
}());

有人可以解釋一下此代碼嗎? 我真的不知道如何使用時間創建路徑,如何放置點以及如何修改它。范圍是做什么用的? 為什么三角函數用於delayPointsArray?

基本上這是我不了解的部分:

updatePath(time) {
        const points = [];
        for (var i = 0; i < this.numPoints + 1; i++) {
          points[i] = ease.cubicInOut(Math.min(Math.max(time - this.delayPointsArray[i], 0) / this.duration, 1)) * 100
        }

        let str = '';
        str += (this.isOpened) ? `M 0 0 V ${points[0]} ` : `M 0 ${points[0]} `;
        for (var i = 0; i < this.numPoints - 1; i++) {
          const p = (i + 1) / (this.numPoints - 1) * 100;
          const cp = p - (1 / (this.numPoints - 1) * 100) / 2;
          str += `C ${cp} ${points[i]} ${cp} ${points[i + 1]} ${p} ${points[i + 1]} `;
        }
        str += (this.isOpened) ? `V 0 H 0` : `V 100 H 0`;
        return str;
      }
      render() {
        if (this.isOpened) {
          for (var i = 0; i < this.path.length; i++) {
            this.path[i].setAttribute('d', this.updatePath(Date.now() - (this.timeStart + this.delayPerPath * i)));
          }
        } else {
          for (var i = 0; i < this.path.length; i++) {
            this.path[i].setAttribute('d', this.updatePath(Date.now() - (this.timeStart + this.delayPerPath * (this.path.length - i - 1))));
          }
        }
      }

為什么要用時間? 這樣做的目的是什么:

points[i] = ease.cubicInOut(Math.min(Math.max(time - this.delayPointsArray[i], 0) / this.duration, 1)) * 100

如果查看如何調用updatePath() ,則如下所示:

this.updatePath(Date.now() - (this.timeStart + this.delayPerPath * i))

因此,傳入的time值是當前時間與我們正在使用的路徑的開始時間之間的差。

那么您感興趣的代碼行是做什么的呢?

points[i] = ease.cubicInOut(Math.min(Math.max(time - this.delayPointsArray[i], 0) / this.duration, 1)) * 100

我將忽略delayPointsArray 它會根據角度稍微修改開始時間。 沒有看到完整的演示,我不確定原因。

這行代碼的目的是計算當前路徑動畫的距離。 結果的形式為從0到100的坐標值。

只需一行代碼即可完成很多工作。 因此,讓我們分解各個步驟。

  1. 首先,我們將經過time為最小0。

     Math.max(time, 0) 

    換句話說,動畫開始時間之前的所有內容都為零。

  2. 然后我們除以動畫的持續時間。

     Math.max(time, 0) / duration 

    這將導致從0(代表動畫的開始)到1(代表動畫的結束)的值。 但是,如果經過的時間是在動畫結束之后,則該值也可能大於1。 因此,下一步。

  3. 現在將此值限制為最大值1。

     Math.min( Math.max(time, 0) / duration, 1) 

    現在,我們有一個值> = 0和<= 1,該值描述了在動畫過程中路徑應位於的位置。 如果我們應該在動畫開始位置,則為0。 1,如果我們應該處於動畫結束位置。 如果動畫正在進行,則介於兩者之間。

  4. 但是,該值嚴格線性,與時間的推移相對應。 通常,直線運動不是您想要的。 這是不自然的。 物體在開始移動時會加速,在停止時會減速。 這將是easeInOut()函數將要執行的操作。 如果您不熟悉緩和曲線,請查看下圖。

    緩入緩出時序曲線

    資料來源: Google:輕松的基礎

    因此,我們傳入一個從0..1(水平軸)開始的線性時間值。 它將返回修改后的值,其中考慮了加速和減速。

  5. 最后一步是乘以100,以轉換為最終坐標值(0..100)。

希望這可以幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM