質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.21%

JavaScriptのアニメーションを実行したい

受付中

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 294

前提・実現したいこと

JavaScriptでスクロールアニメーションを作っています。
(A)スクロールしたらcontentsがふわっと出てくるアニメーションと、
(B)contentsの中の文字が順番に出てくるアニメーションを作りたいのですが、
(A)は一回アニメーションが発生したら終わるのですが、(B)はスクロールをするたびにアニメーションが発生してしまいます。
僕としては(B)も一回だけアニメーションが発生するように実装したいです。

発生している問題・エラーメッセージ

(B)がスクロールするたびにアニメーションが発生してしまう。

該当のソースコード

// こちらが(A)のアニメーションです。

const $contentAnimation = document.querySelectorAll('.js-animation');

document.addEventListener('scroll',()=>{
for (let i = 0; i < $contentAnimation.length; i++){
const $getElementDistance = $contentAnimation[i].getBoundingClientRect().top
+ $contentAnimation[i].clientHeight * .6;

if (window.innerHeight > $getElementDistance){
$contentAnimation[i].classList.add('show')
ScrollAnimation();
}
}
});

// こちらが(B)のアニメーションです。

const $skillsAnimationElement = document.querySelectorAll('.skillsAnimation');

function ScrollAnimation(){
for (let s = 0; s < $skillsAnimationElement.length; s++){
const targetSkills = $skillsAnimationElement[s],
skillsTexts = targetSkills.textContent,
skillsTextsArray = [];

targetSkills.textContent = '';

for (let j = 0; j < skillsTexts.split('').length; j++){
let s = skillsTexts.split('')[j];
if (s === '' ) {
skillsTextsArray.push('');
}else{
skillsTextsArray.push('<span><span style="animation-delay: ' + ((j * .5) + .3) + 's;">' + s + '</span></span>')
}
}

for (let k =0; k < skillsTextsArray.length; k++){
targetSkills.innerHTML += skillsTextsArray[k];
}

}
}

試したこと

(B)で作った関数(ScrollAnimation())を(A)のif文の画面の幅の方が大きくなったときに、呼び出しています。
これだとスクロールするたびに(B)が実行されてしまいます。

補足情報(FW/ツールのバージョンなど)

ここにより詳細な情報を記載してください。

他にも$contentAnimationにshowというクラスが追加されたら(B)を実行するというif文も別途作ってみましたが、
それだとアニメーション自体が動かなくなりました。

どなたかこの問題を解決していただけると助かります。
どうかよろしくお願いいたします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

0

ご提示のコードがJSのファイルだけなので頭の中で動かしてみました。
が、よくわかりませんでした。

Bのコードはテキストを細かく区切って遅延を差し込んでいるのかな?
であるなら、遅延を差し込む区切りを<p>タグとか<div>とか…は違うか。

スクロールをトリガーにしているのでスクロールされるたびにScrollAnimation()が実行されるけど、
const $skillsAnimationElement←この配列の中身が変わってないので何度も繰り返すのだと考えられます。
グローバルで定義しているから何度呼んでも全部のテキストを処理することになるのかなと。

スクロールした量と要素の位置を比較して、配列から外していく処理を記述するか、

skillsTextsArray.push('<span><span style="animation-delay: ' + ((j * .5) + .3) + 's;">' + s + '</span></span>')


