css3で作るクリスタルチックな3Dメニュー

css, WEB制作

css3のtransformを使ってクリスタルチックなメニューを作ってみました。
一番綺麗に表示できるのはwebkit系のブラウザで、firefoxではクリスタルっぽさがなくなってしまいました。
残念ながらIEは非対応です。

2014/10/31追記
コメントにてwebkit系のブラウザでリンクが機能しないとのご報告がありましたので、サンプルコードを追記しました。
基本的にHTML部分とCSS両方のコードの修正をしています。
本文下部にてwebkit系でも動くコードを記載しています。(少々力技ですが)

スポンサーリンク

参考URL

See the Pen Pure CSS 3D Menu by Joshua Hibbert (@joshnh) on CodePen.

サンプル

まずは縦メニュー
こちらは参考ソースにちょっと手を加えただけです。

HTML部分はシンプルです。

<nav class="mainNav">
    <ul>
        <li><a href="" data-title="Home">Home</a></li>
        <li><a href="" data-title="About">About</a></li>
        <li><a href="" data-title="blog">blog</a></li>
        <li><a href="" data-title="Work">Work</a></li>
        <li><a href="" data-title="wordpress">wordpress</a></li>
        <li><a href="" data-title="Contact">Contact</a></li>
    </ul>
</nav>

CSS部分がちょっと長いです。

.mainNav {
    float: left;
   	-webkit-transform: perspective(500px);
   	-o-transform: perspective(500px);
   	transform: perspective(500px);
    -webkit-transform-style: preserve-3d;
    -moz-transform-style: preserve-3d;
    transform-style: preserve-3d;
}

.mainNav ul {
  list-style: none;
  padding: 0;
}
.mainNav a {
    background-color: transparent;
    background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,0%,.05)),
                      linear-gradient(-72deg, hsla(0,0%,100%,.05) 50%, transparent 50%);
    -webkit-backface-visibility: hidden;
    -moz-backface-visibility: hidden;
    backface-visibility: hidden;
    box-shadow: inset 0 0 .25em hsla(0,0%,0%,.1),
                inset 0 0 1.5em hsla(0,0%,0%,.25);
    color: #333;
    display: block;
    font-size:18px;
    font-weight:bold;
    width:108px;
    height:55px;
    line-height:55px;
    padding: 0 1.5em;
    position: relative;
    text-align: center;
    text-decoration: none;
    text-shadow: 0 1px 1px hsla(0,0%,100%,.25);
    -webkit-transition: 0.5s;
    -o-transition: 0.5s;
    transition: 0.5s;
    -webkit-transform-origin: 50% 0;
    -moz-transform-origin: 50% 0;
    -o-transform-origin: 50% 0;
    transform-origin: 50% 0;
    -webkit-transform-style: preserve-3d;
    -moz-transform-style: preserve-3d;
    transform-style: preserve-3d;
}
.mainNav a:after {
    box-shadow: inset 0 0 .25em hsla(0,0%,0%,.2),
                inset 0 0 1.5em hsla(0,0%,0%,.3);
    bottom: 0;
    color: #666;
    content: attr(data-title);
    top: 0;
    left: 100%;
    line-height: 3;
    position: absolute;
    -webkit-transform: rotateY(90deg);
    -o-transform: rotateY(90deg);
    transform: rotateY(90deg);
    -webkit-transform-origin: 0 0;
    -moz-transform-origin: 0 0;
    -o-transform-origin: 0 0;
    transform-origin: 0 0;
    width: 100%;
}
.mainNav a:before {
    background-color: transparent;
    background-image: linear-gradient(hsla(0,0%,100%,.07), hsla(0,0%,0%,.07)),
                      linear-gradient(hsla(0,0%,0%,.01), hsla(0,0%,0%,.1));
    bottom: 0;
    content: '';
    height: 9em;
    left: 100%;
    position: absolute;
    top: 0;
    -webkit-transform-origin: 0 0;
    -moz-transform-origin: 0 0;
    -o-transform-origin: 0 0;
    transform-origin: 0 0;
    width: 100%;
}
.mainNav li:nth-child(-n+2) a:before {
	-webkit-transform: rotateX(-90deg) translateX(-9em) translateZ(3em);
	-o-transform: rotateX(-90deg) translateX(-9em) translateZ(3em);
	transform: rotateX(-90deg) translateX(-9em) translateZ(3em);
}
.mainNav li:nth-child(n+3) a:before {
	-webkit-transform:  rotateX(-90deg) translateX(-9em);
	-o-transform:  rotateX(-90deg) translateX(-9em);
	transform:  rotateX(-90deg) translateX(-9em);
}
.mainNav li:hover a {
	-webkit-transform: rotateY(-90deg) translateX(-4.5em) translateZ(4.5em);
	-o-transform: rotateY(-90deg) translateX(-4.5em) translateZ(4.5em);
	transform: rotateY(-90deg) translateX(-4.5em) translateZ(4.5em);
	background:rgba(0,0,0,0.4);
    z-index: 10;
}​

