SVGとCSSで、
見開きページ風に画像切り替え。

〽️基本の仕組みを考える。 〽️半分の画像を作る。 〽️アニメイション。 〽️もう一工夫。 〽️挑戦は続きます。





⬆︎SVGついてのまとめページはこちら。

こんにちは、「ふ」です。
今回はSVG領域を使って、画像が見開きページのように切り替わるアニメーションを作っていきます。
2枚の画像を用意するだけで実現します。皆さんもチャレンジしてみてください。

「ふ」が準備した画像はこちら⬆︎。「晴れ」画像と「雨」画像です。
画像が写真の場合は問題ないのですが、 イラストの場合には背景を透過させるとうしろの画像が見えてしまいます。白などで塗りつぶしておくようにしてください。

今回はSVGの色々な機能を使って実装するので、初心者の方にはかなりの学習契機となるでしょう。是非試してみてください。
手っ取り早く試したい方は、ページの最後に全コードも貼っています。そちらをお使いください。

基本の仕組みを考える。


「見開き」の動きを表現するためには画像をY軸回転させるのですが、
今回のアニメーションはSVG領域の中で行います。

SVGは3Dに対応していないため、「見開き」の仕組みを作るには少しばかり工夫が必要です。
3D非対応、というのは、「重ね順が厳守されてしまう」ということです。

例えば雨画像を前面に置いて、Y軸回転させたとき。
思惑としては、雨画像の半分が晴れ画像の後ろに回り込むはず。

しかし実際には「重ね順の厳守」により晴れ画像の前面で回転してしまいます。

実際にアニメーションさせてみました。
雨画像の半分が晴れの後ろに回り込むことなく、「重ね順が厳守」されていますね💧

対策。

ここは画像をそれぞれ2分割して、半分だけをアニメーションさせることにしました。
キーフレームは以下⬇︎のようにいきたいと思います。

はじめは晴れの左右を表示。
実はこのとき、雨の左半分は90°に立ち上げているのですが、画面に対して垂直なので実質非表示になっています。
雨の右半分は、晴れ右半分の後ろに隠れています。

晴れの右半分を立ち上げていきます。

90°(実質非表示)になったところで固定。

それと同時に、垂直に立ち上げていた雨の左半分を倒していきます。

完全に倒れたところでアニメーション終了。

これでうまくいくはずです。
画像を配置して作っていきましょう!

半分の画像を作る。


扨(さて)左右半分の画像を用意することになりました。
‥手作業で作ってもいいのですが、せっかくSVGを使っているので、長方形でクリップしてしまいましょう。

HTML > body

<svg viewBox = "0 0 297 210"></svg>

先づHTMLの <body> 〜 </body> 内にSVG領域を確保しましょう。
<svg>タグを記述して、viewBox属性(基準となる座標)を指定してください。A4サイズで作りました。

svg

<svg viewBox = "0 0 297 210"> 「「1<image xlink:href = "hare.png"/>」」 </svg>

タグの中に画像を置きます。

CSS

svg { display:block; width:80%; margin:0 auto; } image { 「「1width:100%;」」 }

SVG領域をてきとーにサイズ調整。
中に読み込ませるimageのサイズを統一するため、「width:100%」に指定しておきました(読み込ませる画像の縦横比によっては、「height:100%」のほうが良い場合があります)。

SVG領域の中に晴れ画像が配置されました。

クリップに使う長方形はコードで直接作ることにしましょう。
<svg>タグのviewBox属性に注目。

<svg viewBox = "「「10 0 297 210」」">

いま「0 0 297 210」となっていますが、これはSVG領域の座標を表しています。

X軸:0〜297
Y軸:0〜210

という意味です。
半分サイズの幅の長方形を左右それぞれ作成し、元画像をクリップしてしまいましょう。

svg

<image xlink:href = "hare.png"/> 「「1<rect x = "0" y ="0" width = "148.5" height = "210" fill = "yellow"/>」」

