プロフィール

髭山髭人(ひげひと)

自分の書いた記事が、一人でも誰かの役に立てば...
活動信条の一つとして「貴方のメモは、誰かのヒント」というのがあります。

このサイトについて

本家HP packetroom.net から切り離した いわゆる技術メモ用のブログで、無料レンタルサーバーにて運用しています。広告表示はその義務なのでご容赦。
XREA さんには長年お世話になっています

jsで指定された秒数を掛けて長さを調節するcssアニメーション

概要

指定したpx幅または%幅まで、特定の時間を掛けて伸びる(または縮む)単純なcssアニメーション
...のようなものを頻繁に行う機会があったようで(?) シンプルめなクラスとしてこさえたかった。

コード

先に使い方のイメージから。
インスタンス化する時、cssアニメを仕込みたいセレクタを与えます

{
    // ※ css に記載されているであろう、
    // ゲージの基礎デザインを組んだセレクタ名と同じものを指定
    const _searchSelectorName = ".gauge"; // この要素名を対象
    const _gaugeManager = new GaugeWidthKeyframeAnimationManager( _searchSelectorName );
    // 紐づけする
    _gaugeManager.setBindNode( _gaugeNode );
    // 開始位置 , 終了位置 , アニメーション時間秒
    // (100px 幅から 250px まで、5秒かけて伸びる)
    _gaugeManager.startAnimation( "100px" , "250px" , 5);
}

なお、%指定も可
( ↓ 幅10% から 95% まで、5秒の時間を掛けて伸びる )

_gaugeManager.startAnimation( "10%" , "95%" , 5);

以下、先述の例で用いる単体のクラス。
これを使います

/**
 * 任意の幅で開始⇔終了する横幅ゲージ操作を意図したCSS-keyframesアニメーションを動的?にアレコレする雰囲気のクラス
 */
class GaugeWidthKeyframeAnimationManager{

    constructor(_selectorName){
        const _baseCss = GaugeWidthKeyframeAnimationManager._innerSccRuleSearchFromSelectorName(_selectorName);
        if(!_baseCss){
            throw "セレクタ文字列から、参照先のCSSを見つけられませんでした";
        }
        this._cssRules = _baseCss;
        this._targetNode;
        this._keyframeRule;
    }
    /**
     * セレクタ―名と合致するCSS定義を掘り出して内部に格納させておく。
     * このメソッドはクラス外から呼ぶ事を想定していない
     * @param {string} _selectorName 
     * @returns 
     */
    static _innerSccRuleSearchFromSelectorName(_selectorName){
        const _TargetSelectorName = _selectorName;
        for(let _s of document.styleSheets){
            const _rules = _s.cssRules;
            //console.log(_rules);
            for(let _r of _rules){
                //console.log(_r);
                // @ts-ignore
                const _selectorTxt = _r.selectorText;
                //console.log(_selectorTxt);
                if(_selectorTxt === _TargetSelectorName){
                    // ▼ CSSStyleRule または CSSKeyframesRule を返す
                    return _r;
                }
            }
        }
    };
    /**
     * 取り扱うノードをここで紐づける
     * @param {*} _node 
     */
    setBindNode(_node){
        this._targetNode = _node;
    }
    /**
     * 御膳立てのち、実際にアニメーション諸々を行う事項系メソッド
     * @param {string} _start 開始位置(単位付き必須)
     * @param {string} _end 終了位置(単位付き必須)
     * @param {string|number} _second 秒数
     */
    startAnimation(_start , _end , _second){
        // [1]. 単発の @keyframes を作成
        const _keyFrameRandName = "kf"+(new Date()).getTime(); // ほぼ一意の名前にしとこ...(適当) 先頭が数値だとNGなので、適当な接頭辞を付けた
        const _kfCssText = `@keyframes ${_keyFrameRandName}{0%{width:${_start};} 100%{width:${_end};}}`;
        this._keyframeRule = _kfCssText;

        // [2]. 既存のゲージターゲットデザインcss に [1] で作った物を呼び出す animation をセット
        //const _txt = "animation: 10s linear 0s normal forwards running gauge_width;"
        const _txt = `${_second}s linear 0s normal forwards running ${_keyFrameRandName}`;
        const _rules = this._cssRules;

        // [3]. 終了時にCSSを外すイベントを仕込んでおく
        this._targetNode.addEventListener("animationend" , (_e)=>{
            // 都度 @keyframes を document.styleSheets 内に積み上げ続ける感じになってしまうので、
            // アニメーションが終わった時点で生成時と同じ名前の @keyframes を削除する。
            // しかしただ削除しただけだと、横幅がリセットされてしまうので、.style で直接 width [n] へと書き換えてフィニッシュ
            //console.log(_e);
            if(_e.type === "animationend"){
                const _delCssName = _e.animationName;
                for(let _s of document.styleSheets){
                    const _rules = _s.cssRules;
                    for(let _c=0; _c < _rules.length; _c++){
                        const _r = _rules.item(_c);
                        // @ts-ignore
                        if(_r.name === _delCssName){
                            //console.log(`削除対象の@keyframes Index:${_c}`);
                            // 削除直前に、直接横幅を終了値で固定させる。
                            this._targetNode.style.width = `${_end}`;
                            _s.deleteRule(_c);  // 削除
                        }
                    }
                }
            }
        });

        // CSSを適用
        this._targetNode.style.animation = _txt;
        _rules.parentStyleSheet?.insertRule( _kfCssText);
    }
}

