一定時間のマウスオーバーで表示されるポップアップをCSSだけで実装する方法

まめわざはかなりの部分を自力で作っておりますが、その過程で見つけたTipsを今後定期的に紹介いたします。

今回は、まめわざ管理サイト内のヘルプで使用しているポップアップの作り方です。
Googleをはじめとして大手のサイトの管理系のページで実装されている「一定時間マウスオーバーをすると表示される」という動作をHTML5のCSSのみで実装します。
出来上がりはこのようになります

まずはHTMLは以下のようになります。
通常はhelp_icoが表示され、マウスオーバーするとhelp_lまたはhelp_rが表示される仕組みです。
尚、左・右にフキダシが表示される2タイプを作るために、help_lとhelp_rを用意しました。

<span class="help"><span class="help_ico">?</span><span class="help_l">説明文<br />説明文</span></span></span>
<span class="help"><span class="help_ico">?</span><span class="help_r">説明文<br />説明文</span></span></span>

CSSは下記のようになります。

.help {
    position: relative;
    display: inline-block;
    width: 20px;
    height: 20px;
    text-align: left;
    cursor: help;
    font-weight: normal;
    white-space: normal !important;
}
    .help:hover {
        position: relative;
        z-index: 1;
        width: auto;
    }

.help_ico {
    display: inline-block;
    width: 16px;
    height: 8px;
    padding: 8px 0 0;
    background-color: #eee;
    border: 2px solid #ccc;
    border-radius: 13px;
    -webkit-border-radius: 13px;
    -moz-border-radius: 13px;
    line-height: 0;
    text-align: center;
    vertical-align: middle;
    font-size: 12px;
    color: #000;
    font-family: "Times New Roman";
}
    .help:hover .help_ico {
        position: relative;
        z-index: 3;
    }

/*help_r*/
.help_r, .help_l {
    position: absolute;
    z-index: 2;
    display: none;
    top: -5px;
    padding: 5px;
    background-color: #eee;
    border: 1px solid #ccc;
    box-shadow: rgba(0, 0, 0, 0.1) 0 0 2px;
    -moz-box-shadow: rgba(0, 0, 0, 0.1) 0 0 2px;
    -webkit-box-shadow: rgba(0, 0, 0, 0.1) 0 0 2px;
    line-height: 100%;
    font-size: 80%;
    color: #000;
}
    .help_r {
        left: -5px;
    }
    .help_l {
        right: -5px;
    }

    .help:hover .help_r, .help:hover .help_l {
        display: block;
    }

    /*フキダシ*/
    .help_r:before, .help_r:after {
        position: absolute;
        z-index: 2;
        content: " ";
        right: 100%;
    }
    .help_r:before {
        top: 9px;
        border: 6px solid transparent;
        border: 6px solid rgba(0, 0, 0, 0);
        border-right: 6px solid #ccc;
    }
    .help_r:after {
        top: 10px;
        border: 5px solid transparent;
        border: 5px solid rgba(0, 0, 0, 0);
        border-right: 5px solid #eee;
    }

    .help_l:before, .help_l:after {
        position: absolute;
        z-index: 2;
        content: " ";
        left: 100%;
    }
    .help_l:before {
        top: 9px;
        border: 6px solid transparent;
        border: 6px solid rgba(0, 0, 0, 0);
        border-left: 6px solid #ccc;
    }
    .help_l:after {
        top: 10px;
        border: 5px solid transparent;
        border: 5px solid rgba(0, 0, 0, 0);
        border-left: 5px solid #eee;
    }

    .help:hover .help_r {
        min-width: 200px;
        margin-left: 30px;
    }
    .help:hover .help_l {
        min-width: 200px;
        margin-right: 30px;
    }

ここまでを解説

ここまでのコードを実装するとこのようになります。

まずspan.help(<span class="help"></span>の意味、以下同)と、中のspan.help_icoも20px四方として表示します。
position:relativeのspan.helpに対し、説明を格納しているフキダシ部分はposition:absoluteで相対表示しますが、display:noneで非表示とし、:hover時にdisplay:blockで表示します。
あえてwidthは設定せず、hover時にmin-widthでサイズを指定します。

フキダシの部分は良く知られているTipsです。ググると色々出ると思います。簡単に説明すると、:before/:afterで疑似要素を設置し、3方向がtransparentのbroderを設置することで三角形を偽装するテクニックです。:beforeと:afterの両方を重ねれば、ちょうど枠のように表現することも可能です。

