Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

liタグを斜めに配置する方法

解決したいこと

現在jsの勉強をしており、いろいろなパーツを作ってjsの処理や応用を学んでいるのですが、
下記のようにliタグを斜めに配置してはじめと最後のliタグを連結し無限に同じリストをスライドできるようにしたいのですが、そんな方法が存在するのかどうか分からず、行えるのであれば行う方法を教えていただきたいです。

・liタグを斜めに配置する方法
・はじめと最後のliタグをくっつけて無限にスクロールできるようにする方法

image.png

最後まで読んでいただきありがとうございます。
ご存じの方がいらっしゃいましたらご教示いただけますと幸いです。
何卒よろしくお願いします。

0

2Answer

以下のようにJSで実装してみました。

  1. HTML上のli要素を、画面いっぱいを埋めるようにJSで複製
  2. さらにスクロール時に画面内で途切れないように複製
  3. 上下にスクロールして指定の要素が画面の端まで到達したときに、真ん中の方にある同じ要素へ飛ぶようにする
infinity_scroll.js

  window.addEventListener("load", () => {
  const list = document.getElementById("js-diagonal_list"); //対象のul
  const originalItems = list.querySelectorAll("li"); //htmlに書いた元のli
  const ORIGINAL_SIZE = originalItems.length; //元のliの要素数
  const SHIFT = 20; //斜め配置の横移動の単位値 任意に設定

  /*
    SHIFTの値と各li要素の高さで斜線の傾きが決まる。
    縦スクロール時、(スクロール量 × 傾き)の分だけ横にも擬似スクロールすることで斜めスクロールに見せる
    
    傾きは、最初のli要素の左上点から最後のli要素の左上点までを結んだ線分を対象として測る
      横幅 = SHIFT × (要素数 - 1)
      高さ = ulの高さ - 最後の要素の高さ
    
    ただし、要素数が1のときは
      横幅 = SHIFT
      高さ = 最初の要素の高さ + 最初の要素のmargin-top
   */

  const SLOPE =
    originalItems.length > 1
      ? (SHIFT * (originalItems.length - 1)) /
        (list.getBoundingClientRect().height - originalItems[originalItems.length - 1].getBoundingClientRect().height)
      : SHIFT / heightWithTopMargin(originalItems[0]);

  setDiagonalList(); //画面ロード時初期化

  window.addEventListener("resize", () => {
    setDiagonalList(); //画面リサイズ時初期化
  });

  /**
   * 画面の縦幅を取得し、対象リスト内のli要素を画面の高さを超えるまでコピーして配置
   * さらに全体を複製することで上下のスクロール領域を確保
   */
  function setDiagonalList() {
    //ul要素を初期化
    list.querySelectorAll("li").forEach((li) => {
      li.remove();
    });
    originalItems.forEach((li) => {
      list.appendChild(li);
    });

    list.style.paddingTop = "0px";

    const ORIGINAL_H = list.getBoundingClientRect().height; //ulの高さ
    const WINDOW_H = window.innerHeight;

    let offsetRight = 0; //右からの横移動値
    originalItems.forEach((li, i) => {
      offsetRight = i * SHIFT; //インデックス × 移動単位分 左にずらす
      li.style.right = offsetRight + "px";
      li.style.transform = "translate(0px,0px)";
    });

    offsetRight += SHIFT; //オリジナルli要素の最下より1単位左からスタート
    let currentListHeight = ORIGINAL_H; //現在のulの高さ
    let i = 0; //ul内でのliのインデックス(コピー含む)

    /** リストの高さが(画面高さ+最初の要素の高さ+2番目の要素のmargin-top)を超えるまでli要素のコピーを1セットずつ追加 */
    while (
      currentListHeight <=
      WINDOW_H +
        heightWithTopMargin(originalItems[0]) +
        parseFloat(window.getComputedStyle(originalItems[1] || originalItems[0]).marginTop)
    ) {
      originalItems.forEach((li, l) => {
        const copy = li.cloneNode(true);

        copy.id = "";

        copy.classList.add("js-item-copy1"); //コピーしたliには目印のclassを付与
        copy.setAttribute("data-copy-of", l); //どのli要素のコピーであるかをdata属性に保持

        copy.style.right = offsetRight + i * SHIFT + "px";
        list.appendChild(copy);

        i += 1;
      });
      //リストの高さを更新
      currentListHeight = list.getBoundingClientRect().height;
    }

    const copiedItems = list.querySelectorAll("li"); //コピーを含む全てのli
    copiedItems.forEach((li, l) => {
      if (l === 1) {
        li.id = "js-scroll-marker--upper"; //オリジナルの上から2番目を上向きスクロール時の判定基準にする
      }
      if (l === ORIGINAL_SIZE + 1) {
        li.id = "js-scroll-target"; //コピー(1周目)の上から2番目を無限スクロールのターゲットにする
      }

      const copy = li.cloneNode(true);
      copy.id = "";
      copy.classList.remove("js-item-copy1");
      copy.classList.add("js-item-copy2");
      if (l === 1) {
        copy.id = "js-scroll-marker--lower"; //コピー(2周目)の上から2番目を下向きスクロール時の判定基準にする
      }

      copy.style.right = parseFloat(list.querySelector("li:last-child").style.right) + SHIFT + "px"; //斜め配置
      list.appendChild(copy);
    });
  }

  window.addEventListener("scroll", scrollDiagonally); //スクロール時の処理
  /**
   * スクロール時に実行する関数
   * 縦スクロール時、(スクロール量 × 傾き)の分だけ全てのli要素を右に移動させることで擬似的に斜めスクロールに見せる
   * 上下の判定基準となるマーカー要素までスクロールしたらターゲット要素に移動
   */
  function scrollDiagonally() {
    list.querySelectorAll("li").forEach((li) => {
      li.style.transform = "translateX(" + window.scrollY * SLOPE + "px)"; //縦にスクロールした分だけli要素を右に移動
    });

    const upperLine = document.getElementById("js-scroll-marker--upper"); //上基準ライン
    const lowerLine = document.getElementById("js-scroll-marker--lower"); //下基準ライン
    const target = document.getElementById("js-scroll-target"); //ターゲット

    /*
      ●下向きスクロール
        下ラインのy座標(.top)が画面上端(y=0)より上になったとき
      ●上向きスクロール
        上ラインのy座標(.top)が画面上端(y=0)より下になったとき

        スクロール位置をターゲットの上端まで移動し、擬似横スクロールの値も変更する。
    */
    if (lowerLine.getBoundingClientRect().top <= 0 || upperLine.getBoundingClientRect().top >= 0) {
      const scrollTo = window.scrollY + target.getBoundingClientRect().top;

      list.querySelectorAll("li").forEach((li) => {
        li.style.transform = "translateX(" + scrollTo * SLOPE + "px)"; //横移動リセット
      });

      window.scrollTo(0, scrollTo); //スクロール移動
    }
  }
});