サンプル・動作確認コピペ用

下記コピペにて hoge.htmlmain.js の2ファイルをこさえたのち、
hoge.html を開いてボタンを押すと cssアニメーションが始まります。
複数パターン動くべ? という感じにしたかったので、その辺も別処理を仕込みました。

html側

html内部に .gauge という style を1つ放り込んでます

<!DOCTYPE html>
<html>
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 <style>
 html{
    height:250px;
    width:100%;
 }
 /*
  * ゲージ幅の基礎デザインCSS
  * 今回用意したクラスに渡した引数と合わせる
  * → そうする事で、このCSS情報がJS,クラス側で参照される
  * 横幅は後付けの @keyframes で与えられるので、このstyle自体では width を設定しない事
  * ( 固定したければ、このスタイルの親ノード側などで制限を掛ける運用が望ましい )
  */
 .gauge{
    border-bottom:2px solid #5050FF;
 }
 </style>
 <title>ゲージ幅 @keyframes 的なヤツ</title>
</head>
  <body>
   <div>
    <p>任意の開始位置→終了位置 で止まる 横幅操作系のCSSアニメーションを動的に作成適用させるクラスのサンプル<br>
    構成は html 内部に書かれた style と <a href="./main.js" target="_blank">main.js</a> をそれぞれご覧いただければと。</p>
   </div>
  </body>
 <script async src="main.js"></script>
</html>

js側

main.js として作成します。
クラス部分は上記と重複するので削りました。
( 最初に紹介した GaugeWidthKeyframeAnimationManager 単体クラスをまるっと、以下のコード上部に貼り付ければOK )


// 【!】 先に GaugeWidthKeyframeAnimationManager クラスをここへコピペで貼り付けておいてください

// =================== 実処理デモ ====================================
{
    // ただのサンプル用引数集合体。 % なのか px なのか、単位を忘れずに。
    const _sampleParams = [
        ["0%","25%",3] ,
        ["25%","50%",3] ,
        ["50%","75%",3] ,
        ["75%","100%",3] ,
        ["0%","100%",5] ,
        ["80%","100%",3] ,
        ["20%","30%",2] ,
        ["300px","400px",3] ,   // px
        ["100px","250px",3] ,   // px
    ];
    let _sampleIndex = 0;
    // サンプルボタン作成
    const _btn = document.createElement("Button");
    _btn.textContent = "デモ用ボタン";
    _btn.addEventListener("click",()=>{
        // ボタン押すたび引数サンプル用の配列を引っ張り出すだけ。
        if(_sampleIndex >= _sampleParams.length){
            _sampleIndex = 0;
        }
        const _param = _sampleParams[_sampleIndex];
        _sampleIndex++;

        const _gaugeNode = document.createElement("div");
        _gaugeNode.classList.add("gauge");
        const _txt = `${_param[0]} → ${_param[1]} を ${_param[2]} 秒で再現`;
        _gaugeNode.textContent = _txt;
        document.body.appendChild( _gaugeNode );

        // 使い方
        // =============================================================
        // ========== ▼ コア部分 =======================================
        {
            // ※ css に記載されているであろう、ゲージの基礎デザインを組んだセレクタ名と同じものを指定
            const _searchSelectorName = ".gauge"; 
            const _gaugeManager = new GaugeWidthKeyframeAnimationManager( _searchSelectorName );
            // 紐づけする
            _gaugeManager.setBindNode( _gaugeNode );
            // 開始位置 , 終了位置 , アニメーション時間秒
            _gaugeManager.startAnimation( _param[0] , _param[1] , _param[2]);
        }
        // ========== ▲ コア部分 =======================================
        // =============================================================

    });

    document.body.appendChild(_btn);

}