▲TOPへ戻る

階層ツリーをCSSとjavascriptで作ってみた

最初は全くチンプンカンプンだったjavascriptもだいぶわかるようになってきました。 ページも増えてきたので、体系的にページを整理しようと思い、階層ツリーを自作してみました。

HTML

HTMLはjavascriptで生成するので、一行だけです。

 <div class="list-wrapper"></div>

CSS

リスト頭にあるプラスと、クリックしたときにマイナスにある部分を疑似要素で表現します。

クラスactiveが追加されたら、マイナスに変わります。

  
  .list-wrapper li::before {
    font-family: FontAwesome;
    content: "\f0fe";
    position: absolute;
    top: 8px;
    left: -10px;
  }
  .list-wrapper li.active::before {
    content: "\f146";
  }

参考

css
疑似要素のafterとbeforeで文字の前後に文字や記号をを追加する方法

クリックしたら展開

最初はheight: 0opacity: 0 にしておき、クラスactiveが追加されたら、 height: 100%opacity: 1に設定。

  .list-wrapper .directory1 ul,
  .list-wrapper .directory1.active .directory2 ul {
    height: 0;
    opacity: 0;
    transition: all 0.3s ease;
  }
  .list-wrapper .directory1.active ul,
  .list-wrapper .directory1.active .directory2.active ul {
    height: 100%;
    opacity: 1;
  }

縦と横の点線

縦の点線は<span>で作っています。

横線は疑似要素で作っています。

  /* 縦の点線 */
  .dir1-ul.active .vertical1 {
    position: absolute;
    width: 2px;
    height: calc(100% - 65px);
    top: 35px;
    left: 0;
    border-left: 2px dotted #333;
  }
  .dir2-ul.active .vertical2 {
    position: absolute;
    width: 2px;
    height: calc(100% - 65px);
    top: 30px;
    left: 18px;
    border-left: 2px dotted #333;
  }
  /* 横の点線 */
  .directory2::after,
  .directory3::after {
    content: "";
    position: absolute;
    width: 20px;
    height: 3px;
    top: 15px;
    left: -35px;
    border-bottom: 2px dotted #333;
  }

javascript

data.js

ここに書き込んでいけば、あとはHTMLが生成されます。

3階層まで作れます。

const data = [
    {
      dir1: [
        "content1",
        {
          dir2: [
            "content1の内容1",
            { dir3: "content1の内容1-1" },
            { dir3: "content1の内容1-2" },
            { dir3: "content1の内容1-3" },
          ],
        },
        {
          dir2: [
            "content1の内容2",
            { dir3: "content1の内容2-1" },
          ],
        },
      ],
    },
    {
      dir1: [
        "content2",
        {
          dir2: [
            "content2の内容1",
            { dir3: "content2の内容1-1" },
            { dir3: "content2の内容1-2" },
          ],
        },
      ],
    },
  ];

HTML生成

  // ここに生成されます。
  const listWrapper = document.querySelector(".list-wrapper");

  // 第一ディレクトリの数だけ処理を繰り返す
  for (let i = 0; i < data.length; i++) {
    const element = data[i];
    const dir1Ul = document.createElement("ul");
    dir1Ul.classList.add("dir1-ul");
    listWrapper.appendChild(dir1Ul);
    const ver1 = document.createElement("span");// 縦線の要素
    ver1.classList.add("vertical1");
    dir1Ul.appendChild(ver1);
    const dir1Li = document.createElement("li");
    dir1Li.classList.add("directory1");
    dir1Li.textContent = element.dir1[0];
    dir1Ul.appendChild(dir1Li);

    // 第二ディレクトリの数だけ処理を繰り返す
    if (element.dir1.length > 1) {
      for (let j = 1; j < element.dir1.length; j++) {
        const element2 = element.dir1[j];
        const dir2Ul = document.createElement("ul");
        dir2Ul.classList.add("dir2-ul");
        dir1Li.appendChild(dir2Ul);
        const ver2 = document.createElement("span");// 縦線の要素
        ver2.classList.add("vertical2");
        dir2Ul.appendChild(ver2);
        const dir2Li = document.createElement("li");
        dir2Li.classList.add("directory2");
        dir2Li.textContent = element2.dir2[0];
        dir2Ul.appendChild(dir2Li);
        const dir3Ul = document.createElement("ul");
        dir3Ul.classList.add("dir3-ul");
        dir2Li.appendChild(dir3Ul);

        // 第三ディレクトリの数だけ処理を繰り返す
        for (let k = 1; k < element2.dir2.length; k++) {
          const element3 = element2.dir2[k];
          const dir3Li = document.createElement("li");
          dir3Li.classList.add("directory3");
          dir3Li.textContent = element3.dir3;
          dir3Ul.appendChild(dir3Li);
        }
      }
    }
  }

クリック処理

クリックしたら、クラスactiveが追加され、展開する。

再度クリックしたら、削除し、閉じる。

  const dir1 = document.querySelectorAll(".directory1");
  const dir = document.querySelectorAll('[class^="directory"]');
  const vertical1 = document.querySelectorAll(".vertical1");
  
  // 全てのリスト(.directory)をforEachで
  dir.forEach((e) => {
    isClicked(e);
  });

  // クリックしたときの処理
  function isClicked(e) {
    e.addEventListener("click", function (event) {
      // 第二第三ディレクトリをクリックする時に重要
      event.stopPropagation(); 
      if (!e.classList.contains("deepestDir")) {
        e.classList.toggle("active");
        e.parentElement.classList.toggle("active");
        vertical();
      }
    });
  }
  
  const li = document.querySelectorAll(".list-wrapper li");
  //  一番最後のディレクトリは最初からactiveを追加し、
  //  クリックしてもactiveが削除されないようにする
  li.forEach((element) => {
    if (element.children.length == 0) {
      element.classList.add("active", "deepestDir");
    }
  });

縦の点線

横線は展開しても長さは変わらないですが、第一ディレクトリは展開すると長さが 変化するので、ひと手間必要。

  function vertical() {
    //  transitionで.3s後に高さが変化するため
    setTimeout(function () {
      for (let i = 0; i < dir1.length; i++) {
        let element = dir1[i];
        //  第一ディレクトリと第二ディレクトリの最後の距離を取得し、
        //  長さを計算
        let targetDir1 = element.getBoundingClientRect().top;
        let targetDir2 = element.lastElementChild.
                          getBoundingClientRect().top;
        vertical1[i].style.height = targetDir2 - targetDir1 - 20 + "px";
      }
    }, 310);
  }
  

こんな記事も読まれています。

profile

パソコン好きなガオ

コロナ禍によるステイホームを機にプログラミングを学ぶ。パソコンに関してはプロではないが、ちょっと詳しい程度。

パソコン

javascript

カメラ

ブログ