.mainNav li:hover a{
	background:#000;
}

続いて横メニューのバージョンです。

HTML部分は特に大きな変更無し。

<nav class="mainNav_2">
    <ul>
        <li><a href="" data-title="Home">Home</a></li>
        <li><a href="" data-title="About">About</a></li>
        <li><a href="" data-title="blog">blog</a></li>
        <li><a href="" data-title="Work">Work</a></li>
        <li><a href="" data-title="Contact">Contact</a></li>
    </ul>
</nav>

CSS部分では結構変更箇所があります。

.mainNav_2 {
    float: left;
   	-webkit-transform: perspective(500px);
   	-o-transform: perspective(500px);
   	transform: perspective(500px);
    -webkit-transform-style: preserve-3d;
    -moz-transform-style: preserve-3d;
    transform-style: preserve-3d;
}

.mainNav_2 ul {
  list-style: none;
  padding: 0;
}
.mainNav_2 li{
	float:left;
}
.mainNav_2 a {
    background-color: transparent;
    background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,0%,.05)),
                      linear-gradient(-72deg, hsla(0,0%,100%,.05) 50%, transparent 50%);
    -webkit-backface-visibility: hidden;
    -moz-backface-visibility: hidden;
    backface-visibility: hidden;
    box-shadow: inset 0 0 .25em hsla(0,0%,0%,.1),
                inset 0 0 1.5em hsla(0,0%,0%,.25);
    color: #333;
    display: block;
    font-size:18px;
    font-weight:bold;
    width:95px;
    height:55px;
    line-height:55px;
    padding: 0 1.5em;
    position: relative;
    text-align: center;
    text-decoration: none;
    text-shadow: 0 1px 1px hsla(0,0%,100%,.25);
    -webkit-transition: 0.5s;
    -o-transition: 0.5s;
    transition: 0.5s;
    -webkit-transform-origin: 50% 0;
    -moz-transform-origin: 50% 0;
    -o-transform-origin: 50% 0;
    transform-origin: 50% 0;
    -webkit-transform-style: preserve-3d;
    -moz-transform-style: preserve-3d;
    transform-style: preserve-3d;
    -webkit-box-sizing: content-box!important;
    -moz-box-sizing: content-box!important;
    box-sizing: content-box!important;
}


.mainNav_2 a:after {
    box-shadow: inset 0 0 .25em hsla(0,0%,0%,.2),
                inset 0 0 1.5em hsla(0,0%,0%,.3);
    bottom: 0;
    color: #666;
    content: attr(data-title);
    left: 0;
    top: 0;
    line-height: 3;
    position: absolute;
    -webkit-transform: translate3d(0px,0px,-55px) rotateX(90deg);
    -o-transform: translate3d(0px,0px,-55px) rotateX(90deg);
    transform: translate3d(0px,0px,-55px) rotateX(90deg);
    -webkit-transform-origin: 0 0;
    -moz-transform-origin: 0 0;
    -o-transform-origin: 0 0;
    transform-origin: 0 0;
    width: 100%;
    display:block;
}

