🐉 ~ 

汎用JavaScriptのまとめ

2023.04.17
RELATED CATEGORY
TABLE OF CONTENTS

筆者自身が汎用的に使用する以下のJavaScript(VanillaJs)をまとめました。
今後も日々アップデートしていきます。

スムーススクロール(アンカーリンク)

CodePen

See the Pen Smooth scroll to Anker link by ShoyaKajita (@ShoyaKajita) on CodePen.

Code

export class AnkerLink {
  constructor() {
    this.idName = {
      header: "header",
      scrollTop: "scrollTop",
    };

    this.distanceFromTop = 0;
  }

  /**
   * スムーススクロール
   * @param {number} y
   * @param {number} x
   */
  scrollTo(y = 0, x = 0) {
    window.scrollTo({
      top: y,
      left: x,
      behavior: "smooth", // ページ内ではスムーススクロールを設定
    });
  }

  /**
   * 移動先の座標を取得しスムーススクロールをする
   * @param {event} e // クリックイベント
   */
  setScrollPosition(e) {
    e.preventDefault();
    const href = e.target.getAttribute("href"),
      string = href.slice(0, 1),
      header = document.getElementById(this.idName.header),
      offset = header
        ? header.getBoundingClientRect().height + this.distanceFromTop
        : this.distanceFromTop;

    if (string === "#" && (href != "" || href != "#")) {
      const target = document.querySelector(href);
      if (target) {
        const y =
          target.getBoundingClientRect().top - offset + window.pageYOffset;
        this.scrollTo(y, 0);
      }
    }
  }

  /**
   * 初期位置の座標を取得し移動する
   */
  setInitPosition() {
    let id = null,
      y = 0;
    const delay = 200,
      hash = window.location.hash,
      query = this.getParameter("id"),
      header = document.getElementById(this.idName.header),
      offset = header
        ? header.getBoundingClientRect().height + this.distanceFromTop
        : this.distanceFromTop;

    if (hash != "") {
      id = document.querySelector(hash);
      if (id != null) {
        y = id.getBoundingClientRect().top - offset + window.pageYOffset;
        setTimeout(() => {
          window.scrollTo(0, y);
        }, delay);
      }
    } else if (query != "" && query != null) {
      id = document.getElementById(`${query}`);
      if (id != null) {
        y = id.getBoundingClientRect().top - offset + window.pageYOffset;
        setTimeout(() => {
          window.scrollTo(0, y);
        }, delay);
      }
    }
  }

  /**
   * GETパラメータのキーから値を取得し返す
   * @param {string} name // 取得したいGETパラメータのキー
   * @returns GETパラメータ値
   */
  getParameter(name) {
    // https://www-creators.com/archives/4463
    name = name.replace(/[\[\]]/g, "\\$&");
    let regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
      results = regex.exec(window.location.href);
    if (!results) return null;
    if (!results[2]) return "";
    return decodeURIComponent(results[2].replace(/\+/g, " "));
  }

  init() {
    this.setInitPosition();

    const linkList = [...document.querySelectorAll("a[href^='#']")];
    if (linkList) {
      linkList.forEach((ele) => {
        ele.addEventListener("click", this.setScrollPosition.bind(this));
      });
    }

    const scrollTop = document.getElementById(this.idName.scrollTop);
    if (scrollTop) {
      scrollTop.addEventListener("click", (e) => {
        this.scrollTo(0, 0);
      });
    }
  }
}

IntersectionObserver

/*
// --------------------------
// html
// --------------------------
// 監視要素 | 「data-delay」が最優先される
<div class="js-observer" data-delay="0" data-delay-pc="0" data-delay-sp="0"></div>

// --------------------------
// css
// --------------------------
.js-observer.is-cue {
  // 発火したスタイルを記述する
}
*/
export class Observer {
  constructor() {
    this.isMatchMedia = window.matchMedia("(max-width: 768px)").matches;
    this.config = {
      root: null,
      rootMargin: "0% 0%",
      threshold: 0,
    };
  }

  reset() {
    this.targetList = [];
    this.destroy();
    this.observer = null;
  }

