CSSだけで横メニューをつくる。但し、ボックスからはみ出たリストはボタンにして、そのボタンを押すと縦のリストメニューが出る

  • 12
    いいね
  • 2
    コメント

うちのデザイナーが無茶言うんだが...。できちゃったかもしれないシリーズ

本日の課題はこれです。

cssだけで、横並びのメニューを作る。但し、ボックスからはみ出たリストはボタンにして、そのボタンを押すと縦のリストメニューが出る

自分でも、何を言っているのか分からなくなってきましたが、
ようはchromeのブックマークバーとかにあるこんなUIですね

Chrome Bookmark Bar

※ 正式名称分かる人教えてください。タイトル長すぎる。

うん、わかる。簡単にできそうだよね。

デザイナー: こんなの瞬殺でしょ?

いや、できねーから

cssだけで、横並びのメニューを作る。⇒ これは出来る。

問題は条件の方

但し、ボックスからはみ出たリストはボタンにして、そのボタンを押すと縦のリストメニューが出る ⇒ これムリゲーじゃね?

overflow: listmenuとかあればいいんだが、そんなものはない。

なんとなく、元々の要素も、はみ出た要素もリスト要素なので、:checked疑似要素と組み合わせれば、出来るような、出来ないような....。

で、作りました。

↓↓↓ デモページはこちら ↓↓↓
CodePen
↑↑↑ デモページはこちら ↑↑↑

  • 通常時


    通常時
  • 幅が狭まってリストが隠れると、右端に》ボタンが出現


    ボタン出現
  • 右端の》ボタンをクリックすると隠された要素が縦のリストで表示される


    隠されたリストを表示する

作ったのかよ!と自己つっこみしつつ
一応、最新のブラウザ(IE11,firefox,chrome)に対応しています。

これ作った奴マジ、頭沸いているとしか思えない。

あ、俺でした。

html
  <label for="menuOn">
    <input id="menuOn" type="checkbox">
    <menu>
      <ul>
        <li><a href="#menu1">概要</a>
        <li><a href="#menu2">逆L字をつくる</a>
        <li><a href="#menu3">ボタン切替</a>
        <li><a href="#menu4">リスト幅可変</a>
        <li><a href="#menu5">ボタン表示</a>
        <li><a href="#menu6">動作環境</a>
        <li><a href="#menu7">仕様</a>
        <li><a href="#menu8">免責事項</a>
      </ul>
    </menu>
    <div class="spacer"></div>
    <div class="overlay"></div>
  </label>
css
/* ========================================================== */
/*   ここから下がメニューの設定                                */
/* ========================================================== */

/* メニューのON/OFFを保存する為のチェックボックス 非表示 */
#menuOn{
  display : none
}

/* 隠しメニューを表示したとき用のスペーサー */
/* なのでデフォルトでは非表示               */
#menuOn + menu + div.spacer{
  display : none;
}

/* 隠しメニューを表示時のメニューの設定 */
/* absolute指定を行い、高さも与える     */
#menuOn:checked + menu{
  height    : calc(100% - 96px); /* 96pxはヘッダの高さ可変だと厳しい */
  position  : absolute;
  max-width : 960px;/*コンテナと同じ数値を指定しておく必要あり*/
  z-index   : 20;
  background: transparent;
}

/* スペーサー                                           */
/* 隠しメニューが表示されているときにメニュー全体が     */
/* absoluteになり高さが失われるので、                   */
/* スペーサーで高さを補います                           */
#menuOn:checked + menu + div.spacer{
  display   : block;
  height    : 40px; /*メニューの高さに合わせる*/
}

/* オーバーレイ                                         */
/* 隠しメニューが表示されているときに                   */
/* LightBoxのように画面全体を覆うブロックを表示する     */
/* これによってメニュー部分を除いて                     */
/* 画面全体がチェックボックスへのクリックになるので     */
/* メニュー以外の部分をクリックするとメニューが閉じます */
#menuOn:checked + menu + div.spacer + div.overlay{
  position : fixed;
  top      :  0;
  bottom   :  0;
  left     :  0;
  right    :  0;
  z-index  : 10;
  /*background : rgba(0,0,0,0.5);*/
}

menu{
  height      : 40px; /* メニューの高さすべてこれに合わせる*/
  overflow    : hidden;
  font-size   : 20px;
  line-height : 20px;
  width       : 100%;
  min-width   : 200px;
  background  : #33aa33;  /* リスト(li)要素の背景色と合わせる*/
}

/* メニューの高さ分のスペースを空ける */
menu::before {
  display : block;
  content : '';
  float   : left;
  width   : 0;
  height  : 40px; /*メニューの高さに合わせる*/
}

ul{
  height : 320px;/*リスト項目(8)*メニューの高さ(40px)=320px以上*/
  width  : calc(100% + 200px);/*隠しリストの幅を足す*/
}

/* 逆L字を作る為の左下のブロック*/
ul::before {
    display : block;
    content : '';
    float   : left;
    clear   : both;
    width   : calc(100% - 200px);/*隠しリストの幅を引く*/
    height  : 100%;
}

