メディア掲載: レバテックフリーランス様のサイトで当ブログが紹介されました

【HTML/CSS/JS】ストリートファイターⅤ風のライフゲージ

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;

これが理解できない・・・のですが、実行中のアニメーションを強制的に止めて、最初から再実行するためにはこれが必要なようです・・・。

コメント

タイトルとURLをコピーしました