よしだです。
最近夢占いに凝っています。夢占いとは見た夢によって自らの運気や精神状態、暗示を占うといったものです。
驚いたことに怖い夢や悪い夢は意外にもプラスな暗示を示していることが多いようです。ちなみに私が最近見た夢はトラの赤ちゃん×2と戯れる夢でした。
ところで、昨今JavaScript標準APIが充実してきたことやjQueryを不要とするJavaScriptフレームワークの登場により脱jQueryの兆候が見受けられます。
本記事ではJavaScriptの標準APIを使用してタブメニューや画像スライダー等Webサイトでよく実装されているものをサンプル付きで10種ご紹介します。
目次
- タブメニュー
- アコーディオンパネル
- スムーズスクロール
- 画像ロールオーバー
- ハンバーガーメニュー
- モーダルウィンドウ
- 画像スライダー
- フェードイン・アウト
- パララックスアニメーション
- ローディングアイコン
※ES5で記述しております。
タブメニュー
任意のタブメニューをクリックすると、全てのタブメニュー・コンテンツを非アクティブにし、クリックされたタブメニューとタブメニューdata属性値と等しいid値を持つタブコンテンツをアクティブにします。
タブメニュー・コンテンツに対して、document.querySelectorAllで要素を取得しています。document.querySelectorAllで取得した要素は配列のようなオブジェクトを保持しているため、click等のイベントを要素に充てたい場合はforループ文を使って配列もどきのオブジェクトから値を取り出し、イベントを充てます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var tabMenus, tabContents; // document.querySelectorAllでマッチしたclass名を持つ要素を取得 tabMenus = document.querySelectorAll('.tab_menu_item_link'); tabContents = document.querySelectorAll('.tab_content'); // 取得した要素は配列のようなオブジェクトを保持しているため、 // 要素の数の分だけループ処理をして値を取り出す for (var i = 0; i < tabMenus.length; i++) { // タブメニュークリック時 tabMenus[i].addEventListener('click', function(e) { (処理) } } |
アコーディオンパネル
クリッカブルな要素を取得し、クリックしたら同じ親要素を持つ隣接した次の要素が表示されます。
同じ親要素を持つ隣接した次の要素はelement.nextElementSiblingで取得できます。
1 |
var acdBody = this.nextElementSibling; |
要素の高さを取得するにはscrollHeightプロパティを使用します。scrollHeightプロパティはpaddingを含む表示されていない要素の高さを取得します。あとはif文で条件分岐を行います。
1 2 3 4 5 6 |
if (acdBody.style.height) { acdBody.style.height = null; } else { // scrollHeightプロパティはスクロールできる分を含めた全体の高さを取得 acdBody.style.height = acdBody.scrollHeight + 'px'; } |
スムーズスクロール
「ページのトップに戻る」をクリックすると、ページの上端までアニメーションしながら移動します。
「ページのトップに戻る」リンクにはidを振ってdocument.getElementByIdで要素を取得します。
スクロール量の取得はFirefoxでも取得できるようにdocument.documentElement.scrollTopも記述します。
1 |
var scroll = document.body.scrollTop || document.documentElement.scrollTop; |
scrollTo(x, y)メソッドで指定したy座標(縦)位置にスクロールさせるのですが、取得したスクロール値を適当な数値で除算(割り算)した値をyにセットします。それをsetTimeoutメソッドでループさせることでアニメーションを実現しています。
たとえば1000pxスクロールした段階で、「ページのトップに戻る」をクリックすると5ミリ秒毎に1000→952.380→907.029→863.837と0に向かってスクロール量の値が小さくなり、ページの上端へと移動します。
1 2 3 4 5 6 7 8 9 10 |
function pageTop() { // スクロール量を取得 var scroll = document.body.scrollTop || document.documentElement.scrollTop; if(scroll) { // y座標へスクロール // scrollTo(x, y); scrollTo(0, scroll/=1.05); setTimeout(pageTop, 5); } } |
画像ロールオーバー
マウスオーバー・マウスアウトをする度にimg要素のsrc属性値を取得し、拡張子を除いたその値の接尾辞を切り替え、別画像を表示させます。最近はあまり見かけないような気がします。
class値にrollover_imgを持つimg要素を取得し、切り替え前(_off)・後(_on)の接尾辞を変数に格納します。
1 2 3 4 5 6 7 8 |
var rolloverImgs, suffix, suffixReplace; // マッチしたclass名を持つ要素を取得、接尾辞変数の定義 rolloverImgs = document.querySelectorAll('.rollover_img'); suffix = '_off'; suffixReplace = '_on'; |
値切り替えを操作するためにelement.getAttribute(‘src’)でimg要素のsrc属性を取得します。
要素へマウスオーバー・マウスアウトをする際、まずmatchメソッドで属性値の接尾辞を判定します。trueならばif内の処理を実行し、画像を切り替えます。画像の切り替えにはreplaceメソッドを使用します。
1 2 3 4 5 6 7 8 9 10 11 12 |
// マウスホバー時「_on」に切り替える rolloverImgs[i].onmouseover = function() { if (this.getAttribute('src').match(suffix)) { this.setAttribute('src', this.getAttribute('src').replace(suffix, suffixReplace)); } } // マウスアウト時「_off」に切り替える rolloverImgs[i].onmouseout = function() { if (this.getAttribute('src').match(suffixReplace)) { this.setAttribute('src', this.getAttribute('src').replace(suffixReplace, suffix)); } } |
ハンバーガーメニュー
CSSであらかじめ特定クラスが追加された状態のスタイルを設定しておき、クリックした要素に特定クラスがあるか否かで処理を切り替えます。
アクティブ・非アクティブの切り替え判定にはelement.classListが便利です。こちらは要素のクラスのリスト(文字列)を返してくれ、以下のようなメソッドを使用することができます。
・contains - 要素に特定のクラスがあるかどうか調べる。jQueryだとhasClass
・add - クラスリストにクラスを追加する。jQueryだとaddClass
・remove - クラスリストの特定のクラスを削除する。jQueryだとremoveClass
・toggle - クラスリスト中の特定クラスの切り替える。jQueryだとtoggleClass
1 2 3 4 5 6 7 8 9 10 11 |
ham.addEventListener('click', function() { for (var i = 0; i < hamItems.length; i++) { if (hamItems[i].classList.contains('is-active')) { hamItems[i].classList.remove('is-active'); } else { hamItems[i].classList.add('is-active'); } // 上記if文を削除し、下記コードをアクティブにしても実行可能 // hamItems[i].classList.toggle('is-active'); } }); |
if文を削除し、hamItem[i].classList.toggle(‘is-active’);と記述しても機能します。こちらの方が短い記述で済みます。
モーダルウィンドウ
モーダルコンテンツやオーバーレイはJavaScriptで追加し、フェードアニメーションはopacityの調整と時間差の要素削除で表現しています。
要素の生成をdocument.createElementで行い、setAttributeで属性や値を設定します。それからelement.append(childElement)で階層を整えます。
展開されたモーダルの背景オーバーレイをクリックした時にそれらは削除するためにはbind()メソッドでthisを制御しなければなりません。bind()メソッドで制御しなければ、setTimeoutメソッド内のthisはグローバルオブジェクトのwindowプロパティを示すため、要素削除ができません。
1 2 3 4 5 6 7 |
// オーバーレイクリック時オーバーレイを削除 overlay.addEventListener('click', function() { this.style.opacity = '0'; setTimeout(function() { this.remove(); }.bind(this), 300); }); |
画像スライダー
左右のボタンクリックで画像がスライドします。
スライド要素、表示幅、ボタン、ナンバー、現在地は変数に格納します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var slideItems, slideItemWidth, slidePrev, slideNext, slideItemNum, slideCurrent; // マッチしたid・class名を持つ要素を取得、変数の定義 slideItems = document.querySelectorAll('.slide_list_item'); slideItemWidth = document.querySelectorAll('.slide_list_item')[0].clientWidth; slidePrev = document.getElementById('slide_arw-prev'); slideNext = document.getElementById('slide_arw-next'); slideItemNum = 0; slideCurrent = 0; |
下記でスライド表示位置やスライドの初期表示を設定します。
1 2 3 4 5 6 7 |
for (var i = 0; i < slideItems.length; i++) { slideItems[i].style.left = slideItemWidth + 'px'; if (slideItemNum === slideCurrent) { slideItems[i].style.left = 0 + 'px'; } slideItemNum++; } |
slide関数の中身を見てみましょう。
現在のスライドの値と次のスライドの値を比較し、positionで調整します。
1 2 3 4 5 6 |
var pos; if (slideCurrent < next) { pos = -slideItemWidth; } else { pos = slideItemWidth; } |
次のスライドの位置を現在のスライド位置の進行方向後ろに移動させ、setTimeout()メソッドを使って調整しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
for (var i = 0; i < slideItems.length; i++) { slideItems[i].style.transition = 'initial'; } // 次のスライドを現在のスライドの進行方向後ろに移動させる slideItems[next].style.left = -pos + 'px'; setTimeout(function() { slideItems[next].style.transition = 'all 0.5s'; slideItems[next].style.left = 0 + 'px'; }, 30); slideItems[slideCurrent].style.transition = 'all 0.5s'; slideItems[slideCurrent].style.left = pos + 'px'; slideCurrent = next; |
フェードイン・アウト
クリックしたボタンに応じてコンテンツがフェードイン・フェードアウトします。アニメーションはJavaScriptのスタイルで設定しています。
パララックスアニメーション
スクロール量がボールの位置に達するとボールが左から右に転がっていきます。
ボール要素の座標の取得方法ですが、element.getBoundingClientRect()で要素の絶対座標を取得し、スクロール量と要素の上端を足したものを最終的に設定しています。
1 2 3 4 5 |
// 要素の絶対座標を取得 offset = balls[i].getBoundingClientRect(); // スクロール量を合わせた要素の絶対座標を取得 animPos = offset.top + window.pageYOffset; |
ウィンドウの中央より少し上でアニメーションさせたかったので、ボール要素の座標位置を1.3で除算して調整しています。
1 2 3 4 5 6 7 |
if (scroll > animPos / 1.3) { balls[i].style.transition = 'all 1s'; balls[i].style.transform = 'rotate(' + 1080 + 'deg)'; balls[i].style.left = 400 + 'px'; } else { return false; } |
ローディングアイコン
HTML5 APIのcanvasで実装しています。
以下コードではcanvas要素の取得、描画機能の有効化を行っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var canvas, ctx; // canvas要素の取得 canvas = document.getElementById('loading'); // ブラウザがcanvasに対応していなければ実行しない。また、<canvas>タグ内のテキストを表示 if (!canvas || !canvas.getContext) { return false; } // canvasに描画するためのAPIにアクセス ctx = canvas.getContext('2d'); |
beginPath()でパスのリセットを行い、fillstyleで塗りつぶす色を指定、arc()で円を描画し、fill()で塗りつぶしています。小さな黒い円ができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function draw() { var pos = 30; var r = 4; // 描画状態を保存 ctx.save(); // 位置 ctx.translate(w / 2, h / 2); // 円を描画 ctx.beginPath(); ctx.fillStyle = '#000000'; ctx.arc(pos * Math.cos(Math.PI / 180 * radian), pos * Math.sin(Math.PI / 180 * radian), r, 0, Math.PI * 2, false); ctx.fill(); // save()で保存した描画情報を復元 ctx.restore(); } |
以下は肝となるarc()メソッドの構文です。
1 |
context.arc(x, y, radius, startAngle, endAngle, anticlockwise) |
・x,yは円の中心位置、ラジアンで表す
・radiusは円の半径
・startAngleは円弧を描き始める角度、ラジアンで表す
・endAngleは円弧を描き終える角度、ラジアンで表す
・anticlockwiseの引数はtrueまたはfalse。時計回りに描画したい場合はfalse、反時計回りに描画したい場合はtrue。
ラジアンとは角度の単位です。ラジアンは「Math.PI / 180 * 角度」で表すことができます。ラジアンについては下記を参照すると理解が深まります。
■arc(x, y, radius, startAngle, endAngle [, anticlockwise ] )-Canvasリファレンス
http://www.htmq.com/canvas/arc.shtml
x,y位置の指定はラジアンを使用し、それぞれMath.cos()とMath.sin()で指定することができます。変数posは中心位置からの距離を表しており、値が大きければ大きいほど円運動が大きくなります。
円弧の角度ですが、startAngleには0(度)を設定し、endAngleにはMath.PI * 2(360度)を設定しています。0度は3時の位置です。
続いてアニメーション部分ですが、角度を表す変数radianの値をsetTimeoutで増加させることでアニメーションさせています。また、透明度50%の白色でcanvasサイズ分を塗りつぶすことでうっすら軌跡を残しながらアニメーションするローディングアイコンができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// アニメーション関数 function update() { ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'; // 上記スタイルを使用して塗りつぶす ctx.fillRect(0, 0, w, h); draw(); radian += 24; setTimeout(function() { update(); }, 50); } update(); |
おわりに
サンプル作成は以下を参考にしました。
You Don’t Need jQuery(日本語版)
https://github.com/oneuijs/You-Dont-Need-jQuery/blob/master/README-ja.md#%E3%82%BB%E3%83%AC%E3%82%AF%E3%82%BF
w3schools.com
https://www.w3schools.com/js/default.asp
ほぼjQueryでの実装で済ましていた身としては、今回の記事を通してかなり勉強になりました。スライダーはかなり力技でしたね。
JavaScriptの標準APIはかなり充実してきております。
現在、まだ草案段階のWeb Animations APIはCSS3のkeyframesアニメーションのように記述できるため、JavaScriptでのアニメーション表現の記述が比較的簡易になり、自由度も高くなりそうです。
将来、勧告されることになればjQueryやプラグインの類に取って代わるのではないかと考えています。
Web Animations APIに興味のある方は是非下記を参考にしてみてください。
■Web Animations APIの基本的な使い方・まとめ
http://defghi1977.html.xdomain.jp/tech/webanim/webanim.htm
■「動き」はどの技術で実装すべきか? Web Animations APIの登場で常識が変わる
https://www.webprofessional.jp/bringing-pages-to-life-web-animations-api/