  /**
   * @param {object} 複数の監視要素の情報が格納されている
   */
  entry(entries) {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        const ele = entry.target;
        const dataDelay = ele.getAttribute("data-delay");
        let delay = dataDelay ? dataDelay : 0;

        if (this.isMatchMedia) {
          const dataDelaySp = ele.getAttribute("data-delay-sp");
          if (dataDelaySp) delay = dataDelaySp;
        } else {
          const dataDelayPc = ele.getAttribute("data-delay-pc");
          if (dataDelayPc) delay = dataDelayPc;
        }

        setTimeout(() => {
          if (ele) ele.classList.add("is-cue");
        }, delay);
      }
    });
  }

  destroy() {
    this.observer.disconnect(); // 監視を終了する
  }

  init() {
    this.targetList = [];
    this.observer = null;

    this.observer = new IntersectionObserver(
      this.entry.bind(this),
      this.config
    );
    this.targetList = [...document.querySelectorAll(".js-observer")];
    this.targetList.forEach((ele) => {
      this.observer.observe(ele);
    });
  }
}

セッションストレージ

export class Session {
  clear() {
    sessionStorage.clear();
  }

  /**
   * @param {string} key // 削除するキー名
   */
  remove(key) {
    sessionStorage.removeItem(key);
    console.log("session remove", key);
  }

  /**
   * @param {string} key //取得したいキー名
   * @returns {string} // 値
   */
  get(key) {
    console.log("session get", key, sessionStorage.getItem(key));
    return sessionStorage.getItem(key);
  }

  /**
   * @param {string} key // セットしたいキー名
   * @param {string} value // セットしたい値
   */
  set(key, value) {
    sessionStorage.setItem(key, value);
    console.log("session set", key, value, sessionStorage.getItem(key));
  }
}

ユーザーエージェント

現在(2023.01.01)「M1、M2」と「Macbook Pro Air」とのユーザーエージェントの違いを確認できていません...

export class Ua {
  constructor(body) {
    this.body = body;
    this.bodyClassList = this.body.classList;
    this.ua = window.navigator.userAgent.toLowerCase();
    this.data = {
      browser: "",
      os: "",
      device: "",
      iphone: "",
    };

    this.checkBrowser();
    this.checkOs();
    this.checkDevice();
    this.checkiPhone();
  }

  checkBrowser() {
    if (
      this.ua.indexOf("edge") !== -1 ||
      this.ua.indexOf("edga") !== -1 ||
      this.ua.indexOf("edgios") !== -1
    ) {
      console.log("🚀 ~ This is Edge");
      this.data.browser = "edge";
    } else if (
      this.ua.indexOf("opera") !== -1 ||
      this.ua.indexOf("opr") !== -1
    ) {
      console.log("🚀 ~ This is Opera");
      this.data.browser = "opera";
    } else if (this.ua.indexOf("samsungbrowser") !== -1) {
      console.log("🚀 ~ This is SamsungInternetBrowser");
      this.data.browser = "samsung";
    } else if (this.ua.indexOf("ucbrowser") !== -1) {
      console.log("🚀 ~ This is UcBrowser");
      this.data.browser = "uc";
    } else if (
      this.ua.indexOf("chrome") !== -1 ||
      this.ua.indexOf("crios") !== -1
    ) {
      console.log("🚀 ~ This is Chrome");
      this.data.browser = "chrome";
    } else if (
      this.ua.indexOf("firefox") !== -1 ||
      this.ua.indexOf("fxios") !== -1
    ) {
      console.log("🚀 ~ This is Firefox");
      this.data.browser = "firefox";
    } else if (this.ua.indexOf("safari") !== -1) {
      console.log("🚀 ~ This is Safari");
      this.data.browser = "safari";
    } else if (
      this.ua.indexOf("msie") !== -1 ||
      this.ua.indexOf("trident") !== -1
    ) {
      console.log("🚀 ~ This is IE");
      this.data.browser = "ie";
      alert("このブラウザは現在サポートされておりません。");
    } else {
      console.log("🚀 ~ This is An unknown browser");
      this.data.browser = "";
    }

    if (this.data.browser != "") this.bodyClassList.add(this.data.browser);
  }

  checkOs() {
    if (this.ua.indexOf("windows nt") !== -1) {
      console.log("🚀 ~ This is Windows");
      this.data.os = "windows";
    } else if (this.ua.indexOf("android") !== -1) {
      console.log("🚀 ~ This is Android");
      this.data.os = "android";
    } else if (
      this.ua.indexOf("iphone") !== -1 ||
      this.ua.indexOf("ipad") !== -1
    ) {
      console.log("🚀 ~ This is iOS");
      this.data.os = "ios";
    } else if (this.ua.indexOf("mac os x") !== -1) {
      console.log("🚀 ~ This is macOS");
      this.data.os = "macos";
    } else {
      console.log("🚀 ~ This is An unknown OS");
      this.data.os = "";
    }

    if (this.data.os != "") this.bodyClassList.add(this.data.os);
  }