.mainNav_2 a:before {
    background-color: transparent;
    background-image: linear-gradient(hsla(0,0%,100%,.07), hsla(0,0%,0%,.07)),
                      linear-gradient(hsla(0,0%,0%,.1), hsla(0,0%,0%,.1));
    content: '';
    height: 55px;
    right: 0;
    position: absolute;
    top: 0;
    -webkit-transform: rotateY(270deg);
    -o-transform: rotateY(270deg);
    transform: rotateY(270deg);
    -webkit-transform-origin: 100% 0;
    -moz-transform-origin: 100% 0;
    -o-transform-origin: 100% 0;
    transform-origin: 100% 0;
    width: 55px;
}

.mainNav_2 li:nth-child(-n+2) a:before {
	-webkit-transform: rotateY(-90deg) translateY(0em) translateZ(0em);
	-o-transform: rotateY(-90deg) translateY(0em) translateZ(0em);
	transform: rotateY(-90deg) translateY(0em) translateZ(0em);
}
.mainNav_2 li:nth-child(n+3) a:before {
	-webkit-transform:  rotateY(-90deg) translateZ(8.2em);
	-o-transform:  rotateY(-90deg) translateZ(8.2em);
	transform:  rotateY(-90deg) translateZ(8.2em);
}

.mainNav_2 li:hover a {
	-webkit-transform: rotateX(-90deg) translateX(0em) translateZ(55px);
	-o-transform: rotateX(-90deg) translateX(0em) translateZ(55px);
	transform: rotateX(-90deg) translateX(0em) translateZ(55px);
	background:rgba(0,0,0,0.4);
    z-index: 10;
}​

webkit系ブラウザでもリンクイベントを正常に動作させる

ここから追記分です。
上記のコードではwebkit系のブラウザ(google chromeやopera,safari)ではリンクが機能しないので、ちょっと前の時代で使われていたテクニックを使って再現しました。
しかしながらIEには対応していないので、IEさえなんとかなれば実用性はあがるんですけどね><

サンプル

HomeとAboutにのみリンク先を指定しています。
その他のリンク先の指定は空欄にしています。

とまあこんな感じでwebkit系のブラウザでも動作させる事ができました。
横メニューは気分的に色をつけてみた。

サンプルコード

まずはHTML部分
そのままのコードではどうしても無理があったのでspan要素を入れることでなんとかなりました。

<nav class="mainNav_ex">
    <ul>
        <li><span data-title="Home">Home</span><a href="http://www.yahoo.co.jp/" target="_blank">Home</a></li>
        <li><span data-title="About">About</span><a href="http://11neko.com/" target="_blank">About</a></li>
        <li><span data-title="blog">blog</span><a href="" target="_blank">blog</a></li>
        <li><span data-title="Work">Work</span><a href="" target="_blank">Work</a></li>
        <li><span data-title="wordpress">wordpress</span><a href="" target="_blank">wordpress</a></li>
        <li><span data-title="Contact">Contact</span><a href="" target="_blank">Contact</a></li>
    </ul>
</nav>

<nav class="mainNav_ex2">
    <ul>
        <li><span data-title="Home">Home</span><a href="http://www.yahoo.co.jp/" target="_blank">Home</a></li>
        <li><span data-title="About">About</span><a href="http://11neko.com/" target="_blank">About</a></li>
        <li><span data-title="blog">blog</span><a href="" target="_blank">blog</a></li>
        <li><span data-title="Work">Work</span><a href="" target="_blank">Work</a></li>
        <li><span data-title="wordpress">wordpress</span><a href="" target="_blank">wordpress</a></li>
        <li><span data-title="Contact">Contant</span><a href="" target="_blank">Contact</a></li>
    </ul>
</nav>

