CSS3アニメーションでつくるインターフェイス表現

第6回画像をパネルに分けてアニメーションで切り替える

今回のお題は、画像を縦のパネルに分けて切り替えるアニメーションだ。数字のボタンを押すと、分かれたパネルが伸縮してクロスフェードするサンプル1⁠。Sliding Image Panels with CSS3の表現を参考にして、HTMLやCSSの構成を改めるとともに、とくにCSSのコードはできるかぎり絞り込んだ。仕組みがわかりやすくなるよう心がけた。

サンプル1 CSS3: Sliding image panels

画像とキャプションを切り替える

<body>要素に書くコードは、つぎのように単純な構成で始めよう。type属性を"radio"とした<input><label>の要素がボタンとなる。キャプションはh3要素の中に、<span>要素でタイトルclass属性"img-title")と説明書きclass属性"img-caption")をそれぞれ加えた。これらの<h3>要素はさらに<div>要素class属性"img-titles")に含めてある。注目してほしいのは<img>要素がないことだ。画像は空の<div>要素class属性"bg-img")にCSSで背景として加えるからだ。

<div class="container">
    <input id="select-img-1" name="radio-set-1" type="radio" checked/>
    <label for="select-img-1" class="label-img-1">1</label>
    <input id="select-img-2" name="radio-set-1" type="radio" />
    <label for="select-img-2" class="label-img-2">2</label>
    <input id="select-img-3" name="radio-set-1" type="radio" />
    <label for="select-img-3" class="label-img-3">3</label>
    <input id="select-img-4" name="radio-set-1" type="radio" />
    <label for="select-img-4" class="label-img-4">4</label>
    <div class="bg-img">
    </div>
    <div class="img-titles">
        <h3><span class="img-title">Child</span><span class="img-caption">Single emperor penguin chick</span></h3>
        <h3><span class="img-title">Adult</span><span class="img-caption">Single penguin on a piece of ice</span></h3>
        <h3><span class="img-title">Family</span><span class="img-caption">Emperor penguin with chicks</span></h3>
        <h3><span class="img-title">Children</span><span class="img-caption">Emperor penguin chicks</span></h3>
    </div>
</div>

この<body>要素の記述に対して、後に掲げるコード1<style>要素を与える。これで、ボタンを押せば画像とキャプションはとりあえず切り替わる図1⁠。ただし、切り替えの効果はまだ加えていない。

図1 ボタンで画像とキャプションが切り替わる
図1 ボタンで画像とキャプションが切り替わる

画像とキャプションが切り替わる仕組みを、先に確かめておこう。選ばれたラジオボタンtype属性"radio"の<input>要素)は、:checked擬似クラスでとれる。そこで、つぎに抜き書きしたコードのとおり、対応した画像のURLをurl()関数background-imageプロパティに定めた。

#select-img-1:checked ~ .bg-img {
    background-image: url(images/image_001.png);
}
#select-img-2:checked ~ .bg-img {
    background-image: url(images/image_002.png);
}
#select-img-3:checked ~ .bg-img {
    background-image: url(images/image_003.png);
}
#select-img-4:checked ~ .bg-img {
    background-image: url(images/image_004.png);
}

キャプションの要素class属性"img-title"と"img-caption")は、つぎのように初めはすべてopacityプロパティで透明にしておく。そして、やはり選ばれたラジオボタンに応じたキャプションの<h3>要素を一般兄弟(間接)セレクタ~:nth-child()擬似クラスで取り出して不透明にすればよい。

.img-title, .img-caption {

    opacity: 0;

}

#select-img-1:checked ~ .img-titles h3:nth-child(1) span,
#select-img-2:checked ~ .img-titles h3:nth-child(2) span,
#select-img-3:checked ~ .img-titles h3:nth-child(3) span,
#select-img-4:checked ~ .img-titles h3:nth-child(4) span {
    opacity: 1;
}

