Slickを利用して画像が徐々にズームしていくエフェクトをかける
通常のスライドショーを行う際に、自分はよくSlickを利用している。
で、少し前に少し大きめの案件が入った際に、画像を徐々にズームをしていきたいといった要望があった。
まぁ普通に考えたら単純にズームをかけていく場合、Slickを使わずともJSで順繰りに要素へズーム用のCSSクラスを付与していけば良いだけなのでそこまで難しいものでもない。
・・・が、Web制作は魔法のように何でも出来ると考える諸氏諸々に、更に後付であれもあれもと要望がくると非常に鬱陶しいので、今回もSlickにお世話になった。
今回はこれを利用して画像が徐々にズームしていくエフェクトを作った際に書いたコードを、またもメモがてら掲載しようと思う。
Slickでの徐々にズームを実装するに当たり、最低やらなければならないこととして、
- 通常のフェードと同じようにスライド継続中は後ろの画像は透過すること。
- スライド継続中は前面の画像が継続してズームされること。
- オープニングの際は無地背景からズームが開始されること。
これらが挙げられる。
1は兎も角、2以降がSlickで行うためには非常に厄介で、slick.jsの仕様を少し考えて設定しなければならない。
特に、2番のスライド継続中の動作が最も厄介。
デフォルトで全面に表示される要素は常に「slick-active」が付与されるので、これに対してCSSでduration多めにズームなりフェードなりを併せ掛けすれば割と簡単に行けるんでない?とか思った。
・・・まぁ、そうも単純には行かないもので、結果はfalse。
「slick-active」は次のスライドに移る際に前のスライドから除去されるので、スライドが前面状態になった際のズーム自体はうまくいっても、次のスライドに移る際に元のサイズに戻っていく。まぁ少し考えれば当然だわな。
そこで、自分が思いついたのが、「slick-active」が付く前の要素に、スライド継続中を識別させるCSSクラスを与えるということ。
具体的に言うとslick.jsには、.on(‘beforeChange’)というメソッドがあるので、これが今回の内容を満たすためのミソとなる。(ちなみに.on(‘afterChange’)というメソッドもあるが、今回は使用しない。)
この.on(‘beforeChange’)は、要素が前面に来る前の動作を指定させることができるメソッド。
つまり、このメソッド内で、slick-activeが付く要素に、継続中を識別させるCSSクラス(仮に「slick-continue」とする)を予め付与させる。
そして、次のスライドに移るときにslick-activeだけを消せば、前面に来た際(slick-active)と、前面から抜けて継続中の状態となる際(slick-continue)のCSSクラスの付与を分けて行うことが出来る。
悩みの継続中の問題はこれで解決する。
続いて、これまた厄介なオープニング処理。
Slickのフェードはデフォルトのまま使用すると、エフェクトが何もかからずに画像が最初から表示されているというのはとても有名な話。
まぁ、このほうがスライドショーとしては整然としているんだけど。
オープニング時点でも、第一要素には初期状態でしっかりslick-activeが付与されている。
これは後付けで最初の要素に設定されるわけではなく、Slickの初期化時点で予め付与されるものである。
そのため、残念ながらオープニング→ズーム開始!とは普通に組み込むだけではなってくれない。
これについては、オープニングを行う際に、スライドの第一要素に対して、タイムアウト処理でslick-activeを除去→再定義を行って対応する。
以上より書いたコードが以下となる。
Slickの基本設定:
var slickbase = $('#slick-main'); var timeline = 5000; slickbase.slick({ fade: true, // fadeモードで動作させる autoplay: true, // autoplayモードを有効化する autoplaySpeed: timeline, // [重要]画像の切り替えのタイミングをautoplaySpeedで取っておく speed: 0, // [重要]通常はスライド自体の速度をこのプロパティで設定するがゼロに設定する(タイミング制御はCSS側で設定するため) pauseOnHover: false, // 徐々にズームをさせるために一時停止は意味を持たないため無効化 pauseOnFocus: false, // 上に同じ swipe: false, // [重要]徐々にズームをかける都合上、組込側が想定している挙動にするためスワイプは無効化 swipeToSlide: false, // 上に同じ slidesToShow: 1, // スライド数は1 slidesToScroll: 1, // スライドのスクロール数は1 dots: true, // ページングくらいは出したいのでdotはtrueで arrows: false, // ページャーも一応設定可能だが、今回は扱わない infinite: true, // 永続させる }).on('beforeChange', function(event, slick, currentSlide){ var slidebase = $(this).parents(); // slick-activeを抜けた際にエフェクトの継続をかけるために付与 slidebase.find('.slick-slide').removeClass('slick-continue'); slidebase.find('.slick-active').addClass('slick-continue'); });
後々使う部分は変数にインスタンス化している。
オープニングにエフェクトを再定義するための設定:
var firstslide = slickbase.find('.slick-slide:nth-child(1)'); firstslide.removeClass('slick-active'); window.setTimeout(function(){ firstslide.addClass('slick-active'); }, 1);
これは当ブログお馴染みのsetTimeoutで。
ついでにページングの設定:
$('.slick-dots li button').on('click', function(e){ if( ! $(this).parent().hasClass('slick-active') ){ $('.slick-slide').removeClass('slick-active slick-continue'); }else{ // slick-activeになっている要素のページング押下を無効化 e.preventDefault(); } });
ページングのボタン押下時は、オープニングと同じ動作へ戻るようにする。
これでSlick側の設定を色々弄ったので、次に本題のズームのエフェクトをCSSでかけていく。
スタイルシート設定:
/* 効果をかけるためのスタイル設定 */ #slick-main .slick-slide{ /* slickによってelement.styleで付けられるopacityを無効化 */ opacity: 1 !important; } #slick-main .slideobject{ /* フェード要素の初期設定 */ opacity: 0; transition: 0s; } #slick-main .slick-active .slideobject{ /* slick-active(効果開始)の際のフェード設定 */ /* [重要]slick側の設定と同じdurationを取る */ opacity: 1; transition: ease 5.0s; } #slick-main .slick-continue .slideobject{ /* エフェクトの継続用に付与したslick-continueのフェード設定 */ /* [重要]slick側の設定と同じdurationを取る */ opacity: 0; transition: ease 5.0s; } #slick-main figure.image{ /* 画像 ズーム要素の初期設定 */ transform: scale(1.0, 1.0); transition: 0s; } #slick-main .slick-active figure.image{ /* 画像 slick-active(効果開始) */ /* ズームをかける要素には必ずフェードに対して大きめのtransition-durationを取る */ transform: scale(1.2, 1.2); transition: ease 10.0s; } #slick-main .slick-continue figure.image{ /* 画像 slick-continue */ /* ズームをかける要素には必ずフェードに対して大きめのtransition-durationを取る */ transform: scale(1.4, 1.4); transition: ease 10.0s; } #slick-main p.text{ /* テキスト 初期設定 */ opacity: 0; transition: 0s; } #slick-main .slick-active p.text{ /* テキスト slick-active(効果開始) この辺は自由にduration */ opacity: 1; transition: ease 1.0s; transition-delay: 1.0s; } #slick-main .slick-continue p.text{ /* テキスト slick-continue この辺は自由にduration */ opacity: 0; transition: ease 1.0s; }
動きをいじる場合、おおよそコメントアウトに記載している通りに適宜設定を変更すること。
スライドの配置とHTML設定:
<style type="text/css"> .slideobject{ padding-top: 50%; position: relative; z-index: 10; overflow: hidden; vertical-align: bottom; } .slideobject figure.image{ background-size: cover; background-repeat: no-repeat; background-position: 50% 50%; } .slideobject figure.image{ background-size: cover; background-repeat: no-repeat; background-position: 50% 50%; width: 100%; height: 100%; position: absolute; z-index: 10; top: 0; left: 0; right: 0; bottom: 0; } .slideobject figure.image{ background-size: cover; background-repeat: no-repeat; background-position: 50% 50%; width: 100%; height: 100%; position: absolute; z-index: 10; top: 0; left: 0; right: 0; bottom: 0; } .slideobject p.text{ font-size: 20px; line-height: 1.5em; width: 10.0em; height: 3.0em; margin: auto; position: absolute; z-index: 20; top: 0; left: 0; right: 0; bottom: 0; color: #fff; font-weight: bold; text-align: center; overflow: hidden; } </style> <div id="slick-main" class="slick"> <div class="slideobject"> <figure class="image" style="background-image: url(img1.jpg);"></figure> <p class="text">スライド1</p> </div> <div class="slideobject"> <figure class="image" style="background-image: url(img2.jpg);"></figure> <p class="text">スライド2</p> </div> <div class="slideobject"> <figure class="image" style="background-image: url(img3.jpg);"></figure> <p class="text">スライド3</p> </div> </div>
上記を全てまとめるとこんな感じになる。
<head> <script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.min.js"></script> <script type="text/javascript" src="slick.js"></script> <link href="slick.css" type="text/css" rel="stylesheet" /> <!-- 設定ここから --> <script type="text/javascript"> (function($){ $(window).load(function(){ var slickbase = $('#slick-main'); var timeline = 5000; // ズームさせるためのslick側の設定 slickbase.slick({ fade: true, // fadeモードで動作させる autoplay: true, // autoplayモードを有効化する autoplaySpeed: timeline, // [重要]画像の切り替えのタイミングをautoplaySpeedで取っておく speed: 0, // [重要]通常はスライド自体の速度をこのプロパティで設定するがゼロに設定する(タイミング制御はCSS側で設定するため) pauseOnHover: false, // 徐々にズームをさせるために一時停止は意味を持たないため無効化 pauseOnFocus: false, // 上に同じ swipe: false, // [重要]徐々にズームをかける都合上、組込側が想定している挙動にするため、スワイプは無効化 swipeToSlide: false, // 上に同じ slidesToShow: 1, // スライド数は1 slidesToScroll: 1, // スライドのスクロール数は1 dots: true, // ページングくらいは出したいのでdotはtrueで arrows: false, // ページャーも一応設定可能だが、今回は扱わない infinite: true, // 永続させる }).on('beforeChange', function(event, slick, currentSlide){ var slidebase = $(this).parents(); // slick-activeを抜けた際にエフェクトの継続をかけるために付与 slidebase.find('.slick-slide').removeClass('slick-continue'); slidebase.find('.slick-active').addClass('slick-continue'); }); // 以下は初回表示の際にスライドをスタートさせるための設定 var firstslide = slickbase.find('.slick-slide:nth-child(1)'); firstslide.removeClass('slick-active'); window.setTimeout(function(){ firstslide.addClass('slick-active'); }, 1); // slick-dotの設定 $('.slick-dots li button').on('click', function(e){ if( ! $(this).parent().hasClass('slick-active') ){ $('.slick-slide').removeClass('slick-active slick-continue'); }else{ // slick-activeになっている要素のページング押下を無効化 e.preventDefault(); } }); }); })(jQuery); </script> <style type="text/css"> /* 効果をかけるためのスタイル設定 */ #slick-main .slick-slide{ /* slickによってelement.styleで付けられるopacityを無効化 */ opacity: 1 !important; } #slick-main .slideobject{ /* フェード要素の初期設定 */ opacity: 0; transition: 0s; } #slick-main .slick-active .slideobject{ /* slick-active(効果開始)の際のフェード設定 */ /* [重要]slick側の設定と同じdurationを取る */ opacity: 1; transition: ease 5.0s; } #slick-main .slick-continue .slideobject{ /* エフェクトの継続用に付与したslick-continueのフェード設定 */ /* [重要]slick側の設定と同じdurationを取る */ opacity: 0; transition: ease 5.0s; } #slick-main figure.image{ /* 画像 ズーム要素の初期設定 */ transform: scale(1.0, 1.0); transition: 0s; } #slick-main .slick-active figure.image{ /* 画像 slick-active(効果開始) */ /* ズームをかける要素には必ずフェードに対して大きめのtransition-durationを取る */ transform: scale(1.2, 1.2); transition: ease 10.0s; } #slick-main .slick-continue figure.image{ /* 画像 slick-continue */ /* ズームをかける要素には必ずフェードに対して大きめのtransition-durationを取る */ transform: scale(1.4, 1.4); transition: ease 10.0s; } #slick-main p.text{ /* テキスト 初期設定 */ opacity: 0; transition: 0s; } #slick-main .slick-active p.text{ /* テキスト slick-active(効果開始) この辺は自由にduration */ opacity: 1; transition: ease 1.0s; transition-delay: 1.0s; } #slick-main .slick-continue p.text{ /* テキスト slick-continue この辺は自由にduration */ opacity: 0; transition: ease 1.0s; } /* スライド周りの基本スタイル */ .slideobject{ padding-top: 50%; position: relative; z-index: 10; overflow: hidden; vertical-align: bottom; } .slideobject figure.image{ background-size: cover; background-repeat: no-repeat; background-position: 50% 50%; } .slideobject figure.image{ background-size: cover; background-repeat: no-repeat; background-position: 50% 50%; width: 100%; height: 100%; position: absolute; z-index: 10; top: 0; left: 0; right: 0; bottom: 0; } .slideobject figure.image{ background-size: cover; background-repeat: no-repeat; background-position: 50% 50%; width: 100%; height: 100%; position: absolute; z-index: 10; top: 0; left: 0; right: 0; bottom: 0; } .slideobject p.text{ font-size: 20px; line-height: 1.5em; width: 10.0em; height: 3.0em; margin: auto; position: absolute; z-index: 20; top: 0; left: 0; right: 0; bottom: 0; color: #fff; font-weight: bold; text-align: center; overflow: hidden; } </style> <!-- 設定ここまで --> </head> <body> <div id="slick-main" class="slick"> <div class="slideobject"> <figure class="image" style="background-image: url(img1.jpg);"></figure> <p class="text">スライド1</p> </div> <div class="slideobject"> <figure class="image" style="background-image: url(img2.jpg);"></figure> <p class="text">スライド2</p> </div> <div class="slideobject"> <figure class="image" style="background-image: url(img3.jpg);"></figure> <p class="text">スライド3</p> </div> </div> </body>
動作サンプルはこちらのような形となる。
以上で、Slickを利用して画像が徐々にズームしていくエフェクトをかけるという要望と内容を満たした。
てかさ・・・
くっさ!めんどくっさ!
何故にこれだけの動作を付けるために、ここまで苦労しなければならないのか。
仕事だからだよ。
それ以上の以下でもねーや(´・ω・`)
しっかし・・・「他にもやってるサイトあるし」とか、無茶振りもいいところだよ・・・。
しかもその「やってるサイト」の動きから、後々要望が出てくるにつれて逸脱し、画像の上にそれぞれのテキスト載せたいだとか、ページングを付けたいだとか、ほとんど別物の動きになってしまった。
(これに関してはこのスクリプトを使うことになった大本のところが結構アレでソレだったりすることによるんだけど・・・。仕事とは別口で、そこに一度営業に行ったことがあるという自分と同じ地域に住んでいる友人からも似たような話を聞いたことがある。)
まぁ制作中はそれに加えて色々あって、スーパーの鮮魚コーナーに売られているサンマのような眼で常にげんなりとし、席から立てば某洋ドラのゾンビーのようにフラフラしていたが、それはまた別の話ってところで今回は〆ようと思う。
・・・さーて、またノースティリスの世界に戻って、せこせこダンジョンの住人集めでもするかぁ。