span.help_icoは円になるようにline-height:0としています。

ここまでで、マウスオーバーで「即時」表示されるフキダシが実現できます。

animationで実行を遅らせる

次に、上のCSSを少し改造して実行を遅らせます。

   .help:hover .help_r {
        min-width: 200px;
        margin-left: 30px;
    }
    .help:hover .help_l {
        min-width: 200px;
        margin-right: 30px;
    }
は取り除き、

    .help:hover .help_r {
        -moz-animation: balloon_r 2s ease forwards;
        -webkit-animation: balloon_r 2s ease forwards;
        -o-animation: balloon_r 2s ease forwards;
        -ms-animation: balloon_r 2s ease forwards;
    }
    .help:hover .help_l {
        -moz-animation: balloon_l 2s ease forwards;
        -webkit-animation: balloon_l 2s ease forwards;
        -o-animation: balloon_l 2s ease forwards;
        -ms-animation: balloon_l 2s ease forwards;
    }

を設置します。
アイコンにも回転アクションを加えるために、.help:hover .help_icoに
        -moz-animation: loader 1s linear;
        -webkit-animation: loader 1s linear;
        -o-animation: loader 1s linear;
        -ms-animation: loader 1s linear;
を追加します。

以上をまとめると以下です。

.help {
    position: relative;
    display: inline-block;
    width: 20px;
    height: 20px;
    text-align: left;
    cursor: help;
    font-weight: normal;
    white-space: normal !important;
}
    .help:hover {
        position: relative;
        z-index: 1;
        width: auto;
    }

.help_ico {
    display: inline-block;
    width: 16px;
    height: 8px;
    padding: 8px 0 0;
    background-color: #eee;
    border: 2px solid #ccc;
    border-radius: 13px;
    -webkit-border-radius: 13px;
    -moz-border-radius: 13px;
    line-height: 0;
    text-align: center;
    vertical-align: middle;
    font-size: 12px;
    color: #000;
    font-family: "Times New Roman";
}
    .help:hover .help_ico {
        position: relative;
        z-index: 3;
        -moz-animation: loader 1s linear;
        -webkit-animation: loader 1s linear;
        -o-animation: loader 1s linear;
        -ms-animation: loader 1s linear;
    }

    @-moz-keyframes loader {
        0% {
            -moz-transform: rotate(0deg);
        }
        100% {
            -moz-transform: rotate(360deg);
        }
    }
    @-webkit-keyframes loader {
        0% {
            -webkit-transform: rotate(0deg);
        }
        100% {
            -webkit-transform: rotate(360deg);
        }
    }
    @-o-keyframes loader {
        0% {
            -o-transform: rotate(0deg);
        }
        100% {
            -o-transform: rotate(360deg);
        }
    }
    @-ms-keyframes loader {
        0% {
            -ms-transform: rotate(0deg);
        }
        100% {
            -ms-transform: rotate(360deg);
        }
    }