<style>要素の定めは、つぎのコード1にまとめたとおりだ。なお、CSSにベンダープレフィックスを付けなくて済むように、<script>要素で-prefix-freeを読み込んでいる(第1回のベンダープレフィックスと-prefix-freeの項参照⁠⁠。また、Google FontsのOswaldを使った(第5回のGoogle Fontsと回り込みの解除参照⁠⁠。

コード1 ボタンのクリックで画像とキャプションを切り替える
<script src="lib/prefixfree.min.js" type="text/javascript"></script>
<link href="https://fonts.googleapis.com/css?family=Oswald:400,700" rel="stylesheet" type="text/css">
<style>
body {
    font-family: "Palatino Linotype", "Book Antiqua", Palatino, serif;
    background: aliceblue;
}
.container {
    width: 400px;
    height: 267px;
    position: relative;
    margin: 0 auto;
    text-align: center;
    border: 15px solid white;
    box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1);
}
.container label {
    font-style: italic;
    width: 100px;
    height: 30px;
    cursor: pointer;
    color: white;
    line-height: 24px;
    font-size: 20px;
    float: left;
    position: relative;
    margin-top: 230px;
    z-index: 100;
}
.container label::before {
    content: '';
    width: 24px;
    height: 24px;
    background: rgba(135, 206, 235, 0.9);
    position: absolute;
    left: 50%;
    margin-left: -12px;
    border-radius: 50%;
    box-shadow: 0px 0px 0px 4px rgba(255, 255, 255, 0.3);
    z-index: -1;
}
.container label:after {
    content: '';
    width: 1px;
    height: 267px;
    background: linear-gradient(to bottom, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1));
    position: absolute;
    bottom: -20px;
    right: 0px;
}
.container input {
    display: none;
}
.bg-img {
    width: 400px;
    height: 267px;
    position: absolute;
}
#select-img-1:checked ~ .bg-img {
    background-image: url(images/image_001.png);
}
#select-img-2:checked ~ .bg-img {
    background-image: url(images/image_002.png);
}
#select-img-3:checked ~ .bg-img {
    background-image: url(images/image_003.png);
}
#select-img-4:checked ~ .bg-img {
    background-image: url(images/image_004.png);
}
.img-title, .img-caption {
    font-weight: normal;
    color: white;
    z-index: 100;
    position: absolute;
    width: 100%;
    left: 0px;
    opacity: 0;
    top: 90px;
}
.img-title {
    left: 0px;
    font-family: "Oswald", sans-serif;
    font-size: 48px;
    letter-spacing: 3px;
}
.img-caption {
    margin-top: 100px;
    letter-spacing: 0px;
    background: rgba(130, 195, 217, 0.9);
    font-size: 14px;
    padding: 4px 0px;
    font-style: italic;
}
#select-img-1:checked ~ .img-titles h3:nth-child(1) span,
#select-img-2:checked ~ .img-titles h3:nth-child(2) span,
#select-img-3:checked ~ .img-titles h3:nth-child(3) span,
#select-img-4:checked ~ .img-titles h3:nth-child(4) span {
    opacity: 1;
}
</style>

擬似要素::beforeと::afterで加えたデザイン

前掲コード1には擬似要素::before::afterで加えたデザインがあるので補っておく。円形のボタンの外枠と画像を4等分する縦線だ図2⁠。どちらも<label>要素に与えている。

図2 擬似要素::beforeと::afterで加えたボタンの丸い枠と画像の4等分線
図2 擬似要素::beforeと::afterで加えたボタンの丸い枠と画像の4等分線

