▲TOPへ戻る

【javascript】スクロールしたら吹き出しが現れ、一文字ずつアニメーション

疑似要素

sample

以下のように、要素が下から画面の20%スクロールされたら、吹き出しが現れ、 1文字ずつアニメーションさせるコードを書きました。参考にしてください。

これより上にスクロールされたら現れる

HTML

HTMLは2行だけ。

要素が現れる目安となる線がいらなければ、
<div class="border"></div> はいりません。

  <div class="border"></div>  <!-- 省略可 -->
  <div class="content"></div>

css

画像と吹き出しの部分

  .wrapper {
    display: flex;
    margin: 10px;
    padding: 10px;
    opacity: 0;
    transition: all 1s ease;
  }
  .wrapper.active {   /* 下から画面20%分スクロールされたactiveを追加する。 */
    animation: appear 2s forwards;
  }
  @keyframes appear {
    0% {
      opacity: 0;
      transform: translateY(50px);
    }
    100% {
      opacity: 1;
      transform: none;
    }
  }
  .wrapper:nth-of-type(even) {  /* 偶数分には画像と吹き出しの順序を入れ替え */
    flex-direction: row-reverse;
  }
  .wrapper:nth-of-type(even).content .wrapper img {
    margin-left: 40px;
  }

吹き出し部分

吹き出しは全て自分で作るのはしんどいので、ジェネレーターを使うと便利です。

👉 吹き出しジェネレーター
  .personA,
  .personB {
    position: relative;
    max-width: 500px;
    height: 100%;
    padding: 10px;
    text-align: left;
    line-height: 1em;
    color: #ffffff;
    font-size: 20px;
    font-weight: bold;
    border-radius: 15px;
    -webkit-border-radius: 15px;
    -moz-border-radius: 15px;
    text-shadow: 1px 1px 2px #111;
  }
  .personA {
    background: green;
    border: 2px solid #666;
    margin-top: 10px;
    margin-left: 30px;
  }
  .personB {
    background: rgb(255, 123, 0);
    border: 2px solid #9b350a;
    margin-top: 10px;
    margin-right: 30px;
  }
  .personA:after,
  .personA:before,
  .personB:after,
  .personB:before {
    border: solid transparent;
    content: "";
    height: 0;
    width: 0;
    pointer-events: none;
    position: absolute;
    top: 50%;
  }
  .personA:after,
  .personA:before{
    right: 100%;
  }
  .personB:after,
  .personB:before {
    left: 100%;
  }
  .personA::after,
  .personB::after{
    border-top-width: 10px;
    border-bottom-width: 10px;
    border-left-width: 20px;
    border-right-width: 20px;
    margin-top: -10px;
  }
  .personA:after {
    border-color: rgba(76, 238, 81, 0);
    border-right-color: green;
  }
  .personB:after {
    border-color: rgba(238, 181, 72, 0);
    border-left-color: rgb(255, 123, 0);
  }
  .personA:before,
  .personB:before {
    border-top-width: 12px;
    border-bottom-width: 12px;
    border-left-width: 24px;
    border-right-width: 24px;
    margin-top: -12px;
  
  }.personA:before {
    border-color: rgba(56, 155, 12, 0);
    margin-right: 2px;
    border-right-color: #666;
  }
  .personB:before {
    border-color: rgba(155, 53, 10, 0);
    margin-left: 2px;
    border-left-color: rgb(104, 67, 0);
  }

1文字ずつアニメーション

  @keyframes slide {
    0% {
      transform: translateX(-50%);
      opacity: 0;
    }
    20% {
      transform: translateX(0px);
      opacity: 1;
    }
  }
  .wrapper.active > .textAni > span > span {
    animation: slide 0.5s ease-in-out backwards;
  }
  .textAni span {
    display: inline-block;
    overflow: hidden;
  }

javascript

data.js

  const pA = "./demo/conversation/img/nanasi.png",
        pB = "./demo/conversation/img/nanasi.png"
  const data = [
    {
      img: pA,
      text: "ミラーさん。",
    },
    {
      img: pB,
      text: "何ですか。",
    },
    {
      img: pA,
      text: "明日友達とお花見をします。ミラーさんも一緒に行きませんか?",
    },
    {
      img: pB,
      text: "いいですね。どこへ行きますか。",
    },
    {
      img: pA,
      text: "大阪城公園です。",
    },
    {
      img: pB,
      text: "何時ですか。",
    },
    {
      img: pA,
      text: "10時です。大阪城公園駅で会いましょう。",
    },
    {
      img: pB,
      text: "わかりました。",
    },
    {
      img: pA,
      text: "じゃ、また明日",
    },
  ];

HTML生成

  const content = document.querySelector(".content");
  const animationTarget = document.querySelectorAll(".textAni");

  for (let i = 0; i < data.length; i++) {
    const element = data[i];
    const div = document.createElement("div");
    const img = document.createElement("img");
    const div2 = document.createElement("div");
    div.classList.add("wrapper");
    img.src = element.img;
    if (i % 2 == 0) {
      div2.classList.add("personA");
    } else {
      div2.classList.add("personB");
    }
    div2.classList.add("textAni");
    content.appendChild(div);
    div.appendChild(img)
    div.appendChild(div2);
    const texts = element.text;
    textsArray = [];
    for (let j = 0; j < texts.split("").length; j++) {
      const textT = texts.split("")[j];
      if (textT === " ") {
        textsArray.push(" ");
      } else {
        textsArray.push(
          '<span><span style="animation-delay:' +
            (j * 0.1 + 0.2) +
            's;">' +
            textT +
            "</span></span>"
        );
      }
    }
    for (let k = 0; k < textsArray.length; k++) {
      div2.innerHTML += textsArray[k];
    }
  }

スクロールしたらアニメーション

  window.addEventListener("scroll", function () {
    for (let i = 0; i < wrapper.length; i++) {
      const element = wrapper[i];
      const distance = element.getBoundingClientRect().top + 
                       window.innerHeight * 0.2;
      //  下から画面20%分、要素が現れたら、クラスactiveを追加
      if (window.innerHeight > distance) {
        element.classList.add("active");
      } else {
        element.classList.remove("active");
      }
    }
  });

参考

js
【CSS】【javascript】テキストを1文字ずつアニメーションさせる方法
js
要素の高さを取得する方法。getBoundingClientRectとは?
point

スクロールしたら吹き出しが現れ、一文字ずつアニメーションさせるコードを紹介しました。全体はかなり長いので、 以下に、リンクを貼っておきます。

👉 全体のコード

こんな記事も読まれています。

profile

パソコン好きなガオ

コロナ禍によるステイホームを機にプログラミングを学ぶ。パソコンに関してはプロではないが、ちょっと詳しい程度。

パソコン

javascript

カメラ

ブログ