  checkDevice() {
    if (
      this.ua.indexOf("iphone") !== -1 ||
      (this.ua.indexOf("android") !== -1 && this.ua.indexOf("Mobile") > 0)
    ) {
      console.log("🚀 ~ This is Mobile");
      this.data.device = "mobile";
    } else if (
      this.ua.indexOf("ipad") !== -1 ||
      this.ua.indexOf("android") !== -1
    ) {
      console.log("🚀 ~ This is Tablet");
      this.data.device = "tablet";
    } else if (
      this.ua.indexOf("ipad") > -1 ||
      (this.ua.indexOf("macintosh") > -1 && "ontouchend" in document)
    ) {
      console.log("🚀 ~ This is iPad");
      this.data.device = "tablet";
    } else {
      console.log("🚀 ~ This is PC");
      this.data.device = "pc";
    }

    if (this.data.device != "") this.bodyClassList.add(this.data.device);
  }

  checkiPhone() {
    if (this.ua.indexOf("iphone") !== -1) {
      console.log("🚀 ~ This is iPhone");
      this.data.iphone = "iphone";
    } else {
      this.data.iphone = "";
    }

    if (this.data.iphone != "") this.bodyClassList.add(this.data.iphone);
  }
}

1文字分割

/*
// --------------------------
// html
// --------------------------
// before
<div class="js-splitText">これは文字列です。</div>

// after
<div class="js-splitText">
  <span class="t" style="display: inline-block;">こ</span><span class="t" style="display: inline-block;">れ</span><span class="t" style="display: inline-block;">は</span><span class="t" style="display: inline-block;">文</span><span class="t" style="display: inline-block;">字</span><span class="t" style="display: inline-block;">列</span><span class="t" style="display: inline-block;">で</span><span class="t" style="display: inline-block;">す</span><span class="t" style="display: inline-block;">。</span>
</div>

// --------------------------
// scss
// --------------------------
// 遅延設定(1文字アニメーション用)
.t{
  @for $i from 1 through 100 {
    &:nth-of-type(#{$i}) {
      transition-delay: (0.05s * $i) - 0.05s;
    }
  }
}
*/
export class SplitText {
  /**
   * 分割した1文字を要素にする
   * @param {string} text 1文字
   * @returns {element} 要素 | <span class="t" style="display: inline-block;">1文字</span>
   */
  createEle(text) {
    const ele = document.createElement("span");
    ele.className = "t";
    ele.setAttribute("style", "display: inline-block;");
    ele.innerHTML = text != " " ? text : "  ;"; // ⚠️ 目視するために半角スペースを入れている
    return ele;
  }

  /**
   * 文字列を要素に分割し追加する
   * @param {element} obj.wrap 文字列の格納先
   * @param {string} obj.text 分割する文字列
   */
  createSplit(obj) {
    const wrap = obj.wrap,
      text = obj.text;

    const array = text.split("");
    const fragment = document.createDocumentFragment();

    for (let i = 0; i < array.length; i++) {
      const ele = this.createEle(array[i]);
      fragment.appendChild(ele);
    }

    wrap.appendChild(fragment);
  }

  init() {
    this.eleList = [...document.querySelectorAll(".js-splitText")];
    this.eleList.forEach((ele) => {
      const obj = {
        wrap: ele,
        text: ele.innerText,
      };
      ele.innerText = "";
      if (obj.text != "") this.createSplit(obj);
    });
  }
}

メニュー

/* 
// --------------------------
// html
// --------------------------
// メニューボタン
<div id="menuBtn">メニュー</div>

// 閉じるボタン
<div class="js-menuClose">閉じる</div>

// --------------------------
// css
// --------------------------
body[data-menu="open"] {
  overflow: hidden;
}
*/
export class Menu {
  constructor(body = document.body) {
    this.body = body;
    this.body.setAttribute("data-menu", "close");

    this.isOpenMenu = false;
    this.isAbleToClick = true;
    this.interval = 600;

    this.menuBtn = document.getElementById("menuBtn");
    this.menuBtn.addEventListener("click", (e) => {
      if (this.isAbleToClick) {
        this.isAbleToClick = false;
        this.isOpenMenu ? this.toCloseMenu() : this.toOpenMenu();
        setTimeout(() => {
          this.isAbleToClick = true;
        }, this.interval);
      }
    });
  }