li:first-child{
  padding-left : 200px;/*隠しリストの幅*/
  max-width    : 600px;/*隠しリストの幅*3 */
}

li{
  display          : inline-block;
  /* メニュー要素の背景色と合わせる*/
  background-color : #33aa33;
  min-width        :  305px;/*隠しリストの幅/2+α*/
  max-width        :  400px;/*隠しリストの幅*2 */
  margin-left      : -200px;/*-隠しリストの幅*/
  white-space      : nowrap;
  text-overflow    : ellipsis;
}

li a{
  display       : block;
  padding       :  10px;
  padding-right : 210px;/*隠しリストの幅+10px */
}

/* ========================================================== */
/*   ここから下ははみ出たリストを表示する為のボタンの制御     */
/* ========================================================== */

li:last-child{
  position : relative;
}

/* メニューのボックスの幅が狭まって、リストが隠れた場合に*/
/* 隠れたリストが見れるボタンを作る */
li:last-child::after{
  position    : absolute;
  z-index     : 30;
  display     : block;
  /* メニューの項目数以上必要                     */
  /* \bb は終わりギュメ                           */
  /*   RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK */
  /* \a  は改行コード                             */
  content     : '\bb\a\bb\a\bb\a\bb\a\bb\a\bb\a\bb\a\bb\a';
  bottom      : 40px;/* メニューの高さに合わせる*/
  right       :  0;
  width       : 125px;
  color       : white;
  white-space : pre;
  line-height : 40px;
  background  : #229922;
  padding-left: 10px;
}

/* リストを見るボタンが押されている場合は消す */
#menuOn:checked + menu li:last-child::after{
  display : none;
}

/* 隠しリストが表示されているときは常に表示 */
#menuOn:checked + menu::after {
  position     : absolute;
  z-index      : 30;
  display      : block;
  content      : '\bb';
  line-height  : 40px;
  width        : 20px;
  padding-left : 10px;
  color        : white;
  right        :  0;
  top          :  0;
  background   : #229922;
}

さて、htmlとcssのコードはわりとすっきり書けたのですが、解説ないと意味不明だと思われるので、基本的な考え方を解説して行きます。

逆L字を作る

まず、隠れたリストも表示された状態である、逆L字を作ります。

これは、

右下または左下においた画像に文字列を回り込ませるHTML/CSS

こちらの画像を右下に置いた時の回り込みを応用し、画像の部分を:before,:afterなどの疑似要素を使って実現しています。

これで大枠が出来ました!

次にこれをボタンを押したら逆L字状態にしたい

htmlとcssだけでボタンを作りたいという場合は、定番のテクニック、チェックボックス使って、:checked疑似要素と隣接セレクタのコンボですね。

これで、はみ出た要素をボタン化することが出来ました。

横メニューのリストは文字数によって可変・ボタンを押した時の縦メニューは固定幅

横メニューのリストとボタンを押した時の縦メニューの幅が等幅だったらここで終わりだったんだが...。

マジ言ってんの?
いや、確かにchromeのブックマークツールバーはそうだけどさ
overflowの状態が分かる疑似要素:overflowみたいなのがないので、途中で固定幅には出来ない。

んじゃ、最初からリスト要素の右側を幅大目に取っておけばいいんじゃないのという戦略でnegativeマージン使ってリスト要素を重ねる。

尚、この戦略はメニューが透明だと要素が重なっているのが見えてしまうので採れない

最後に隠された要素がある場合だけ表示されるボタン

メニューのリンク以外のすべての領域をクリックするとメニューの開閉となるので、機能上はほぼ完成だが、最後に隠された要素がある場合だけ表示されるボタンを表示したい。のだが、前述のとおり、疑似要素:overflowみたいなのがないので、コンテンツがはみ出ているかどうか知るすべはない。(javascriptならもちろんできますとも)

なので、通常時は横リスト、隠された要素は縦リストという、縦、横の差異を利用して実現しています。

動作環境

基本的に最新ブラウザであれば対応していますが、わりと古い技術を使っているので、IEの古い奴とかじゃなければ動くかもしれない。IE10のエミュレーションモードでは動作。IE9のエミューレーションモードは無理だった。Operaは未確認。

  • 動作確認済みブラウザ
    Windows 7 Chrome 56.0.2924.87 (64-bit)
    Firefox 51.0.1 (32 ビット)
    IE11 11.0.38
    macOS Sierra(10.12.2) Chrome 56.0.2924.87 (64-bit)
    Firefox 52.0.1 (64 ビット)
    Safari 10.0.2
    iOS 9.3.5 mobile safari
    Android 5.0.0(ZenFone2) chrome 54.0.2840.68
    ASUS Browser(標準ブラウザ) 2.1.2.71_160715

免責事項

基本的に実用上は問題ないレベルに仕上がっているとは思っていますが、ご利用は自己責任でお願いします。

実際にサービスに投入されることは多分ないと思います
とはいえ、たまには無茶な要求にチャレンジして腕を磨くのもいいですね。

そんな私はインフラエンジニアです。

え?

えっ??