::before擬似要素がボタンの丸い枠だ。border-radiusプロパティを50%に定めるとかたちが円になる。そのうえで、box-shadowプロパティにより外枠の太さと色を定めた(第3回のメニューバーに外枠を加える参照⁠⁠。

.container label::before {
    content: '';
    width: 24px;
    height: 24px;
    background: rgba(135, 206, 235, 0.9);

    border-radius: 50%;
    box-shadow: 0px 0px 0px 4px rgba(255, 255, 255, 0.3);

}

::after擬似要素には縦線を定めた。色はlinear-gradient()関数で、下に向かって透明から白のグラデーションとした(第2回のメニューに角の丸みと影を加える参照⁠⁠。

.container label:after {
    content: '';
    width: 1px;
    height: 267px;
    background: linear-gradient(to bottom, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1));

}

キャプションをクロスフェードさせる

前述のとおり、キャプションの要素class属性"img-title"と"img-caption")opacityプロパティで表示と非表示を切り替えた。したがって、つぎのようにtransitionプロパティさえ加えれば、滑らかにクロスフェードする。

.img-title, .img-caption {

    opacity: 0;

    transition: 0.8s ease-in-out;
}

さらに、どの画像が表示されているのかわかるように、クリックされたボタンの色も変えよう。つぎのようにして、選ばれた<label>要素の文字と、外枠::before擬似要素)の色を定めた。こちらはtransitionプロパティを加えていないので、クリックすればただちに切り替わる。

#select-img-1:checked ~ label.label-img-1,
#select-img-2:checked ~ label.label-img-2,
#select-img-3:checked ~ label.label-img-3,
#select-img-4:checked ~ label.label-img-4 {
    color: deepskyblue;
}
#select-img-1:checked ~ label.label-img-1::before,
#select-img-2:checked ~ label.label-img-2::before,
#select-img-3:checked ~ label.label-img-3::before,
#select-img-4:checked ~ label.label-img-4::before {
    background: white;
    box-shadow: 0px 0px 0px 4px rgba(0, 191, 255, 0.6);
}

これらの手を加えたCSSの定めが以下のコード2だ。クリックしたボタンの色と画像が切り替わるとともに、キャプションはクロスフェードする図3⁠。

図3 キャプションがクロスフェードしてボタンの色も切り替わる
図3 キャプションがクロスフェードしてボタンの色も切り替わる
コード2 クリックしたボタンの色と画像を切り替えてキャプションはクロスフェードさせる
body {
    font-family: "Palatino Linotype", "Book Antiqua", Palatino, serif;
    background: aliceblue;
}
.container {
    width: 400px;
    height: 267px;
    position: relative;
    margin: 0 auto;
    text-align: center;
    border: 15px solid white;
    box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1);
}
.container label {
    font-style: italic;
    width: 100px;
    height: 30px;
    cursor: pointer;
    color: white;
    line-height: 24px;
    font-size: 20px;
    float: left;
    position: relative;
    margin-top: 230px;
    z-index: 100;
}
.container label::before {
    content: '';
    width: 24px;
    height: 24px;
    background: rgba(135, 206, 235, 0.9);
    position: absolute;
    left: 50%;
    margin-left: -12px;
    border-radius: 50%;
    box-shadow: 0px 0px 0px 4px rgba(255, 255, 255, 0.3);
    z-index: -1;
}
.container label::after {
    content: '';
    width: 1px;
    height: 267px;
    background: linear-gradient(to bottom, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1));
    position: absolute;
    bottom: -20px;
    right: 0px;
}
#select-img-1:checked ~ label.label-img-1,
#select-img-2:checked ~ label.label-img-2,
#select-img-3:checked ~ label.label-img-3,
#select-img-4:checked ~ label.label-img-4 {
    color: deepskyblue;
}
#select-img-1:checked ~ label.label-img-1::before,
#select-img-2:checked ~ label.label-img-2::before,
#select-img-3:checked ~ label.label-img-3::before,
#select-img-4:checked ~ label.label-img-4::before {
    background: white;
    box-shadow: 0px 0px 0px 4px rgba(0, 191, 255, 0.6);
}
.container input {
    display: none;
}
.bg-img {
    width: 400px;
    height: 267px;
    position: absolute;
}
.bg-img div {
    width: 100px;
    height: 100%;
    position: relative;
    float: left;
    overflow: hidden;
}
#select-img-1:checked ~ .bg-img {
    background-image: url(images/image_001.png);
}
#select-img-2:checked ~ .bg-img {
    background-image: url(images/image_001.png);
}
#select-img-3:checked ~ .bg-img {
    background-image: url(images/image_001.png);
}
#select-img-4:checked ~ .bg-img {
    background-image: url(images/image_001.png);
}
.img-title, .img-caption {
    font-weight: normal;
    color: white;
    z-index: 100;
    position: absolute;
    width: 100%;
    left: 0px;
    opacity: 0;
    top: 90px;
    transition: 0.8s ease-in-out;
}
.img-title {
    left: 0px;
    font-family: "Oswald", sans-serif;
    font-size: 48px;
    letter-spacing: 3px;
}
.img-caption {
    margin-top: 100px;
    letter-spacing: 0px;
    background: rgba(130, 195, 217, 0.9);
    font-size: 14px;
    padding: 4px 0px;
    font-style: italic;
}
#select-img-1:checked ~ .img-titles h3:nth-child(1) span,
#select-img-2:checked ~ .img-titles h3:nth-child(2) span,
#select-img-3:checked ~ .img-titles h3:nth-child(3) span,
#select-img-4:checked ~ .img-titles h3:nth-child(4) span {
    opacity: 1;
}