今まではli要素にhoverした時にa要素を回転させていたのですが、webkit系のブラウザだとa要素の領域ごと移動してしまうので、リンクにクリックが出来ない状態でした。(他のブラウザだと領域は確保されたままなのに・・・)

なのでspan要素で表示部分を作り、a要素を透明にして領域だけ確保!という形にしました。

CSS部分

.mainNav_ex {
    float: left;
    -webkit-transform: perspective(500px);
    -o-transform: perspective(500px);
    transform: perspective(500px);
    -webkit-transform-style: preserve-3d;
    -moz-transform-style: preserve-3d;
    transform-style: preserve-3d;
}

.mainNav_ex ul {
  list-style: none;
  padding: 0;
}
.mainNav_ex li{
    position:relative;
    cursor:pointer;
}
.mainNav_ex a {
    opacity:0;
    font-size:18px;
    font-weight:bold;
    width:108px;
    height:55px;
    top:0px;
    z-index:10;
    display:block;
    position: absolute;
    line-height:55px;
    padding: 0 1.5em;
    text-align: center;
    text-decoration: none;
}
.mainNav_ex span{
    background-color: transparent;
    background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,0%,.05)),
                      linear-gradient(-72deg, hsla(0,0%,100%,.05) 50%, transparent 50%);
    -webkit-backface-visibility: hidden;
    -moz-backface-visibility: hidden;
    backface-visibility: hidden;
    box-shadow: inset 0 0 .25em hsla(0,0%,0%,.1),
                inset 0 0 1.5em hsla(0,0%,0%,.25);
    color: #333;
    display: block;
    font-size:18px;
    font-weight:bold;
    width:108px;
    height:55px;
    line-height:55px;
    padding: 0 1.5em;
    position: relative;
    text-align: center;
    text-decoration: none;
    text-shadow: 0 1px 1px hsla(0,0%,100%,.25);
    -webkit-transition: 0.5s;
    -o-transition: 0.5s;
    transition: 0.5s;
    -webkit-transform-origin: 50% 0;
    -moz-transform-origin: 50% 0;
    -o-transform-origin: 50% 0;
    transform-origin: 50% 0;
    -webkit-transform-style: preserve-3d;
    -moz-transform-style: preserve-3d;
    transform-style: preserve-3d;
}
.mainNav_ex span:after{
    box-shadow: inset 0 0 .25em hsla(0,0%,0%,.2),
                inset 0 0 1.5em hsla(0,0%,0%,.3);
    bottom: 0;
    color: #666;
    content: attr(data-title);
    top: 0;
    left: 100%;
    line-height: 3;
    position: absolute;
    -webkit-transform: rotateY(90deg);
    -o-transform: rotateY(90deg);
    transform: rotateY(90deg);
    -webkit-transform-origin: 0 0;
    -moz-transform-origin: 0 0;
    -o-transform-origin: 0 0;
    transform-origin: 0 0;
    width: 100%;
    z-index: 1;
}
.mainNav_ex span:before{
    background-color: transparent;
    background-image: linear-gradient(hsla(0,0%,100%,.07), hsla(0,0%,0%,.07)),
                      linear-gradient(hsla(0,0%,0%,.01), hsla(0,0%,0%,.1));
    bottom: 0;
    content: '';
    height: 9em;
    left: 100%;
    position: absolute;
    top: 0;
    -webkit-transform-origin: 0 0;
    -moz-transform-origin: 0 0;
    -o-transform-origin: 0 0;
    transform-origin: 0 0;
    width: 100%;
    z-index: 2;
}
.mainNav_ex li:nth-child(-n+2) span:before {
    -webkit-transform: rotateX(-90deg) translateX(-9em) translateZ(3em);
    -o-transform: rotateX(-90deg) translateX(-9em) translateZ(3em);
    transform: rotateX(-90deg) translateX(-9em) translateZ(3em);
}
.mainNav_ex li:nth-child(n+3) span:before {
    -webkit-transform:  rotateX(-90deg) translateX(-9em);
    -o-transform:  rotateX(-90deg) translateX(-9em);
    transform:  rotateX(-90deg) translateX(-9em);
}
.mainNav_ex li:hover span{
    -webkit-transform: rotateY(-90deg) translateX(-4.5em) translateZ(4.5em);
    -o-transform: rotateY(-90deg) translateX(-4.5em) translateZ(4.5em);
    transform: rotateY(-90deg) translateX(-4.5em) translateZ(4.5em);
    background:rgba(0,0,0,0.4);
    z-index: 10;
}​
.mainNav_ex li:hover a{
    display: block;
    z-index:1;
}

