JavaScriptアニメーションAPI

42448 ワード

私は最近、フロントエンドの開発者のポジションのために会社に申し込みました、そして、私は技術的なインタビューの前に課題を完了するよう頼まれました.このポストは、私がどのように私がたどった課題とプロセスを完了したかについてです.
まず第一に、割り当てはランダムにいくつかのボックスの高さをアニメーション化し、起動し、各ボックスのアニメーションを停止する機能を追加しました.


始めましょう
私は割り当ての基本的な構造を作成し始めた.私はdiv コンテナとして5つ追加div '容器の中のs.コンテナを作成した後、私はそれらを中心に、アニメーションを開始し、停止する2つのボタンを追加し、かなり基本的なもの.HTMLの構造は次のようになります.
      <div class="box-container">
        <div class="box" id="box-1"></div>
        <div class="box" id="box-2"></div>
        <div class="box" id="box-3"></div>
        <div class="box" id="box-4"></div>
        <div class="box" id="box-5"></div>
      </div>

      <div class="actions">
        <button class="action-btn" id="start-btn">Start</button>
        <button class="action-btn" id="stop-btn">Stop</button>
      </div>
今ではコンテナとボックスにスタイルを追加する時間です.コンテナdiv は、Flexプロパティを本体に追加することによって中心となります.
body {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 2rem;

    width: 100vw;
    height: 100vh;
}
ボックスdiv sスタイルは、かなり自明です.言及する価値がある一つのことがあります.強調表示線.box クラスのスタイル.
.box-container {
    display: flex;
    width: 80%;
    height: 216px;
    background: black;
    border: 8px solid black;
}

.box {
    align-self: flex-end; /* this line */
    flex-grow: 1;
    height: 200px;
    min-width: 50px;
}
その行がなければ、アニメーションは次のようになります.