画像をパネルに分けてクロスフェードする

いよいよ画像を4つのパネルに分けて、伸縮しつつクロスフェードさせる。まず、HTMLにパネルの要素をつぎのように加える。4つのパネルは<div>要素にして、それぞれに4つの画像用の<span>要素を含めた。要素の中身は空なので、CSSで画像や位置を定める。

<div class="bg-img">
    <div><!--Slice 1 -->
        <span><!-- Image 1 --></span>
        <span><!-- Image 2 --></span>
        <span><!-- Image 3 --></span>
        <span><!-- Image 4 --></span>
    </div>
    <div><!-- Slice 2 -->
        <span><!-- Image 1 --></span>
        <span><!-- Image 2 --></span>
        <span><!-- Image 3 --></span>
        <span><!-- Image 4 --></span>
    </div>
    <div><!-- Slice 3 -->
        <span><!-- Image 1 --></span>
        <span><!-- Image 2 --></span>
        <span><!-- Image 3 --></span>
        <span><!-- Image 4 --></span>
    </div>
    <div><!-- Slice 4 -->
        <span><!-- Image 1 --></span>
        <span><!-- Image 2 --></span>
        <span><!-- Image 3 --></span>
        <span><!-- Image 4 --></span>
    </div>
</div>

それぞれの<span>要素には、つぎのようにやはりbackground-imageプロパティで画像のURLを定め、パネルごとに位置決めした。さらに、transformプロパティに与えたscale()関数で拡大している。けれど、opacityプロパティが透明(0)なので表示されず、ここまでは目に見える動きは変わらない。

.bg-img div {
    width: 100px;
    height: 100%;
    position: relative;
    float: left;
    overflow: hidden;
}
.bg-img div span {
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0px;
    left: 0px;
    transform: scale(1.5);
    opacity: 0;
}
#select-img-1:checked ~ .bg-img,
.bg-img div span:nth-child(1) {
    background-image: url(images/image_001.png);
}
#select-img-2:checked ~ .bg-img,
.bg-img div span:nth-child(2) {
    background-image: url(images/image_002.png);
}
#select-img-3:checked ~ .bg-img,
.bg-img div span:nth-child(3) {
    background-image: url(images/image_003.png);
}
#select-img-4:checked ~ .bg-img,
.bg-img div span:nth-child(4) {
    background-image: url(images/image_004.png);
}
.bg-img div:nth-child(1) span {
    background-position: 0px 0px;
}
.bg-img div:nth-child(2) span {
    background-position: -100px 0px;
}
.bg-img div:nth-child(3) span {
    background-position: -200px 0px;
}
.bg-img div:nth-child(4) span {
    background-position: -300px 0px;
}