左半分の長方形(rect)を作成します。

始点の座標
x:0
y:0

幅と高さ
width:198.5(横領域の半分ですね)
height:219(高さはそのまま)

見やすいよう色を付けます。
fill(塗り):yellow

領域内に幅半分の長方形が描画されました。SVGコードでは、後に記述された要素が前面に表示されます。
長方形をclipPathタグに定義します。

svg

「「1<clipPath id = "leftclip">」」 <rect x = "0" y ="0" width = "148.5" height = "210"/> 「「1</clipPath>」」

<clipPath> タグで <rect> を囲みます。idを「leftclip」としました。
fill(塗り)属性は不要なので外します。

svg

<image 「「1class = "left"」」 xlink:href = "hare.png"/>

晴れ画像にclass属性「left」を指定します。

CSS

.left { 「「1clip-path:url(#leftclip);」」 }

CSSで呼び出し、先ほどの「leftclip」でクリップ。

晴れの左半分だけが表示される画像ができました。
このあとは、

・晴れ画像を2枚、雨画像を2枚
・左半分のクリップ用長方形、右半分のクリップ用長方形

を<svg>タグ内に配置してCSSでクリップ。最終的に晴れ/雨の左右半分の画像4枚を作成します。

svg

<image class = "left" xlink:href = "hare.png"/> <image class = "right" xlink:href = "ame.png"/> <image class = "left" xlink:href = "ame.png"/> <image class = "right" xlink:href = "hare.png"/> <clipPath id = "leftclip"> <rect x = "0" y ="0" width = "148.5" height = "210"/> </clipPath> <clipPath id = "rightclip"> <rect 「「1x = "148.5"」」 y ="0" width = "148.5" height = "210"/> </clipPath>

CSS

.left { clip-path:url(#leftclip); } .right { clip-path:url(#rightclip); }

右側クリップ用の長方形。幅や高さは左側用と同じですが、始点のX座標が「148.5」、すなはちSVG領域の中央に指定しています。

4枚の画像が準備できました。
アニメーションさせる「晴れ右」と「雨左」を前面に、動かさない「雨右」「晴れ左」は背面に置くようにしましょう。

アニメイション。


ではいよいよ、アニメーションさせていきます。

svg

<image class = "left" xlink:href = "hare.png"/> <image class = "right" xlink:href = "ame.png"/> <image 「「1id = "ameL"」」 class = "left" xlink:href = "ame.png"/> <image 「「1id = "hareR"」」 class = "right" xlink:href = "hare.png"/>

動かすのは「雨の左」と「晴れの右」なので、それぞれid名「ameL」「hareR」としました。

CSS

image { width:100%; 「「1transform-origin:center;」」 }

SVG要素の変形の基準点は、「左上」がデフォルトです。
なので、「中心」に指定し直しておきます。

CSS

#ameL { 「「1animation:ameL 2s linear alternate infinite;」」 } #hareR { 「「1animation:hareR 2s linear alternate infinite;」」 }

アニメーションのタイミングに関する指定を行います。

・アニメーション名:ameL/hareR
・継続時間:2s
・進行具合:linear(直線的に)
・方向:alternate(交互に)
・繰り返し:infinite(永遠)

〜ポイントは「アニメーションの方向:alternate」です。
これを指定すると、順方向に再生/逆方向に再生が繰り返されます。結果的に「ページをめくったり戻したり」が繰り返されるため、往復のためのキーフレームを指定する手間が省けます。

CSS

@keyframes ameL { 0% { transform:rotateY(90deg); } 25% { transform:rotateY(90deg); } 50% { transform:rotateY(0deg); } 100% { transform:rotateY(0deg); } } @keyframes hareR { 0% { transform:rotateY(0deg); } 25% { transform:rotateY(90deg); } 100% { transform:rotateY(90deg); } }

先にお話しした「仕組み」を思い出してください。
アニメーション開始時、「雨左」は垂直に立ち上げられていて、実質非表示の状態です。
「晴れ右」は開始と同時に立ち上がり始め、25%の時点で垂直(非表示)に。それと入れ替わりで「雨左」を倒し始めます。
50%の時点で倒し完了。残りの1s間は雨画像を静止したままにしました。

結果はこの⬆︎ように。
うまくできましたでしょうか?

もう一工夫。


ひとまづはお疲れ様でした。
「これで完成!」でも良いのですが、もう少し立体感を出していくための小ワザをここでは紹介します。

◼︎ 傾きをつける〜skewY。

現在、真正面から「冊子」を見ている状態といえます。
リアル世界では、冊子は自分より少し前方にあるため、立ち上げ中のページは少し傾いて見えるはず。

transform:skewYをキーフレームに追加し、「傾き」を付けてみましょう。

CSS

@keyframes ameL { 0% { transform:rotateY(-90deg) 「「1skewY(20deg)」」; } 25% { transform:rotateY(-90deg) 「「1skewY(20deg)」」; } 50% { transform:rotateY(0deg) 「「1skewY(0deg)」」; } 100% { transform:rotateY(0deg) 「「1skewY(0deg)」」; } } @keyframes hareR { 0% { transform:rotateY(0deg) 「「1skewY(0deg)」」; } 25% { transform:rotateY(90deg) 「「1skewY(-20deg)」」; } 100% { transform:rotateY(90deg) 「「1skewY(-20deg);」」 } }

20°ほど角度をつけることにしました。
左右対象に傾斜させるため、「雨左」は+方向に、「晴れ右」はマイナス方向に指定しています。

傾きをつけることができました。
しかし何だか、それっぽくない・・

SVG領域の制限により、ページをめくるときの「」の部分が見えなくなってしまっているんですね。
CSSでoverflowを「visible」にして、表示できるようにして差し上げましょう。

CSS

svg { display:block; width:80%; margin:0 auto; 「「1overflow:visible;」」 }

これで「それっぽく」なってきましたね♪
隣接する要素への配慮として、SVG領域の上部は余白を十分に取るか、z-indexを調整してください。

◼︎ 影も付けましょう。

さらに。ページをめくるときに影を落としてみます。
幅半分サイズの長方形をコードで描いていきます。

SVG

<rect id = "kageL" width = "297" height = "210" class = "left" fill = "gray" fill-opacity = "0.4"/> <rect id = "kageR" width = "297" height = "210" class = "right" fill = "gray" fill-opacity = "0.4"/>

・id名:「kageL」「kageR」
・幅と高さ:SVG領域いっぱいでOK
・class指定でそれぞれ「left」「right」とすることでクリップパスが適用され、幅が半分サイズに成増。
・fill(塗り):gray
・fill-opacity(塗りの不透明度):0.4

影を落とすことができました。

影の「重ね順」に注意してください。
アニメーションさせる「晴れ右」「雨左」よりは背面に、動かない「雨右」「晴れ左」よりは前面に配置します。
要素の記述順を調整しましょう。

そして影もアニメーションさせます。ページの立ち上がりに応じて幅と不透明度を変化させましょう。

CSS

rect { transform-origin:center; } #kageR { animation:kageR 2s alternate linear infinite; } @keyframes kageR { 0% { transform:scaleX(1); opacity:1; } 25% { transform:scaleX(0); opacity:0; } 100% { transform:scaleX(0); opacity:0; } } #kageL { animation:kageL 2s alternate linear infinite; } @keyframes kageL { 0% { transform:scaleX(0); opacity:0; } 25% { transform:scaleX(0); opacity:0; } 50% { transform:scaleX(1); opacity:1; } 100% { transform:scaleX(1); opacity:1; } }

rect(長方形)には一括で、変形の基準点を中央に指定します。
左右の影はページの立ち上がりに合わせて水平方向に伸び縮みし、90°になったときには不透明度が0になるように指定しました。

影のアニメイションにも成功。〜今度こそ完成です。
お疲れ様でした!

挑戦は続きます。


今回の全コードはこちら⬇︎。htmlファイルをまるっと貼っています。

◼︎ mihiraki.html

<!DOCTYPE html> <html lang = "ja"> <head> <meta charset = "UTF-8"> <style> 「「4/*SVGのサイズはお好みで。*/」」 svg { display:block; width:60%; margin:0 auto; overflow:visible; } image { width:100%; 「「4/*あるいはheight:100%;*/」」 transform-origin:center; } .left { clip-path:url(#leftclip); } .right { clip-path:url(#rightclip); } #ameL { animation:ameL 2s linear alternate infinite; } #hareR { animation:hareR 2s linear alternate infinite; } @keyframes ameL { 0% { transform:rotateY(-90deg) skewY(20deg); } 25% { transform:rotateY(-90deg) skewY(20deg); } 50% { transform:rotateY(0deg) skewY(0deg); } 100% { transform:rotateY(0deg) skewY(0deg); } } @keyframes hareR { 0% { transform:rotateY(0deg) skewY(0deg); } 25% { transform:rotateY(90deg) skewY(-20deg); } 100% { transform:rotateY(90deg) skewY(-20deg); } } rect { transform-origin:center; } #kageR { animation:kageR 2s alternate linear infinite; } @keyframes kageR { 0% { transform:scaleX(1); opacity:1; } 25% { transform:scaleX(0); opacity:0; } 100% { transform:scaleX(0); opacity:0; } } #kageL { animation:kageL 2s alternate linear infinite; } @keyframes kageL { 0% { transform:scaleX(0); opacity:0; } 25% { transform:scaleX(0); opacity:0; } 50% { transform:scaleX(1); opacity:1; } 100% { transform:scaleX(1); opacity:1; } } </style> </head> <body> <svg viewBox = "0 0 297 210"> 「「1<!--画像Aのurl-->」」 <image class = "left" xlink:href = "hare_sennasi.png"/> 「「1<!--画像Bのurl-->」」 <image class = "right" xlink:href = "ame_sennasi.png"/> <rect id = "kageL" width = "297" height = "210" class = "left" fill = "gray" fill-opacity = "0.4"/> <rect id = "kageR" width = "297" height = "210" class = "right" fill = "gray" fill-opacity = "0.4"/> 「「1<!--画像Bのurl-->」」 <image id = "ameL" class = "left" xlink:href = "ame_sennasi.png"/> 「「1<!--画像Aのurl-->」」 <image id = "hareR" class = "right" xlink:href = "hare_sennasi.png"/> <clipPath id = "leftclip"> <rect x = "0" y ="0" width = "148.5" height = "210"/> </clipPath> <clipPath id = "rightclip"> <rect x = "148.5" y ="0" width = "148.5" height = "210"/> </clipPath> </svg> </body> </html>


〜今回はやることがかなり多く、長記事となりました。最後までお読みいただき、本当にありがとうございました。
「ふ」の挑戦はまだ続きます。次はユーザイベントに対応させたり、もっと多くの画像を使った「見開きアニメーション」を作ってみたいと思います。
完成次第また記事にさせていただきます。そのときまたお会いしましょう。ではまた〜 🎵



関連記事

SVG モーフィングが動かない!

2021.01.11
3つの確認ポイント。


SVGでWeb Animations API。

2020.11.12
〽️ ネイティブJavaScriptでのアニメーション。


SVG、use要素を使ったモーションパス。

2020.09.07
〽️ ポイントは‥「マイナスの遅れ」。


SVGアニメーション05、アニメーションしている部分をクリッピング(マスク)する。

2020.07.06
〽️ テキストの内部だけアニメーション。












「ふ」です。

swift、web、ガジェットなど。役立つ情報や観ていてたのしいページを書いていきたいと思います。