ボタンとボックスの参照を取得し、EventListenersをボックスに追加しましょう.
window.addEventListener("load", () => {
    console.log("Loaded");

    const boxes = [...document.querySelectorAll(".box")];

    const startBtn = document.getElementById("start-btn");
    const stopBtn = document.getElementById("stop-btn");

    startBtn.addEventListener("click", () => {

    });

    stopBtn.addEventListener("click", () => {

    });
}
最初に、私は.animation-active 以下のクラス
/*animation: name duration timing-function delay iteration-count direction fill-mode play-state;*/
.animation-active {
    animation: toggle-height 700ms infinite;
}

@keyframes toggle-height {

  0% {
    height: 10%;  
  }

  100% {
     height: 110px;
  }
}
私は、加えることを試みましたanimation-active スタートボタンがクリックされ、停止ボタンがクリックされると削除されます.しかし、あなたは、割り当てがランダムに箱の高さをトグルする必要があるのを見ることができますanimation-active クラスの高さを変更するための固定値10% to 110px .
そこで、JavaScriptアニメーションAPIを使うことにしました.アニメーションAPI JSのためのクイック検索でWeb Animations API - MDN 上へ.APIを使う方法を探している間、私はこの関連に遭遇しましたUsing the Web Animations API . ここでは、CSSからJavaScriptを使用してアニメーションを移動する方法を見ることができますanimate HTML要素で使用できるプロパティ.で説明したアニメーションを変換するにはanimation-active クラスは次のようにします.
function animateElement(element) {
    boxes.forEach(box => {

        box.animate(
            [
                { height: "10%" },
                { height: "110%" }
            ],{
                duration: 700,
                easing: "linear:
            }
        );
    });
}
今、我々はプログラムの高さの値の値を変更することができますテンプレートリテラルを使用してanimate 関数は以下のようになります:
function animateElement(element) {
    boxes.forEach(box => {

        box.animate(
            [
                { height: "10%" },
                { height: `${Math.floor(Math.random() * 100) + 1}%` }
            ],{
                duration: 700,
                easing: "linear:
            }
        );
    });
}
偉大な、今我々はそれぞれのアニメーション機能を呼び出すことができますbox 要素には、ランダムな高さが設定されますbox アニメーションが完了したら.私たちのアニメーション機能を少し調整しましょうanimation object アニメーションを設定することで.
function animate(box) {

    const end = `${Math.floor(Math.random() * 100) + 1}%`;

    return box.animate(
        [
            {
                height: "10%"
            },
            {
                height: end
            }
        ], {
            duration: 700,
            easing: "ease-in"
        }
    );
}
アニメーションを開始するためのeventhandler関数を作成しましょうhandleStart . 割り当ての要件は、停止がクリックされるまでボックスの高さをアニメーション化することです.これらの要件を念頭に置いて、4つのものを設定する必要があります.
  • handleStart 関数.
  • アニメーションが再生中か停止かを表すフラグ.
  • 上記のフラグを使用する高さを連続的にアニメーション化するループ.
  • ループの各反復の後にクリアされるすべてのアニメーションを格納する配列
  • // flag
    let stopAnimation = true;
    
    // function
    function handleStart() {
    
        while (!stopAnimation) {
    
            // array to store animations on each box
            let animations = [];
    
            // do something
        }
    
    }
    
    我々とhandleStart 関数は現在、各ボックスにアニメーションを追加するforeachループとanimate 関数.
    function handleStart() {
    
        while (!stopAnimation) {
            // array to store animations on each box
            let animations = [];
    
            boxes.forEach(box => {
                animations.push({
                    animate: animate(box)
                });
            })
        }
    }
    
    全体的なスクリプトは次のようになります.
    window.addEventListener("load", () => {
        console.log("Loaded");
    
        const boxes = [...document.querySelectorAll(".box")];
    
        const startBtn = document.getElementById("start-btn");
        const stopBtn = document.getElementById("stop-btn");
    
        let stopAnimation = true;
    
        function handleStart() {
    
            while (!stopAnimation) {
    
                // array to store animations on each box
                let animations = [];
    
                boxes.forEach(box => {
                    animations.push({
                        animate: animate(box)
                    });
                })
            }
        }
    }
    
    function animate(box) {
    
        const end = `${Math.floor(Math.random() * 100) + 1}%`;
    
        return box.animate(
            [
                {
                    height: "10%"
                },
                {
                    height: end
                }
            ], {
                duration: 700,
                easing: "ease-in"
            }
        );
    }
    
    スタイルとスクリプトをHTMLページにフックし、ページを開きます.使用中Live Server ローカルサーバーとしてvscodeに.ボタンをクリックしてhandleStart 関数が正しく動作している.あなたは何もスタートボタンをクリックした後に起こっていることに気づくことができます.それが反応して、それを再開しないかもしれないページを閉じてください.開放するdebugger ブラウザで、この行をポイントします.

    スタートボタンをクリックし、次の関数呼び出しのステップを押すと、各ボックスにアニメーションを見ることができるボックスを見て1つずつ実行されます.しかし、我々がスタートボタンをクリックするとき、何も起こりません、そして、ページは反応していないようです.それは私たちは、アニメーションを終了し、別のアニメーションを開始するのを待っていないため、おそらく、ページを有効にすると、(各アニメーション間の700 msの期間のギャップがあるように).または少なくとも、それは私が考えたものです.それで、私は次のものを始める前に各々のアニメーションのために待ち時間を終えることに決めました.どのように、我々は各々のアニメーションの間で待ちますか?幸運にも、1つの箱の上でアニメーションのインターフェースで実験している間、何かのものをロギングしている間、私はfinished 返り値animate() メソッドElement インターフェイス.

    ご覧の通り、上記のイメージでfinished プロパティはPromise それでAnimation オブジェクト.アニメーションが終了した後にボックスの高さを保持するためにアニメーションが終了した後、ボックスの高さを設定しながら、このオブジェクトは、後で便利になります.今のところ、ループanimations 配列.現在我々handleStart 関数は以下のようになります:
    async function handleStart() {
    
        while (!stopAnimation) {
    
            // array to store animations on each box
            let animations = [];
    
            boxes.forEach(box => {
                animations.push({
                    animate: animate(box)
                });
            })
    
            for(let i = 0; i < animations.length; i++) {
                const eachAnimation = animations[i];
    
                const animationObj = await eachAnimation.animate.finished;
            }
        }
    }
    
    我々が使用していない通知forEach ループを繰り返すanimations 配列Promise doesn't work in forEach loop .
    ページをもう一度開き、スタートボタンをクリックします.アニメーションが動作します.それで、この線const animation by = await eachAnimation.animate.finished; やっていることは、各アニメーションを終了するのを待っていることです.各アニメーションが終了したら、コントロールは外側のループに行きます.アニメーションは次のようになります.

    我々は長い道のりを来ている、アニメーションが動作しているページは、しばらくのループで野生の実行アニメーションのためにクラッシュしていません.残っているのは、アニメーションが終了する高さにボックスの高さを設定することです.置くべき時間animationObj 使う.すばやくconsole.log the animationObj , 注意事項effect プロパティ.The effect プロパティはKeyframeEffect を持つオブジェクトtarget アニメーションが再生を終えた要素を指すプロパティ.我々は、使用するつもりですtarget 要素を取得するプロパティ.アニメーションが終了した要素の高さを抽出する関数を設定しましょう.アニメーション終了後、元の高さに戻るので、プログラムの高さを設定する必要があります.関数名を指定しますsetElementHeightAfterAnimation .
    function setElementHeightAfterAnimation(animation) {
    
        // get target element whose animation ended
        const srcElement = animation.effect.target;
    }
    
    newBoxHeight アニメーションが終了した高さ値を保持する変数です.高さを得るために、もう一度見ましょうanimationObj 's効果プロパティanimationObjgetKeyframes() 方法.このメソッドは、animate メソッド.二つのキーフレームを設定しているのでgetKeyframes() メソッドは2つのキーフレームごとに2つのオブジェクトの配列を返します.

    次に何をするつもりか推測できます.そうですね.アニメーションが終了した高さをgetKeyframes() メソッド.
    function setElementHeightAfterAnimation(animation) {
    
        // get target element whose animation ended
        const srcElement = animation.effect.target;
    
        // get height at which the animation ended
        // keyframes returned contains the value at index 1 where 
        // animation ended
        const newBoxHeight = animation.effect.getKeyframes()[1].height;
    
        // set the height of the element equal to newBoxHeight
        // so that the animation should start from new height
        srcElement.style.height = newBoxHeight;
    }
    
    呼び出しを追加した後setElementHeightAfterAnimation forループからボックスの高さを設定します.今残っているのはEventHandler ボタンを開始し、停止します.
    window.addEventListener("load", () => {
        .
        .
        .
        .
        // function
        async function handleStart() {
    
            while (!stopAnimation) {
                .
                .
                .
                .
    
                for(let i = 0; i < animations.length; i++) {
                    const eachAnimation = animations[i];
    
                    const animationObj = await eachAnimation.animate.finished;
    
                    // add this line
                    setElementHeightAfterAnimation(animationObj);
                }
            }
        }
    
        startBtn.addEventListener("click", () => {
            stopAnimation = false;
    
            handleStart();
        });
    
        stopBtn.addEventListener("click", () => {
            stopAnimation = true;
        });
    }
    
    それです.現在、我々の箱はスタートボタンがクリックされて、停止ボタンがクリックされる高さで止まる間、ランダムにアニメーション化されます.完全なスクリプトは次のようになります.
    window.addEventListener("load", () => {
        console.log("Loaded");
    
        const boxes = [...document.querySelectorAll(".box")];
    
        const startBtn = document.getElementById("start-btn");
        const stopBtn = document.getElementById("stop-btn");
    
        let stopAnimation = true;
    
        // function
        async function handleStart() {
    
            while (!stopAnimation) {
                // array to store animations on each box
                // resets after each iteration
                let animations = [];
    
                boxes.forEach(box => {
                    animations.push({
                        animate: animate(box)
                    });
                })
    
                for(let i = 0; i < animations.length; i++) {
                    const eachAnimation = animations[i];
    
                    const animationObj = await eachAnimation.animate.finished;
    
                    setElementHeightAfterAnimation(animationObj);
                }
            }
        }
    
        startBtn.addEventListener("click", () => {
            stopAnimation = false;
    
            handleStart();
        });
    
        stopBtn.addEventListener("click", () => {
            stopAnimation = true;
        });
    });
    
    function animate(box) {
    
        // since the height of each box is 200px
        // dividing the offsetHeight by 2(200/2) to 
        // convert the box's height to percentage
        const start = `${box.offsetHeight/2}%`;
    
        const end = `${Math.floor(Math.random() * 100) + 1}%`;
    
        return box.animate(
            [
                {
                    height: start
                },
                {
                    height: end
                }
            ], {
                duration: 700,
                easing: "ease-in"
            }
        );
    }
    
    function setElementHeightAfterAnimation(animation) {
    
        // get target element whose animation ended
        const srcElement = animation.effect.target;
    
        // get height at which the animation ended
        // keyframes returned contains the value at index 1 where 
        // animation ended
        const newBoxHeight = animation.effect.getKeyframes()[1].height;
    
        // set the height of the element equal to newBoxHeight
        // so that the animation should start from new height
        srcElement.style.height = newBoxHeight;
    }
    
    それは私の最初のポストですので、親切にしてください.もっと簡単にできることがあるかもしれません.または多分この全体のことが異なって行われている可能性があります.あなたの提案について教えてください.
    コンプリートコードGist
    使用例:Codepen