.mainNav_ex2 {
    float: left;
    -webkit-transform: perspective(500px);
    -o-transform: perspective(500px);
    transform: perspective(500px);
    -webkit-transform-style: preserve-3d;
    -moz-transform-style: preserve-3d;
    transform-style: preserve-3d;
}
.mainNav_ex2 ul {
  list-style: none;
  padding: 0;
}
.mainNav_ex2 li{
    float: left;
    cursor: pointer;
}
.mainNav_ex2 a {
    opacity:0;
    background-color: rgba(0,0,0,0.5);
    font-size:18px;
    font-weight:bold;
    width:108px;
    height:55px;
    top:0px;
    z-index:10;
    display:block;
    position: absolute;
    line-height:55px;
    padding: 0 1.5em;
    text-align: center;
    text-decoration: none;
    z-index: 100;
}
.mainNav_ex2 span {
    background-color: rgba(0,0,255,0.2);
    background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,0%,.05)),
                      linear-gradient(-72deg, hsla(0,0%,100%,.05) 50%, transparent 50%);
    -webkit-backface-visibility: hidden;
    -moz-backface-visibility: hidden;
    backface-visibility: hidden;
    box-shadow: inset 0 0 .25em hsla(0,0%,0%,.1),
                inset 0 0 1.5em hsla(0,0%,0%,.25);
    color: #333;
    display: block;
    font-size:18px;
    font-weight:bold;
    width:95px;
    height:55px;
    line-height:55px;
    padding: 0 1.5em;
    position: relative;
    text-align: center;
    text-decoration: none;
    text-shadow: 0 1px 1px hsla(0,0%,100%,.25);
    -webkit-transition: 0.5s;
    -o-transition: 0.5s;
    transition: 0.5s;
    -webkit-transform-origin: 50% 0;
    -moz-transform-origin: 50% 0;
    -o-transform-origin: 50% 0;
    transform-origin: 50% 0;
    -webkit-transform-style: preserve-3d;
    -moz-transform-style: preserve-3d;
    transform-style: preserve-3d;
    -webkit-box-sizing: content-box!important;
    -moz-box-sizing: content-box!important;
    box-sizing: content-box!important;
}
.mainNav_ex2 span:after {
    background-color: rgba(0,255,0,0.5);
    box-shadow: inset 0 0 .25em hsla(0,0%,0%,.2),
                inset 0 0 1.5em hsla(0,0%,0%,.3);
    bottom: 0;
    color: #666;
    content: attr(data-title);
    left: 0;
    top: 0;
    line-height: 3;
    position: absolute;
    -webkit-transform: translate3d(0px,0px,-55px) rotateX(90deg);
    -o-transform: translate3d(0px,0px,-55px) rotateX(90deg);
    transform: translate3d(0px,0px,-55px) rotateX(90deg);
    -webkit-transform-origin: 0 0;
    -moz-transform-origin: 0 0;
    -o-transform-origin: 0 0;
    transform-origin: 0 0;
    width: 100%;
    display:block;
    z-index:1;
}
.mainNav_ex2 span:before {
    background-color: rgba(255,0,0,0.2);
    background-image: linear-gradient(hsla(0,0%,100%,.07), hsla(0,0%,0%,.07)),
                      linear-gradient(hsla(0,0%,0%,.1), hsla(0,0%,0%,.1));
    content: '';
    height: 55px;
    right: 0;
    position: absolute;
    top: 0;
    -webkit-transform: rotateY(270deg);
    -o-transform: rotateY(270deg);
    transform: rotateY(270deg);
    -webkit-transform-origin: 100% 0;
    -moz-transform-origin: 100% 0;
    -o-transform-origin: 100% 0;
    transform-origin: 100% 0;
    width: 55px;
    z-index:2;
}
.mainNav_ex2 li:nth-child(-n+2) span:before {
    -webkit-transform: rotateY(-90deg) translateY(0em) translateZ(0em);
    -o-transform: rotateY(-90deg) translateY(0em) translateZ(0em);
    transform: rotateY(-90deg) translateY(0em) translateZ(0em);
}
.mainNav_ex2 li:nth-child(n+3) span:before {
    -webkit-transform:  rotateY(-90deg) translateZ(8.2em);
    -o-transform:  rotateY(-90deg) translateZ(8.2em);
    transform:  rotateY(-90deg) translateZ(8.2em);
}
.mainNav_ex2 li:hover span{
    -webkit-transform: rotateX(-90deg) translateX(0em) translateZ(55px);
    -o-transform: rotateX(-90deg) translateX(0em) translateZ(55px);
    transform: rotateX(-90deg) translateX(0em) translateZ(55px);
    background:rgba(0,0,255,0.4);
}​