  toOpenMenu() {
    this.isOpenMenu = true;
    this.body.setAttribute("data-menu", "open");
  }

  toCloseMenu() {
    this.isOpenMenu = false;
    this.body.setAttribute("data-menu", "close");
  }

  init() {
    this.isAbleToClick = true;
    this.isOpenMenu = false;
    this.body.setAttribute("data-menu", "close");
  }
}

アコーディオン|detailsを使用したもの

CodePen

Code

/*
// HTML 
<details class="c-details">
  <summary class="c-details__summary">
    <h2>Summary</h2>
  </summary>
  <div class="c-details__content">
    <div class="c-details__inner">
      コンテンツ // ここで`padding`で脂肪をつける
    </div>
  </div>
</details>

// css
summary {
  display: block; // list-item; → 三角形のアイコンを表示する
  cursor: pointer;
}
//Safariの三角形アイコンを非表示にする
summary::-webkit-details-marker {
  display: none;
}
*/
export class Accordion {
  constructor() {
    this.isMatchMedia = window.matchMedia("(max-width: 768px)").matches;
    this.config = {
      duration: 300,
      easing: "ease",
    };
  }

  reset() {}

  resizeAfter() {
    this.detailsList.forEach((ele) => {
      const summary = ele.querySelector("summary"),
        content = summary.nextElementSibling;
      content.style.height = "auto";
    });
  }

  /**
   * @param {element} obj.details // コンテナ
   * @param {element} obj.summary // 開閉ボタン
   * @param {element} obj.content // 開閉要素
   */
  open(obj) {
    const details = obj.details,
      summary = obj.summary,
      content = obj.content;

    const openAnimeKeyframes = (content) => [
      {
        height: 0,
        opacity: 0,
      },
      {
        height: content.offsetHeight + "px",
        opacity: 1,
      },
    ];

    details.setAttribute("open", "true");
    details.setAttribute("data-status", "open");

    const openingAnime = content.animate(
      openAnimeKeyframes(content),
      this.config
    );
    openingAnime.onfinish = () => {
      // 終了時の処理
    };
  }

  close(obj) {
    const details = obj.details,
      summary = obj.summary,
      content = obj.content;

    const closeAnimeKeyframes = (content) => [
      {
        height: content.offsetHeight + "px",
        opacity: 1,
      },
      {
        height: 0,
        opacity: 0,
      },
    ];

    const closingAnime = content.animate(
      closeAnimeKeyframes(content),
      this.config
    );

    closingAnime.onfinish = () => {
      details.removeAttribute("open");
      details.setAttribute("data-status", "close");
    };
  }

  init() {
    this.detailsList = [];
    this.detailsList = [...document.querySelectorAll("details")];
    this.detailsList.forEach((ele) => {
      const isOpen = ele.getAttribute("data-open");
      if (
        (isOpen === "pc" && !this.isMatchMedia) ||
        (isOpen === "sp" && this.isMatchMedia)
      ) {
        ele.setAttribute("open", "true");
        ele.setAttribute("data-status", "open");
      } else {
        ele.removeAttribute("open");
        ele.setAttribute("data-status", "close");
      }

      const summary = ele.querySelector("summary"),
        content = summary.nextElementSibling;

      content.setAttribute(
        "style",
        `overflow: hidden; transition: ${
          this.config.duration / 1000
        }s ease height;`
      );

      const obj = {
        details: ele,
        summary: summary,
        content: content,
      };

      summary.addEventListener("click", (e) => {
        e.preventDefault();
        if (
          ele.getAttribute("data-status") === "open" ||
          ele.getAttribute("data-status") === "close"
        ) {
          ele.setAttribute("data-status", "run");
          ele.open ? this.close(obj) : this.open(obj);
        }
      });
    });
  }
}

モーダル|ID指定(複数設置)

CodePen

Code