そこで、画像の読み込まれた<span>要素に、つぎのようにtransitionプロパティでアニメーションを加えた。そして、ボタンで選んだパネルの<span>要素は、つぎのようにopacityプロパティの値を1の不透明にすれば、画像もクロスフェードする。さらにその画像の<span>要素は、transformプロパティのscale()関数に渡す値を実寸(1)に戻すことによりアニメーションに伸縮も加えた図4⁠。

.bg-img {

    z-index: 1;
}

.container input:checked ~ .bg-img div span {
    transition: 0.5s ease-in-out;
}
#select-img-1:checked ~ .bg-img div span:nth-child(1),
#select-img-2:checked ~ .bg-img div span:nth-child(2),
#select-img-3:checked ~ .bg-img div span:nth-child(3),
#select-img-4:checked ~ .bg-img div span:nth-child(4) {
    opacity: 1;
    transform: scale(1);
    z-index: 100;
}
図4 パネルの画像が伸縮しながらクロスフェードする
図4 パネルの画像が伸縮しながらクロスフェードする

これで、前掲サンプル1の表現ができあがった。<body>要素への記述とCSSの定めはつぎのコード3にまとめている。CSSのコードは150行を超えて、少々長めとなった。だが、参考にしたSliding Image Panels with CSS3と比べればかなり絞り込んである。

コード3 パネルに分けた画像を伸縮させながらクロスフェードする
<div class="container">
    <input id="select-img-1" name="radio-set" type="radio" checked/>
    <label for="select-img-1" class="label-img-1">1</label>
    <input id="select-img-2" name="radio-set" type="radio" />
    <label for="select-img-2" class="label-img-2">2</label>
    <input id="select-img-3" name="radio-set" type="radio" />
    <label for="select-img-3" class="label-img-3">3</label>
    <input id="select-img-4" name="radio-set" type="radio" />
    <label for="select-img-4" class="label-img-4">4</label>
    <div class="bg-img">
        <div><!--Slice 1 -->
            <span><!-- Image 1 --></span>
            <span><!-- Image 2 --></span>
            <span><!-- Image 3 --></span>
            <span><!-- Image 4 --></span>
        </div>
        <div><!-- Slice 2 -->
            <span><!-- Image 1 --></span>
            <span><!-- Image 2 --></span>
            <span><!-- Image 3 --></span>
            <span><!-- Image 4 --></span>
        </div>
        <div><!-- Slice 3 -->
            <span><!-- Image 1 --></span>
            <span><!-- Image 2 --></span>
            <span><!-- Image 3 --></span>
            <span><!-- Image 4 --></span>
        </div>
        <div><!-- Slice 4 -->
            <span><!-- Image 1 --></span>
            <span><!-- Image 2 --></span>
            <span><!-- Image 3 --></span>
            <span><!-- Image 4 --></span>
        </div>
    </div>
    <div class="img-titles">
        <h3><span class="img-title">Child</span><span class="img-caption">Single emperor penguin chick</span></h3>
        <h3><span class="img-title">Adult</span><span class="img-caption">Single penguin on a piece of ice</span></h3>
        <h3><span class="img-title">Family</span><span class="img-caption">Emperor penguin with chicks</span></h3>
        <h3><span class="img-title">Children</span><span class="img-caption">Emperor penguin chicks</span></h3>
    </div>
