← 一覧へ

Tab Menu / 28 3Dアイソメトリック|3D Isometric

デザイン見本

  • Tab 1
  • Tab 2
  • Tab 3
Good morning. This is the content of Tab 1.
Hello. This is the content of Tab 2.
Good evening. This is the content of Tab 3.

立体感(アイソメトリック)を意識したブロックのようなタブです。選択時やホバー時には実際にブロックが押し込まれたり浮き上がったりするような動きをCSSの `transform-style: preserve-3d;` と `transform` を使って表現しています。

実装コード

HTML
<div class="tab-container">
    <ul>
        <li class="selected" data-id="tab-1"><span class="front">Tab 1</span><span class="left"></span><span class="bottom"></span></li>
        <li data-id="tab-2"><span class="front">Tab 2</span><span class="left"></span><span class="bottom"></span></li>
        <li data-id="tab-3"><span class="front">Tab 3</span><span class="left"></span><span class="bottom"></span></li>
    </ul>
    <div class="tab-content selected" id="tab-1">
        Good morning. This is the content of Tab 1.
    </div>
    <div class="tab-content" id="tab-2">
        Hello. This is the content of Tab 2.
    </div>
    <div class="tab-content" id="tab-3">
        Good evening. This is the content of Tab 3.
    </div>
</div>
CSS
.tab-container {
    perspective: 1000px;
    padding: 20px 0;
}

.tab-container ul {
    margin: 0 0 32px 0;
    padding: 0;
    list-style: none;
    display: flex;
    gap: 16px;
    transform-style: preserve-3d;
}

.tab-container ul li {
    flex: 1;
    position: relative;
    height: 50px;
    cursor: pointer;
    transform-style: preserve-3d;
    transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

.tab-container ul li .front {
    position: absolute;
    top: 0; left: 0; width: 100%; height: 100%;
    background: #fdfdfd;
    border: 2px solid #2b2b2b;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: bold;
    color: #2b2b2b;
    box-sizing: border-box;
    transform: translateZ(10px);
    transition: all 0.4s ease;
}

.tab-container ul li .left {
    position: absolute;
    top: 0; left: 0; width: 10px; height: 100%;
    background: #e0e0e0;
    border: 2px solid #2b2b2b;
    border-right: none;
    transform: rotateY(-90deg) translateZ(5px) translateX(-5px);
    box-sizing: border-box;
    transition: all 0.4s ease;
}

.tab-container ul li .bottom {
    position: absolute;
    bottom: 0; left: 0; width: 100%; height: 10px;
    background: #d0d0d0;
    border: 2px solid #2b2b2b;
    border-top: none;
    transform: rotateX(-90deg) translateZ(5px) translateY(5px);
    box-sizing: border-box;
    transition: all 0.4s ease;
}

.tab-container ul li:not(.selected):hover {
    transform: translate(-4px, -4px);
}
.tab-container ul li:not(.selected):hover .front {
    box-shadow: 4px 4px 0px #2b2b2b;
}

.tab-container ul li.selected {
    transform: translate(2px, 2px);
}
.tab-container ul li.selected .front {
    background: #2b2b2b;
    color: #fff;
    box-shadow: 0 0 0 transparent;
}
.tab-container ul li.selected .left { background: #1a1a1a; }
.tab-container ul li.selected .bottom { background: #000; }

.tab-container .tab-content {
    display: none;
    padding: 24px;
    background: #fff;
    border: 3px solid #2b2b2b;
    border-radius: 8px;
    box-shadow: 8px 8px 0px rgba(43,43,43,0.1);
    min-height: 150px;
}

.tab-container .tab-content.selected {
    display: block;
    animation: isometricPop 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

@keyframes isometricPop {
    from { opacity: 0; transform: scale(0.95) translateY(10px); }
    to { opacity: 1; transform: scale(1) translateY(0); }
}
JS
(function () {
    'use strict';
    document.addEventListener('DOMContentLoaded', () => {
        const tabBoxes = document.querySelectorAll('.tab-container');
        for (const box of tabBoxes) {
            const menuItems = box.querySelectorAll('ul li');
            const contents = box.querySelectorAll('.tab-content');
            for (const item of menuItems) {
                item.addEventListener('click', () => {
                    for (const mItem of menuItems) {
                        mItem.classList.remove('selected');
                    }
                    item.classList.add('selected');
                    for (const content of contents) {
                        content.classList.remove('selected');
                    }
                    const targetKey = item.dataset.id;
                    const targetContent = document.getElementById(targetKey) || box.querySelector('#' + targetKey);
                    if (targetContent) {
                        targetContent.classList.add('selected');
                    }
                });
            }
        }
    });
})();