/*
// --------------------------
// html
// --------------------------
// モーダルボタン
<button class="js-modalBtn" data-modal-id="modal1">モーダルボタン</button>

// モーダル要素
<div class="l-modal" id="modal1">
  <div class="js-modalCloseBtn" data-modal-id="modal1">閉じる</div>
  <div class="js-modalPrevBtn" data-modal-id="modal3">前のモーダルへ</div>
  <div class="js-modalNextBtn" data-modal-id="modal2">▶次のモーダルへ</div>
</div>

// --------------------------
// css
// --------------------------
body.is-openModalId{
  overflow: hidden;
}
.l-modal.is-openModalId {
  // モーダルが開く
}
.js-modalCloseBtn,
.js-modalPrevBtn,
.js-modalNextBtn{
  cursor: pointer;
}
*/
export class ModalId {
  constructor(body = document.body) {
    this.body = body;
    this.bodyClassList = body.classList;

    this.timer = {
      modal: null,
    };

    this.className = {
      open: "is-openModalId",
    };

    this.isOpenModal = false;
    this.isAbleToClick = true;
    this.interval = 500;
    this.currentId = "";
  }

  removeClassName() {
    if (this.bodyClassList.contains(this.className.open)) this.bodyClassList.remove(this.className.open);
  }

  addClassName() {
    this.bodyClassList.add(this.className.open);
  }

  reset() {
    this.isOpenModal = false;
    this.isAbleToClick = false;
    clearTimeout(this.timer.modal);
    this.currentId = "";
  }

  toCloseModal(target) {
    console.log("open");
    this.isOpenModal = false;
    this.removeClassName();
    if (target.classList.contains(this.className.open)) target.classList.remove(this.className.open);
  }

  toOpenModal(target) {
    console.log("close");
    this.isOpenModal = true;
    this.addClassName();
    target.classList.add(this.className.open);
  }

  toClick(ele) {
    console.log("isClick");
    if (this.isAbleToClick) {
      this.isAbleToClick = false;

      const targetId = ele.getAttribute("data-modal-id");
      console.log(targetId);
      if (targetId) {
        this.currentId = targetId;
        const target = document.getElementById(targetId);
        target.scrollTo(0, 0);
        console.log(target);
        if (this.isOpenModal) {
          this.currentId = "";
          this.toCloseModal(target);
        } else {
          this.currentId = targetId;
          this.toOpenModal(target);
        }

        clearTimeout(this.timer.modal);
        this.timer.modal = setTimeout(() => {
          this.isAbleToClick = true;
        }, this.interval);
      } else {
        this.isAbleToClick = true;
        this.isOpenModal = false;
      }
    }
  }

  toChange(ele) {
    if (this.isAbleToClick) {
      this.isAbleToClick = false;
      const currentTarget = document.getElementById(this.currentId);
      if (currentTarget) {
        this.currentId = "";
        this.toCloseModal(currentTarget);
      }

      const targetId = ele.getAttribute("data-modal-id");
      if (targetId) {
        this.currentId = targetId;
        const target = document.getElementById(targetId);
        target.scrollTo(0, 0);
        if (this.isOpenModal) {
          this.currentId = "";
          this.toCloseModal(target);
        } else {
          this.currentId = targetId;
          this.toOpenModal(target);
        }

        clearTimeout(this.timer.modal);
        this.timer.modal = setTimeout(() => {
          this.isAbleToClick = true;
        }, this.interval);
      } else {
        this.isAbleToClick = true;
        this.isOpenModal = false;
      }
    }
  }

  init() {
    console.log("🚀 ~ Modal init");
    this.isOpenModal = false;
    this.isAbleToClick = true;

    // 開くボタン
    this.modalBtnList = [...document.querySelectorAll(".js-modalBtn")];
    if (this.modalBtnList) {
      this.modalBtnList.forEach((ele) => {
        ele.addEventListener("click", (e) => {
          this.toClick(ele);
        });
      });
    }

    // 閉じるボタン
    this.closeBtnList = [...document.querySelectorAll(".js-modalCloseBtn")];
    if (this.closeBtnList) {
      this.closeBtnList.forEach((ele) => {
        ele.addEventListener("click", (e) => {
          this.toClick(ele);
        });
      });
    }

    // prevボタン
    this.prevBtnList = [...document.querySelectorAll(".js-modalPrevBtn")];
    if (this.prevBtnList) {
      this.prevBtnList.forEach((ele) => {
        ele.addEventListener("click", (e) => {
          this.toChange(ele);
        });
      });
    }

    // nextボタン
    this.nextBtnList = [...document.querySelectorAll(".js-modalNextBtn")];
    if (this.nextBtnList) {
      this.nextBtnList.forEach((ele) => {
        ele.addEventListener("click", (e) => {
          this.toChange(ele);
        });
      });
    }
  }
}

モーダル|画像

CodePen

