ichi3270です。
以前、HTML / CSS / JSでシンプルなゲージを作る記事を書きました。
需要はほとんど無いのですが、自分としては、結構楽しかったです。
今回は、もうちょっと複雑な物をつくってみよう・・・ということで、ストリートファイターⅤ風のライフゲージを作ってみました。
完成したゲージ
ストリートファイターⅤ風 ライフゲージ
*攻撃ボタン、毒ボタンも動作するので、是非押してみてください。
ソースコード
かなり雑で申し訳ないのですが・・・誰かの参考になれば幸いです。
HTML
<div id="gauge-container"> <div id="background"> <div id="background-top"></div> <div id="background-bottom"></div> <div id="back-gauge" class="full"></div> <div id="front-gauge" class="full"></div> <div id="frame"> <div id="triangle-tl"></div> <div id="triangle-tr"></div> <div id="triangle-bl"></div> <div id="triangle-br"></div> </div> </div> </div> <div id="btns"> <button onclick="attack(5)">攻撃</button> <button onclick="poison()">毒</button> </div>
CSS
#gauge-container * { box-sizing: border-box; } #gauge-container { width: 100%; height: 30px; background: black; display: flex; justify-content: center; align-items: center; } #background { position: relative; z-index: 0; width: calc(100% - 4px); height: calc(100% - 4px); display: flex; align-items: center; } #background-top,#background-bottom { position: absolute; z-index: 1; width: 100%; height: 50%; } #background-top { top: 0%; background: linear-gradient(to right,rgb(45,45,45),rgb(60,60,60)); } #background-bottom { bottom: 0%; background: linear-gradient(to right,rgb(35,35,35),rgb(20,20,20)); } #back-gauge,#front-gauge { position: absolute; z-index: 2; width: 100%; height: 60%; } #back-gauge { background: rgb(160, 15, 15); border-top: solid rgb(240, 30, 10) 3px; border-bottom: solid rgb(240, 30, 10) 3px; opacity: 0; } #back-gauge.full { background: repeating-linear-gradient(45deg, rgb(200, 160, 70) 0%, rgb(250, 250, 180) 10%, rgb(200, 160, 70) 35%); border-color: rgb(220, 220, 120); opacity: 1; } .fadeout { animation: fadeOut 2s forwards; } @keyframes fadeOut { 0% { opacity: 0.9 } 50% { opacity: 0.9 } 100% { opacity: 0 } } #front-gauge { border-style: solid; border-width: 3px 0; } #front-gauge.full { background: linear-gradient(45deg, transparent 0% 5%, rgb(255, 255, 200) 7% 9%, transparent 11% 12%, rgb(255, 255, 200) 14%, transparent 16% ); background-size: 200% 200%; border: none; animation: flow 5s infinite ease; } @keyframes flow { 0% { opacity: 0; background-position: -60%; } 40% { background-position: -60%; opacity: 1; } 80% { background-position: -10%; opacity: 0.4; } 100% { background-position: -10%; opacity: 0; } } .normal { background: linear-gradient(to left, rgba(36, 206, 87, 0.8), rgba(189, 224, 61, 0.8)); border-color: lightgreen; } .poisoned { animation: poisoned 1s infinite alternate ease-out; } @keyframes poisoned { 0% { background-color: rgb(80,40,140); border-top: solid rgb(100,60,160); border-bottom: solid rgb(100,60,160); } 100% { background-color: rgb(200,170,230); border-top: solid rgb(220,190,250); border-bottom: solid rgb(220,190,250); } } #frame { position: absolute; z-index: 3; width: 100%; height: 100%; border-top: solid 2px darkgray; border-bottom: solid 2px darkgray; border-left: solid 3px white; border-right: solid 3px white; } #triangle-tl { position: absolute; top: -2px; left: -3px; width: 0; height: 0; border-top: 7px solid white; border-right: 7px solid transparent; } #triangle-tr { position: absolute; top: -2px; right: -3px; width: 0; height: 0; border-top: 7px solid white; border-left: 7px solid transparent; } #triangle-bl { position: absolute; bottom: -2px; left: -3px; width: 0; height: 0; border-bottom: 7px solid white; border-right: 7px solid transparent; } #triangle-br { position: absolute; bottom: -2px; right: -3px; width: 0; height: 0; border-bottom: 7px solid white; border-left: 7px solid transparent; } #btns { display: flex; justify-content: left; margin-top: 10px; } #btns button { width: 5em; margin-right: 1em; }
JavaScript
const frontGauge = document.getElementById("front-gauge"); const backGauge = document.getElementById("back-gauge"); const comboLimit = 1000; let life = 100; let backGaugeIsAnimation = false; let gaugeMode = "full"; let damagedTime = new Date().getTime(); // fadeoutアニメーション開始時 backGauge.addEventListener("animationstart", () => { backGaugeIsAnimation = true; }); // fadeoutアニメーション終了時 backGauge.addEventListener("animationend", () => { backGauge.style.width = life + "%"; backGauge.classList.remove("fadeout"); backGaugeIsAnimation = false; }); // *** 攻撃ボタン *** // function attack(damage){ // ゲージ状態更新 if ( gaugeMode === "full" ){ frontGauge.classList.remove("full"); backGauge.classList.remove("full"); frontGauge.classList.add("normal"); gaugeMode = "normal"; } // コンボ判定 const currentTime = new Date().getTime(); if ( currentTime - damagedTime > comboLimit ){ backGauge.style.width = life + "%"; } // ダメージ処理 life < damage ? life = 0 : life -= damage; frontGauge.style.width = life + "%"; damagedTime = new Date().getTime(); // 進行中のfadeoutアニメーションを終了 if ( backGaugeIsAnimation ){ backGauge.classList.remove("fadeout"); void backGauge.offsetWidth; } // fadeoutアニメーション開始 backGauge.classList.add("fadeout"); } // *** 毒ボタン *** // function poison(){ // ゲージ状態更新 if (gaugeMode === "full"){ frontGauge.classList.remove("full"); backGauge.classList.remove("full"); } else if ( gaugeMode === "normal" ){ frontGauge.classList.remove("normal"); } gaugeMode = "poison"; frontGauge.classList.add("poisoned"); // ライフ減少 const intervalId = setInterval(() => { life < 0.1 ? life = 0 : life -= 0.1; frontGauge.style.width = life + "%"; }, 100); // 毒状態解除 setTimeout(() => { clearInterval(intervalId); frontGauge.classList.remove("poisoned"); frontGauge.classList.add("normal"); gaugeMode = "normal"; }, 10000);
まとめ
今回は、あまり経験がなかったグラデーションやアニメーションにチャレンジできました。ノーダメージ状態(金色)時のアニメーションなんかは結構苦労しました・・・が、正直あまり理解できておらず。
それ以上に、特に・・・
void backGauge.offsetWidth;
これが理解できない・・・のですが、実行中のアニメーションを強制的に止めて、最初から再実行するためにはこれが必要なようです・・・。
コメント