今回は音声ファイルを音量が徐々に上がっていく「フェードイン」する形で再生を開始、逆に音量が徐々に下がっていく「フェードアウト」し再生を終了することに挑戦してみます。
通常は以下のようにplay()
で開始するわけですが、これだと音量MAXで始まってしまうため唐突感が生まれてしまう場合があります。停止する際にpause()
を実行する場合も同様です。それぞれ「カットイン」「カットアウト」と呼ばれますが、意図したものであればもちろんこれで問題はありません。
<audio id="bgm" src="xxx.mp3" preload></audio> <script> document.querySelector("#bgm").play(): // 唐突に再生される(カットイン) document.querySelector("#bgm").pause(): // 唐突に止まる(カットアウト) </script>
SE(効果音)やジングルなんかはカットインがほとんどですが、しんみりした雰囲気をBGMで出したい場合はフェードイン/フェードアウトを多用することになりますね。
この方法はiPhoneやiPadなどiOSのWebブラウザでは利用できません。もし対応する場合にはWebAudioAPIを採用する必要があります。
基本的な原理
フェードイン/フェードアウトの原理は非常にシンプルでsetInterval()
で一定時間毎に音量を上げ下げし、一定のボリュームになったらタイマーを終了するだけです。
注意点としてはvolume
に0〜1以外の範囲の値を入れると実行時エラーとなるため代入前にチェックする必要があります。1.0001など小数点以下もエラーとなります。
//-------------------------------- // フェードイン //-------------------------------- const bgm = document.querySelector("#bgm"); bgm.volume = 0; //ボリュームを最低値にする bgm.play(); let timerid = setInterval( ()=>{ // ボリュームが1になったら終了 if( (bgm.volume + 0.1) >= 1 ){ bgm.volume = 1; clearInterval(timerid); //タイマー解除 } // 0.1ずつボリュームを足していく else{ bgm.volume += 0.1; } } , 200); //0.2秒ごとに繰り返す
//-------------------------------- // フェードアウト //-------------------------------- const bgm = document.querySelector("#bgm"); bgm.volume = 1; bgm.play(); let timerid = setInterval( ()=>{ // ボリュームが0になったら終了 if( (bgm.volume - 0.1) <= 0 ){ bgm.volume = 0; bgm.pause(); clearInterval(timerid); //タイマー解除 } // 0.1ずつボリュームを減らしていく else{ bgm.volume -= 0.1; } } , 200); //0.2秒ごとに繰り返す
ではもう少し汎用的なJavaScriptのクラスも用意しつつ、簡単なサンプルを作成してみます。
フェードイン・フェードアウトを実装する
実行例
以下のページから実際にサンプルをお試しいただけます。 miku3.net
- ボタンを押すと再生をフェードインしながら開始します。
- 再生中にボタンを押すとフェードアウトしながら停止します。
- フェードイン/フェードアウト中はボタンが「ぐるぐる」回るアイコンになり、この間は操作ができなくなります。
ボタンの上のアイコンは「Font Awesome」、音声ファイルは「魔王魂」さんからお借りしています。
ソース
HTML
実際のフェードイン/フェードアウトの処理は外部のJSで行っているため、HTMLは最低限の物になります。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>HTML5 Audio Sample3</title> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous"> <style> #btn-play{ width:100px; height:40px; padding:10px; font-size:18px; } </style> </head> <body> <h1>HTML5 Audio フェードイン/フェードアウト</h1> <!-- 音声ファイル --> <audio id="bgm1" preload> <source src="op.ogg" type="audio/ogg"> <source src="op.mp3" type="audio/mp3"> </audio> <!-- 再生ボタン --> <button id="btn-play" type="button"><i class="fas fa-play"></i></button> <!-- 外部のJSファイルで処理する --> <script src="audiofade.js"></script> <script src="app.js"></script> </body> </html>
JavaScript - app.js
メインの処理を書いています。
以前の記事で紹介したコードを流用しています。今回は後述するAudioFade
クラスを作成し実際の泥臭い処理はそちらで行っています。
ボタンがクリックされると現在の再生状態に合わせてフェードイン/フェードアウトを行っています。ポイントとしては同時に複数の処理が走ると無限ループに陥る可能性があるため簡単なロック機構を用意しています。
//------------------------------------------------ // 定数 //------------------------------------------------ const BUTTON_PLAY = '<i class="fas fa-play"></i>'; // 再生ボタン const BUTTON_PAUSE = '<i class="fas fa-pause"></i>'; // 停止ボタン const BUTTON_RUNNING = '<i class="fas fa-circle-notch fa-spin"></i>'; // 処理中ボタン /** * [イベント] ページの読み込み完了 */ window.onload = ()=>{ const bgm1 = document.querySelector("#bgm1"); // <audio> const btn = document.querySelector("#btn-play"); // <button> // フェードイン・アウト操作クラス const bgm1Ctrl = new AudioFade(bgm1); /** * [イベント] 再生ボタンをクリック */ btn.addEventListener("click", ()=>{ // 処理中であれば無視する if( bgm1Ctrl.isRun ){ return(false); } // フェード◯◯中はぐるぐるボタンにする btn.innerHTML = BUTTON_RUNNING; btn.setAttribute("disabled", true); //--------------------------- // フェードイン //--------------------------- if( bgm1Ctrl.paused ){ // pausedがtrue=>停止, false=>再生中 bgm1Ctrl.fadeIn(()=>{ // フェードイン完了時の処理 btn.innerHTML = BUTTON_PAUSE; btn.removeAttribute("disabled"); }); } //--------------------------- // フェードアウト //--------------------------- else{ bgm1Ctrl.fadeOut(()=>{ // フェードアウト完了時の処理 btn.innerHTML = BUTTON_PLAY; btn.removeAttribute("disabled"); }); } }); /** * [イベント] 再生終了時に実行 */ bgm1.addEventListener("ended", ()=>{ bgm1.currentTime = 0; // 再生位置を先頭に移動 bgm1.volume = 1; // ボリュームを既定値 btn.innerHTML = BUTTON_PLAY; // 「再生ボタン」に変更 }); }
JavaScript - audiofade.js
フェードイン/フェードアウトを行うJavaScriptのクラスです。基本的な利用方法はコンストラクタにaudioタグのオブジェクトを渡し、任意のタイミングでfadeIn()
またはfadeOut()
メソッドを実行するだけです。
/** * [HTML5 Audio] フェードアウト・フェードイン 簡易操作クラス * * @author M.katsube <katsubemakito@gmail.com> */ class AudioFade{ /** * コンストラクタ * * @param {Object} audio - <audio>タグのオブジェクト * @param {number} [maxvol=1] - 最大ボリューム * @param {number} [minvol=0] - 最低ボリューム * @param {number} [wait=200] - setInterval()の間隔 * @return {void} */ constructor(audio, maxvol=1, minvol=0, wait=200){ // iOSであれば終了する if( navigator.userAgent.match(/iPhone|iPad|iPod/) ){ throw {cd:"E001", message:"Does not work iOS(iPhone, iPad, iPod)", ua:navigator.userAgent}; } // 引数をプロパティに詰める this._audio = audio; this._maxvol = maxvol; this._minvol = minvol; this._wait = wait; // 何らかの処理がすでに実行中か this._isrun = false; } /** * Getter: 実行中フラグ * * @readonly * @memberof AudioFade */ get isRun(){ return( this._isrun ); } /** * Getter: 再生状態 * * @readonly * @memberof AudioFade */ get paused(){ return( this._audio.paused ); } /** * フェードイン * * @param {function} [callback=null] - フェードイン完了時に実行する関数 * @param {number} [sec=3] - 最大音量になるまでの秒数 * @return {void} * @public */ fadeIn(callback=null, sec=3){ const audio = this._audio; //----------------------------------- // 再生中 or すでに実行中ならやめる //----------------------------------- if( (! audio.paused) || (this._isrun === true) ) return(false); this._isrun = true; // 実行中フラグを立てる //----------------------------------- // 1回あたりに増やす音量を計算 //----------------------------------- const step = parseFloat( (this._maxvol / ((sec * 1000) / this._wait)).toFixed(3) ); //----------------------------------- // 最大音量までタイマーで繰り返す //----------------------------------- let timerid = setInterval(()=>{ // 停止状態であれば再生する if( audio.paused ){ audio.volume = 0; audio.play(); } else{ // 最大音量に到達したらタイマー停止 if( (audio.volume + step) >= this._maxvol ){ audio.volume = this._maxvol; // 最後の音量は決め打ち this._isrun = false; // 実行フラグを下ろす clearInterval(timerid); // タイマー解除 // 完了時のCallback関数を実行 if( callback !== null ){ callback(); } } else{ audio.volume += step; } } }, this._wait); } /** * フェードアウト * * @param {function} [callback=null] - フェードアウト完了時に実行する関数 * @param {number} [sec=3] - 最低音量になるまでの秒数 * @return {void} * @public */ fadeOut(callback=null, sec=3){ const audio = this._audio; //----------------------------------- // 停止中 or すでに実行中ならやめる //----------------------------------- if( audio.paused || (this._isrun === true) ) return(false); this._isrun = true; // 実行中フラグを立てる //----------------------------------- // 1回あたりに減らす音量を計算 //----------------------------------- const step = parseFloat( ( (audio.volume - this._minvol) / ((sec * 1000) / this._wait)).toFixed(3) ); //----------------------------------- // 最低音量までタイマーで繰り返す //----------------------------------- let timerid = setInterval(()=>{ // 最低音量に到達したらタイマー停止 if( (audio.volume - step) <= this._minvol ){ audio.volume = this._minvol; // 最後の音量は決め打ち audio.pause(); // 再生を停止 this._isrun = false; // 実行フラグを下ろす clearInterval(timerid); // タイマー解除 // 完了時のCallback関数を実行 if( callback !== null ){ callback(); } } else{ audio.volume -= step; } }, this._wait) } }