/*help_r*/
.help_r, .help_l {
    position: absolute;
    z-index: 2;
    display: none;
    top: -5px;
    padding: 5px;
    background-color: #eee;
    border: 1px solid #ccc;
    box-shadow: rgba(0, 0, 0, 0.1) 0 0 2px;
    -moz-box-shadow: rgba(0, 0, 0, 0.1) 0 0 2px;
    -webkit-box-shadow: rgba(0, 0, 0, 0.1) 0 0 2px;
    line-height: 100%;
    font-size: 80%;
    color: #000;
}
    .help_r {
        left: -5px;
    }
    .help_l {
        right: -5px;
    }

    .help:hover .help_r, .help:hover .help_l {
        display: block;
    }

    /*フキダシ*/
    .help_r:before, .help_r:after {
        position: absolute;
        z-index: 2;
        content: " ";
        right: 100%;
    }
    .help_r:before {
        top: 9px;
        border: 6px solid transparent;
        border: 6px solid rgba(0, 0, 0, 0);
        border-right: 6px solid #ccc;
    }
    .help_r:after {
        top: 10px;
        border: 5px solid transparent;
        border: 5px solid rgba(0, 0, 0, 0);
        border-right: 5px solid #eee;
    }

    .help_l:before, .help_l:after {
        position: absolute;
        z-index: 2;
        content: " ";
        left: 100%;
    }
    .help_l:before {
        top: 9px;
        border: 6px solid transparent;
        border: 6px solid rgba(0, 0, 0, 0);
        border-left: 6px solid #ccc;
    }
    .help_l:after {
        top: 10px;
        border: 5px solid transparent;
        border: 5px solid rgba(0, 0, 0, 0);
        border-left: 5px solid #eee;
    }

    .help:hover .help_r {
        -moz-animation: balloon_r 2s ease forwards;
        -webkit-animation: balloon_r 2s ease forwards;
        -o-animation: balloon_r 2s ease forwards;
        -ms-animation: balloon_r 2s ease forwards;
    }
    .help:hover .help_l {
        -moz-animation: balloon_l 2s ease forwards;
        -webkit-animation: balloon_l 2s ease forwards;
        -o-animation: balloon_l 2s ease forwards;
        -ms-animation: balloon_l 2s ease forwards;
    }

    @-moz-keyframes balloon_r {
        0% {
            max-height: 20px;
            min-width: 0;
            padding: 0;
            opacity: 0;
            -ms-filter: "alpha(opacity=0)";
        }
        49% {
            max-height: 20px;
            min-width: 0;
            padding: 0;
        }
        50% {
            max-height: 20px;
            min-width: 0;
            padding: 5px;
        }
        51% {
            max-height: 1000px;
            min-width: 200px;
            margin-left: 30px;
            opacity: 0;
            -ms-filter: "alpha(opacity=0)";
            overflow: visible;
        }
        100% {
            max-height: 1000px;
            min-width: 200px;
            margin-left: 30px;
            opacity: 1;
            -ms-filter: "alpha(opacity=100)";
        }
    }
    @-webkit-keyframes balloon_r {
        0% {
            max-height: 20px;
            min-width: 0;
            padding: 0;
            opacity: 0;
            -ms-filter: "alpha(opacity=0)";
        }
        49% {
            max-height: 20px;
            min-width: 0;
            padding: 0;
        }
        50% {
            max-height: 20px;
            min-width: 0;
            padding: 5px;
        }
        51% {
            max-height: 1000px;
            min-width: 200px;
            margin-left: 30px;
            opacity: 0;
            -ms-filter: "alpha(opacity=0)";
            overflow: visible;
        }
        100% {
            max-height: 1000px;
            min-width: 200px;
            margin-left: 30px;
            opacity: 1;
            -ms-filter: "alpha(opacity=100)";
        }
    }
    @-o-keyframes balloon_r {
        0% {
            max-height: 20px;
            min-width: 0;
            padding: 0;
            opacity: 0;
            -ms-filter: "alpha(opacity=0)";
        }
        49% {
            max-height: 20px;
            min-width: 0;
            padding: 0;
        }
        50% {
            max-height: 20px;
            min-width: 0;
            padding: 5px;
        }
        51% {
            max-height: 1000px;
            min-width: 200px;
            margin-left: 30px;
            opacity: 0;
            -ms-filter: "alpha(opacity=0)";
            overflow: visible;
        }
        100% {
            max-height: 1000px;
            min-width: 200px;
            margin-left: 30px;
            opacity: 1;
            -ms-filter: "alpha(opacity=100)";
        }
    }
    @-ms-keyframes balloon_r {
        0% {
            max-height: 20px;
            min-width: 0;
            padding: 0;
            opacity: 0;
            -ms-filter: "alpha(opacity=0)";
        }
        49% {
            max-height: 20px;
            min-width: 0;
            padding: 0;
        }
        50% {
            max-height: 20px;
            min-width: 0;
            padding: 5px;
        }
        51% {
            max-height: 1000px;
            min-width: 200px;
            margin-left: 30px;
            opacity: 0;
            -ms-filter: "alpha(opacity=0)";
            overflow: visible;
        }
        100% {
            max-height: 1000px;
            min-width: 200px;
            margin-left: 30px;
            opacity: 1;
            -ms-filter: "alpha(opacity=100)";
        }
    }

    @-moz-keyframes balloon_l {
        0% {
            max-height: 20px;
            min-width: 0;
            padding: 0;
            opacity: 0;
            -ms-filter: "alpha(opacity=0)";
        }
        49% {
            max-height: 20px;
            min-width: 0;
            padding: 0;
        }
        50% {
            max-height: 20px;
            min-width: 0;
            padding: 5px;
        }
        51% {
            max-height: 1000px;
            min-width: 200px;
            margin-right: 30px;
            opacity: 0;
            -ms-filter: "alpha(opacity=0)";
        }
        100% {
            max-height: 1000px;
            min-width: 200px;
            margin-right: 30px;
            opacity: 1;
            -ms-filter: "alpha(opacity=100)";
        }
    }
    @-webkit-keyframes balloon_l {
        0% {
            max-height: 20px;
            min-width: 0;
            padding: 0;
            opacity: 0;
            -ms-filter: "alpha(opacity=0)";
        }
        49% {
            max-height: 20px;
            min-width: 0;
            padding: 0;
        }
        50% {
            max-height: 20px;
            min-width: 0;
            padding: 5px;
        }
        51% {
            max-height: 1000px;
            min-width: 200px;
            margin-right: 30px;
            opacity: 0;
            -ms-filter: "alpha(opacity=0)";
        }
        100% {
            max-height: 1000px;
            min-width: 200px;
            margin-right: 30px;
            opacity: 1;
            -ms-filter: "alpha(opacity=100)";
        }
    }
    @-o-keyframes balloon_l {
        0% {
            max-height: 20px;
            min-width: 0;
            padding: 0;
            opacity: 0;
            -ms-filter: "alpha(opacity=0)";
        }
        49% {
            max-height: 20px;
            min-width: 0;
            padding: 0;
        }
        50% {
            max-height: 20px;
            min-width: 0;
            padding: 5px;
        }
        51% {
            max-height: 1000px;
            min-width: 200px;
            margin-right: 30px;
            opacity: 0;
            -ms-filter: "alpha(opacity=0)";
        }
        100% {
            max-height: 1000px;
            min-width: 200px;
            margin-right: 30px;
            opacity: 1;
            -ms-filter: "alpha(opacity=100)";
        }
    }
    @-ms-keyframes balloon_l {
        0% {
            max-height: 20px;
            min-width: 0;
            padding: 0;
            opacity: 0;
            -ms-filter: "alpha(opacity=0)";
        }
        49% {
            max-height: 20px;
            min-width: 0;
            padding: 0;
        }
        50% {
            max-height: 20px;
            min-width: 0;
            padding: 5px;
        }
        51% {
            max-height: 1000px;
            min-width: 200px;
            margin-right: 30px;
            opacity: 0;
            -ms-filter: "alpha(opacity=0)";
        }
        100% {
            max-height: 1000px;
            min-width: 200px;
            margin-right: 30px;
            opacity: 1;
            -ms-filter: "alpha(opacity=100)";
        }
    }