Code

/*
// --------------------------
// html
// --------------------------
// ボタン
<div class="js-modalImgBtn">
  <img src="#" alt="" />
</div>

// モーダル
<div class="l-modalImg">
  <div class="js-modalImgCloseBtn"></div>
  <div id="modalImgTarget">
    <!-- ここにimgタグがJSの処理によって入ります。 -->
  </div>
</div>

// --------------------------
// css
// --------------------------
body.is-openModalImg {
  overflow: hidden;
}
.js-modalImgBtn,
.js-modalImgCloseBtn {
  cursor: pointer;
}
*/
export class ModalImg {
  constructor(body = document.body) {
    this.body = body;
    this.bodyClassList = this.body.classList;

    this.className = {
      open: "is-openModalImg",
    };

    this.isPageEnter = true;
    this.isAbleToClick = true;
    this.interval = 200;
  }

  reset() {
    this.isPageEnter = false;
    this.isAbleToClick = false;
  }

  removeClassName() {
    if (this.bodyClassList.contains(this.className.open))
      this.bodyClassList.remove(this.className.open);
  }

  addClassName() {
    this.bodyClassList.add(this.className.open);
  }

  toCloseModal() {
    if (this.isPageEnter) {
      this.isAbleToClick = false;
      this.removeClassName();
      setTimeout(() => {
        if (this.isPageEnter) this.isAbleToClick = true;
      }, this.interval);
    }
  }

  /**
   * @param {event} e // クリックイベント
   */
  toOpenModal(e) {
    if (this.isPageEnter) {
      this.isAbleToClick = false;

      const ele = e.target;
      const src = ele.querySelector("img").getAttribute("src");
      const img = `<img src="${src}" alt="">`;

      this.target.innerHTML = img;

      this.addClassName();
      setTimeout(() => {
        if (this.isPageEnter) this.isAbleToClick = true;
      }, this.interval);
    }
  }

  init() {
    console.log("🚀 ~ Modal Image");

    this.isPageEnter = true;
    this.isAbleToClick = true;

    this.btnList = [...document.querySelectorAll(".js-modalImgBtn")];
    this.closeList = [...document.querySelectorAll(".js-modalImgCloseBtn")];
    this.target = document.getElementById("modalImgTarget");

    // 開くボタン
    this.btnList.forEach((ele) => {
      ele.addEventListener("click", (e) => {
        if (this.isAbleToClick && this.isPageEnter) this.toOpenModal(e);
      });
    });

    // 閉じるボタン
    this.closeList.forEach((ele) => {
      ele.addEventListener("click", (e) => {
        if (this.isAbleToClick && this.isPageEnter) this.toCloseModal();
      });
    });
  }
}

画面サイズをCSS変数として設定

`resize` イベントで使用します。

/**
 * @param {number} w // window.innerWidth
 * @param {number} h // window.innerHeight
 */
export function SetPropertySize(w, h) {
  const vh = h * 0.01,
    longer = w > h ? w : h,
    shorter = w > h ? h : w;

  document.documentElement.style.setProperty("--vh", vh + "px"); // height: calc(var(--vh, 1vh) * 100);
  document.documentElement.style.setProperty("--longer", longer + "px");
  document.documentElement.style.setProperty("--shorter", shorter + "px");
}

Utility(lerp, delay, getParameter)

export class Utility {
  /**
   * 線形補間
   * リファレンス:https://ja.wikipedia.org/wiki/%E7%B7%9A%E5%BD%A2%E8%A3%9C%E9%96%93
   * @param {number} start
   * @param {number} end
   * @param {number} ease
   * @returns {number} startとendを補完した数値
   */
  lerp(start, end, ease) {
    return start * (1 - ease) + end * ease;
  }

  /**
   * 遅延 (await)
   * @param {number} time 遅延時間
   * @returns
   */
  delay(time) {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve();
      }, time);
    });
  }

  /**
   * GETパラメータのキーから値を取得し返す
   * 参考サイト:https://www-creators.com/archives/4463
   * @param {string} name // 取得したいGETパラメータのキー
   * @returns {string} // GETパラメータ値
   */
  getParameter(name) {
    name = name.replace(/[\[\]]/g, "\\$&");
    let regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
      results = regex.exec(window.location.href);
    if (!results) return null;
    if (!results[2]) return "";
    return decodeURIComponent(results[2].replace(/\+/g, " "));
  }
}

PICKUP ARTWORK