階層ツリーを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";
}
参考

クリックしたら展開
最初はheight: 0、opacity: 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);
}