非常に長いですが、keyframesのクロスブラウザ対策で同じ内容を繰り返しているだけです。
ひょっとすると、これらをまとめて書く方法があるのかも知れませんが、不勉強です。特に、margin-leftとmargin-rightが違うだけのほぼ同じコードを長々と書かなければいけないのが何とも非効率ですね。

animationの解説

まず、span.help_icoに
animation: loader 1s linear;
を設定して、1秒間このspanを回転させます。
次に、span.help_l/span.help_rに
animation: balloon_r 2s ease forwards;
を設定して、2秒間のアクションを指定します。
49%までの最初の1秒は何もせず、49->50%でpaddingを戻し、51%以降にwidthを指定して、100%までの1行間にopacityを1まで上げてフェードインします。
0%~49%でpaddingを0にしないと、iconにマウスが重なった時点で、まだ表示されていないフキダシエリアがマウスオーバー領域になってしまい、マウスアウトができなくなってしまいます。意図せずヘルプアイコンにマウスを当ててしまった際に、すぐにマウスが逃げられるように実装している細かいTipsです。

クロスブラウザ

以上は、Chrome・Firefox・Safariなどのモダンブラウザと、IE10以上に対応しています。IE9以下に対応するために下記を追加します。

<!--[if lte IE 9 ]>
<style type="text/css">
    .help:hover .help_r {
        min-width: 200px;
        margin-left: 30px;
    }
    .help:hover .help_l {
        min-width: 200px;
        margin-right: 30px;
    }
</style>
<![endif]-->

これにより、IE8・IE9で同じようなフキダシが表示されます。一応、IE7でも表示できます。

改善の余地があるかも知れませんが、ひとまず現時点で作成したものをこちらで説明しました。参考になれば幸いです。

2015/4/17