ここのspanにclass='show'`としてしまうとか。 CSSのコード次第だけど、

$contentAnimation[i].classList.add('show') //;が無いような?

こっちの要素にはshowを付けて一度だけの処理で終わるとすれば、
テキストの方にも同じCSSが効きそうな気がします。
「このclassが付いたらアニメーションを止める」みたいな記述がしてあるかと。

htmlとcssのコードがあれば修正したJSを貼るところですが、
無いのでこのあたりでご勘弁を。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2021/02/23 10:10

    ご回答ありがとうございます。
    情報が少なくて申し訳ございません。
    HTMLとCSSの情報もお送りしますので、ぜひご回答いただけますと幸いです。

    //(A)//
    (A)に関しては各コンテンツに'show'が追加されるようにしています。

    htmlは 
    <div class="skills js-animation">
    <div class="skills-box">
    <div class="skills-item">
    この中にtitleやicon,messageなどの要素も入ってます。
    </div>
    </div>
    </div>

    CSSは

    .skills{
    opacity: 0;
    transition: .6s;
    transform: translateY(-50px);
    }

    .skills.show{
    opacity: 1;
    transform: none;
    }

    //(B)//
    (B)に関しては skillsの中のレベルを表す星マークを順番に出したいと思ってます。

    HTMLは、上記の要素の中に星マークを入れております。
    <div class="skills js-animation">
    <div class="skills-box">
    <div class="skills-item">
    <div class="skillsAnimation">★★★★☆</div>
    </div>
    </div>
    </div>

    CSSは横か文字が出てくるように実装しています。

    @keyframes skillsShow {
    0%{
    opacity: 0;
    transform: translateX(200px);
    }
    100%{
    opacity: 1;
    transform: none;
    }
    }

    .skillsAnimation span{
    display: inline-block;
    }

    .skillsAnimation span{
    margin: 10px 0;
    animation: skillsShow .8s backwards;
    }

    これで先ほどのJavaScriptだとスクロールするたびに星のアニメーションが実行されてしまいます。
    星のアニメーションはskillsのコンテンツが(A)によってふわっと出てきたときに1回だけ実行したいです。

    大変お手数おかけいたしますが、ぜひご回答よろしくお願いいたします。

    キャンセル

  • 2021/02/23 23:34 編集

    スクロールするたびに全部☆を拾っているのが原因なので、
    showがついていない一番上の1個のみ取得して引数として関数に渡してみました。
    関数を実行する条件は画面上に見える☆の高さまで…かな?
    関数の中にあったループは外しました。
    渡した1個のみ操作すればいいので。

    動くようになったコードを捨てるのももったいないので貼っておきますね。
    差分チェックしながら調整をしてみてください。
    ※html、cssは特に弄ってないので割愛させていただきますね。

    const $contentAnimation = document.querySelectorAll('.js-animation');

    document.addEventListener('scroll',()=>{
    for (let i = 0; i < $contentAnimation.length; i++){
    const $getElementDistance = $contentAnimation[i].getBoundingClientRect().top + $contentAnimation[i].clientHeight * .6;

    if (window.innerHeight > $getElementDistance){
    $contentAnimation[i].classList.add('show');
    }
    }

    let item = document.querySelector('.skillsAnimation:not(.show)');
    const getItemDistance = item.getBoundingClientRect().top + item.clientHeight * .6;

    if (window.innerHeight > getItemDistance){
    item.classList.add('show');
    ScrollAnimation(item);
    }
    });

    function ScrollAnimation(item){

    const targetSkills = item;
    skillsTexts = targetSkills.textContent;
    skillsTextsArray = [];

    targetSkills.textContent = '';

    for (let j = 0; j < skillsTexts.split('').length; j++){
    let s = skillsTexts.split('')[j];
    if (s === '' ) {
    skillsTextsArray.push('');
    } else {
    skillsTextsArray.push('<span><span style="animation-delay: ' + ((j * .5) + .3) + 's;">' + s + '</span></span>');
    }
    }

    for (let k =0; k < skillsTextsArray.length; k++){
    targetSkills.innerHTML += skillsTextsArray[k];
    }
    }

    追記 すみません、動作が少しおかしいのでもう一度確認します。
    追記 頭が寝てました。DOM操作用のJSなのにhead内に置いてました。動きます。
    追記 追記を下に動かしました。念のためにここに貼ったコードで再確認しましたが動きました。
    お騒がせしてすみません。拙いコードですが参考までにどうぞ。

    キャンセル

  • 2021/02/24 20:02

    ご丁寧にありがとうございます。
    おかげさまで動きました。
    今後もしわからないことがございましたら、ぜひ教えたいただけますと幸いです。
    本当にありがとうございました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.21%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る