.mainNav_exは縦メニュー、.mainNav_ex2は横メニューです。

ちょっと力技な気もしますが、こういう方法しか思いつかなかったです><
概念としては、li要素をhoverした時にspan要素とspanの擬似要素を回転させる。リンク領域は失いたくないのでa要素は透明度0パーセントでpositionをabsoluteにし重ねておく。

あとは擬似要素に重なったらリンクをクリックできなくなるので、z-indexで重ね順指定をしておく。

問題点としてはメンテナンスの効率。
メニューの文字数により、li要素の幅が変わるようなデザインであれば、その都度a要素のwidthも変更してあげないといけない。

レスポンシブデザインの場合だと、Media Queries等で対応すれば問題ないかな。

簡単な解説

ポイントを絞って解説。
まずはメニュー全体に対してtransform: perspectiveを適用する。

perspective(数値)
perspective()関数では、透視投影行列を指定します。 この行列は、視点を頂点として底面が視点から無限に遠ざかるピラミッドを想定して、そこに対象物となる立体を位置付けます。 perspective()関数のパラメータとして与えられる深度は、視点からz=0の平面までの距離を表します。 数値はピクセルで指定します。 数値を低くすると、ピラミッドがより平坦になって遠近感が強くなります。 例えば、1000を指定すると遠近感がゆるやかになり、200を指定すると遠近感が極端になります。 深度の値は0より大きくなくてはなりません。 0以下を指定すると無効となります。

transform:perspective()-CSS3リファレンス

同じくメニューの一番上の要素にtransform-style: preserve-3d;を適用。

transform-styleプロパティは、 このプロパティを指定した要素の子要素が、 3D空間の中でフラットに(平らに)描画されるか、立体的に描画されるかを指定する際に使用します。 指定できる値は、flat と preserve-3d のいずれかで、初期値は flat です。

transform-style-CSS3リファレンス

メニューのリンク要素にtransform-origin: 50% 0;を指定。
擬似要素のafterとbeforeを使う為、このリンク要素にもtransform-style: preserve-3d;を指定しておきます。
beforeが影の部分のスタイルを作り、afterが回転して表示される部分のスタイルを指定しています。

あとはtransformの指定で、細かい修正をいれています。

あとがき

IEでどうにか動かしたくて色々調べて頑張ってみたけど断念。
とりあえずwebkit系のブラウザでは綺麗に表示できるからスマホ向けのデザインで何かに使えるかもしれない。

この記事が気に入ったら
『いいね』しよう!