/**
 * 要素のmargin-topを含む高さを取得
 * @type {number}
 * @param {HTMLElement} elm
 */
const heightWithTopMargin = (elm) => {
  return elm.getBoundingClientRect().height + parseFloat(window.getComputedStyle(elm).marginTop);
};

1Like

こういう感じでしょうか?

main {
  flex: 1 1 auto;
  display: block;
  overflow: auto;
  scroll-timeline-name: --main_view;
}
.content {
  height: 50vh;
}
.wrapped {
  overflow: hidden;
  ul {
    padding: 0;
    margin-left: 10em;
    transform-origin: left top;
    transform: rotate(45deg);
    animation: ul-scroll auto linear forwards;
    animation-timeline:--main_view;
    animation-range: contain 0% contain 100%;
    li {
      transform-origin: left top;
      transform: rotate(-45deg);
      word-break: keep-all;
    }
  }
}
@keyframes ul-scroll {
  from {
    margin-left: 10em;
  }
  to {
    margin-left: 100%;
  }
}
0Like

Comments

  1. @Kobayashi0620

    Questioner

    イメージしているものとすごく近いです!

    斜めの位置固定で画面中央を通っているイメージで、斜めにスワイプすると、移動していくようなイメージです。
    説明が下手ですみません

Your answer might help someone💌