</div>
body {
    font-family: "Palatino Linotype", "Book Antiqua", Palatino, serif;
    background: aliceblue;
}
.container {
    width: 400px;
    height: 267px;
    position: relative;
    margin: 0 auto;
    text-align: center;
    border: 15px solid white;
    box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1);
}
.container label {
    font-style: italic;
    width: 100px;
    height: 30px;
    cursor: pointer;
    color: white;
    line-height: 24px;
    font-size: 20px;
    float: left;
    position: relative;
    margin-top: 230px;
    z-index: 100;
}
.container label::before {
    content: '';
    width: 24px;
    height: 24px;
    background: rgba(135, 206, 235, 0.9);
    position: absolute;
    left: 50%;
    margin-left: -12px;
    border-radius: 50%;
    box-shadow: 0px 0px 0px 4px rgba(255, 255, 255, 0.3);
    z-index: -1;
}
.container label::after {
    content: '';
    width: 1px;
    height: 267px;
    background: linear-gradient(to bottom, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1));
    position: absolute;
    bottom: -20px;
    right: 0px;
}
#select-img-1:checked ~ label.label-img-1,
#select-img-2:checked ~ label.label-img-2,
#select-img-3:checked ~ label.label-img-3,
#select-img-4:checked ~ label.label-img-4 {
    color: deepskyblue;
}
#select-img-1:checked ~ label.label-img-1::before,
#select-img-2:checked ~ label.label-img-2::before,
#select-img-3:checked ~ label.label-img-3::before,
#select-img-4:checked ~ label.label-img-4::before {
    background: white;
    box-shadow: 0px 0px 0px 4px rgba(0, 191, 255, 0.6);
}
.container input {
    display: none;
}
.bg-img {
    width: 400px;
    height: 267px;
    position: absolute;
    z-index: 1;
}
.bg-img div {
    width: 100px;
    height: 100%;
    position: relative;
    float: left;
    overflow: hidden;
}
.bg-img div span {
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0px;
    left: 0px;
    transform: scale(1.5);
    opacity: 0;
}
#select-img-1:checked ~ .bg-img,
.bg-img div span:nth-child(1) {
    background-image: url(images/image_001.png);
}
#select-img-2:checked ~ .bg-img,
.bg-img div span:nth-child(2) {
    background-image: url(images/image_002.png);
}
#select-img-3:checked ~ .bg-img,
.bg-img div span:nth-child(3) {
    background-image: url(images/image_003.png);
}
#select-img-4:checked ~ .bg-img,
.bg-img div span:nth-child(4) {
    background-image: url(images/image_004.png);
}
.bg-img div:nth-child(1) span {
    background-position: 0px 0px;
}
.bg-img div:nth-child(2) span {
    background-position: -100px 0px;
}
.bg-img div:nth-child(3) span {
    background-position: -200px 0px;
}
.bg-img div:nth-child(4) span {
    background-position: -300px 0px;
}
.container input:checked ~ .bg-img div span {
    transition: 0.5s ease-in-out;
}
#select-img-1:checked ~ .bg-img div span:nth-child(1),
#select-img-2:checked ~ .bg-img div span:nth-child(2),
#select-img-3:checked ~ .bg-img div span:nth-child(3),
#select-img-4:checked ~ .bg-img div span:nth-child(4) {
    opacity: 1;
    transform: scale(1);
    z-index: 100;
}
.img-title, .img-caption {
    font-weight: normal;
    color: white;
    z-index: 100;
    position: absolute;
    width: 100%;
    left: 0px;
    opacity: 0;
    top: 90px;
    transition: 0.8s ease-in-out;
}
.img-title {
    left: 0px;
    font-family: "Oswald", sans-serif;
    font-size: 48px;
    letter-spacing: 3px;
}
.img-caption {
    margin-top: 100px;
    letter-spacing: 0px;
    background: rgba(130, 195, 217, 0.9);
    font-size: 14px;
    padding: 4px 0px;
    font-style: italic;
}
#select-img-1:checked ~ .img-titles h3:nth-child(1) span,
#select-img-2:checked ~ .img-titles h3:nth-child(2) span,
#select-img-3:checked ~ .img-titles h3:nth-child(3) span,
#select-img-4:checked ~ .img-titles h3:nth-child(4) span {
    opacity: 1;
}

おすすめ記事

記事・ニュース一覧