画面に入ると横に伸びるスキルバーをJavaScriptで実装する方法【ポートフォリオに最適】

Webエンジニアやデザイナーの方なら、実績を掲載するためのポートフォリオサイトが必要ですね。

自分はどんなスキルがあるのか・なにを作れるのかを知ってもらうことで、仕事を受注できる確率がアップします。

そこで今回の記事では、スキルの熟練度が一目でわかるスキルバーの実装方法を紹介していきます!

視界に入ると横に伸びるアニメーション付きなので、サイトを訪れたユーザーの注意を引くことができておすすめです。

CSSとJavaScriptで実装していきますので、よければ参考にしてみてください。

目次

アニメーション付きスキルバーをDEMOページで確認

スキルバーを確認する用のDEMOページを作りましたので、まずはそちらをご覧ください。

要素が画面内に入ると、

  • バーの横幅が伸びる
  • 熟練度の%の数値がカウントアップされる

これらのアニメーションが実行されます。

確認できたら、次のセクションで実際にコードを書いていきましょう。

アニメーション付きスキルバーの実装コード

ここでは実際のコードを紹介していきます。

コピペですぐに使えるようにしてますので、自由にお使いください。

HTML

<div class="skill" data-proficiency="100"><!-- スキル全体を囲う要素。熟練度を0~100の間で記入 -->

  <div class="skill-info">
    <p class="skill-name">HTML</p><!-- 名前 -->

    <p class="skill-percentage">
      <span class="countup"></span>%<!-- 熟練度の数値をJSで動的に表示 -->
    </p>
  </div>

  <div class="skill-bar-container"><!-- スキルバーの親要素 -->
    <div class="skill-bar"></div><!-- スキルバー本体 -->
  </div>

</div>

まず、スキル全体を囲うdivにskillというクラス名と、カスタムデータ属性を付与します。

data-proficiencyの中に、0~100の数値を入れてください。

この数値をJavaScriptで取得して、スキルバーの横幅とパーセンテージの値にセットします。

skillのdivの中には、

  • スキルの名前
  • スキルの熟練度(%)
  • スキルバー本体

これらを入れています。

他にもスキルのアイコンや経験年数など入れても良いと思うので、必要であれば自身で足してみてくださいね。

HTMLはこれで終わりです!

CSS

/* スキル全体を囲うdiv */
.skill {
  margin: 30px 0; /* 上下のマージンを指定 */
}

/* スキルの情報 */
.skill-info {
  display: flex;
  justify-content: space-between;
  font-size: 16px;
  line-height: 1.5;
  margin-bottom: 10px;
}

/* スキルの熟練度(パーセンテージ) */
.skill-percentage {
  opacity: 0; /* 初期状態では透明に */
  transition: opacity 0.6s;
}

/* スキルバーの親要素 */
.skill-bar-container {
  position: relative;
  width: 100%;
  height: 15px; /* スキルバーの高さ */
  background-color: #f3f3f3; /* スキルバーの背景色 */
  overflow: hidden;
}

/* スキルバー本体 */
.skill-bar {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: 0; /* 初期状態では0 */
  background-color: #34e8d7; /* スキルバーの色 */
  transition: width 3s cubic-bezier(0.22, 1, 0.36, 1); /* スキルバーが伸びる速度を調整 */
}

こちらがベースのCSSになります。

細かいカスタマイズ方法は後ほど紹介するので、ここでは重要なものだけを説明しますね。

まず、.skill-percentageの初期設定としてopacity: 0がかかっていますが、これがないと以下の画像のようになってしまいます。

アニメーションが発火すると同時に見えるようにしたいので、最初は透明にしておきましょう。

次に.skill-barwidth: 0についてですが、これがないとスキルバーが横に伸びるアニメーションが発火しなくなります。

これは忘れがちなので、スキルバーのアニメーションがうまく動かないという方はここを確認してください。

JavaScript

今回の実装にはIntersection observer APIを使用しているのですが、ブラウザ対応のためにpolyfillを読み込む必要がありますので、先にやっておきましょう。

こちらのページから、intersection-observer.jsをダウンロードするか、ソースをコピーします。

polyfillがJSファイルよりも上にくるように、以下のようにして読み込んでください。

  <script src="path/intersection-observer.js"></script><!-- polyfillを読み込む -->
  <script src="path/main.js"></script><!-- この中に実際のコードを書く -->
</body>
</html>

以上の準備が整ったら、実際のJavaScriptのコードを見ていきましょう。

window.addEventListener('DOMContentLoaded', () => {

  // DOM要素を取得
  const skillEls = document.querySelectorAll('.skill');

  // カウントアップの設定
  const animationDuration = 2000;
  const frameDuration = 1000 / 60;
  const totalFrames = Math.round(animationDuration / frameDuration);
  const easeOut = t => t * (2 - t);
  const animateCountUp = el => {
    let frame = 0;
    const countTo = parseInt(el.innerHTML, 10);
    const counter = setInterval( () => {
      frame++;
      const progress = easeOut(frame / totalFrames);
      const currentCount = Math.round(countTo * progress);

      if (parseInt(el.innerHTML, 10) !== currentCount) {
        el.innerHTML = currentCount;
      }

      if (frame === totalFrames) {
        clearInterval(counter);
      }
    }, frameDuration);
  };

  // Intersection observerに渡すコールバック関数
  const cb = function(entries, observer) {
    entries.forEach((entry) => {
      if(entry.isIntersecting) {
        const proficiencyVal = entry.target.dataset.proficiency;
        const skillBar = entry.target.querySelector('.skill-bar');
        const percentage = entry.target.querySelector('.skill-percentage');
        const countup = entry.target.querySelector('.countup');

        skillBar.style.width = proficiencyVal + '%';
        percentage.style.opacity = 1;
        countup.textContent = proficiencyVal;
        animateCountUp(countup);

        observer.unobserve(entry.target);
      }
    });
  };

  // Intersection observerに渡すオプション
  const options = {
    rootMargin: "-50px 0px"
  };

  // IntersectionObserver初期化
  const io = new IntersectionObserver(cb, options);
  io.POLL_INTERVAL = 100; // Polyfillの設定
  skillEls.forEach(el => {
    io.observe(el);
  });

});

それでは、コードを分解しつつ説明していきます。

4行目:DOM要素を取得する

const skillEls = document.querySelectorAll('.skill');

まず最初に、.skillというクラスが付いた要素をquerySelectorAllですべて取得します。

7~27行目:数値のカウントアップのスピードを徐々に遅くする関数を作成

ここに関しては説明が長くなるので、また別の記事で詳しく説明しますね…。

何をしているかというと、熟練度を表す80%とか100%とかの数値をカウントアップするスピードを調整しています。

DEMOを見ていただけると分かると思いますが、数値が大きくなるにつれカウントするスピードが遅くなっていますね。

CSSならease-outなどを使うと1行で済むのですが、素のJavaScriptだとそういったものがないので、これだけ長いコードになってしまいます。

30~46行目:Intersection observerに渡すコールバック関数を作成

const cb = function(entries, observer) {
  entries.forEach((entry) => {
    if(entry.isIntersecting) {
      const proficiencyVal = entry.target.dataset.proficiency;
      const skillBar = entry.target.querySelector('.skill-bar');
      const percentage = entry.target.querySelector('.skill-percentage');
      const countup = entry.target.querySelector('.countup');

      skillBar.style.width = proficiencyVal + '%';
      percentage.style.opacity = 1;
      countup.textContent = proficiencyVal;
      animateCountUp(countup);

      observer.unobserve(entry.target);
    }
  });
};

ここでは、要素が画面内に入ったら発火する処理を書いていきます。

if(entry.isIntersecting)というのは、“監視している要素が見える状態になったら”という条件式です。

なのでここでは、.skillのdivが画面内に入ったら{ }内を実行するという意味になりますね。さっそく中身を見ていきましょう。

const proficiencyVal = entry.target.dataset.proficiency;

まずこちらの行では、HTMLで設定したカスタムデータ属性の値を取得しています。

<div class="skill" data-proficiency="100">
<!-- 中身は省略 -->
</div>

最初に作成した、HTML内のdata-proficiencyの部分ですね。

const skillBar = entry.target.querySelector('.skill-bar');
const percentage = entry.target.querySelector('.skill-percentage');
const countup = entry.target.querySelector('.countup');

次の3行では、.skillのdiv内にある3つの要素を取得しています。

  • スキルバー本体
  • スキルの熟練度(%)のテキスト
  • パーセンテージの数値を表示する空のspanタグ

要素が取得できたので、それぞれのアニメーションを実行しましょう。

skillBar.style.width = proficiencyVal + '%';
percentage.style.opacity = 1;
countup.textContent = proficiencyVal;
animateCountUp(countup);

まず1行目で、data-proficiencyに設定した熟練度の数値をスキルバーの横幅としてセットしています。

これでスキルバーの横幅が初期値の0から100%になり、バーが横に伸びるというわけですね。

次の行では、初期設定で透明にしておいたパーセンテージのテキストを見えるようにしています。

countup.textContent = proficiencyVal;
animateCountUp(countup);

この2行では、

  • 熟練度の数値を.countupのspanタグに挿入
  • 事前に作成したanimateCountUp関数を実行しカウントアップする

という処理をしています。これでパーセンテージの数値が画面上でカウントアップされるようになりました。

observer.unobserve(entry.target);

最後のこのコードは、1度アニメーションが発火したらそれ以上は監視しないという命令を出しています。

これでコールバック関数部分は完了です!

49~51行目:Intersection observerに渡すオプションを設定する

const options = {
  rootMargin: "-50px 0px"
};

ここではIntersection observerに渡すオプションを設定します。

他にも設定できる項目はありますが、今回はrootMarginのみを指定します。

まずrootMargin無しの状態では、ビューポートと監視中の要素が交錯した瞬間にアニメーションが発火してしまいます。つまり、画面の上端か下端が要素に触れた瞬間にアニメーションが実行されるということですね。

これだと、アニメーション開始タイミングが早すぎて、見逃してしまう可能性があります…。

この”要素が交錯した”という判定をずらすことができるのがrootMarginです!

rootMargin: -50px 0pxは何を意味するかというと、要素が画面に入ってから、さらに50px分スクロールするとアニメーションを開始するということになります。

このオプションはCSSのようにショートハンドで書くことができて、

rootMargin: "-50px 0px"
rootMargin: "-50px 0px -50px 0px"

これら2つの意味は同じになります。

ただしCSSと違って0を指定するときはpxという単位が必須なので、その点だけ注意してくださいね。

54~58行目:Intersection observerを実行する

const io = new IntersectionObserver(cb, options);
io.POLL_INTERVAL = 100; // Polyfillの設定
skillEls.forEach(el => {
  io.observe(el);
});

これでようやく最後のJavaScriptコードになります。

まず一行目で、設定したコールバック関数とオプションを渡します。

二行目はpolyfillを使用するために必要なコードですので、そのまま記述してください。

そして最後にforEachメソッドを使って、すべての.skillのdivを監視するという処理を行っています。

これで、スキルの要素が画面内に入ったらアニメーションが実行されるというコードが完成しました!

Intersection observerは最初は難しく感じるかと思うので、ぜひドキュメントに目を通してみてください。

【まとめ】アニメーション付きのスキルバーでポートフォリオを充実させよう

今回の記事では、アニメーション付きのスキルバーの実装方法を紹介しました。

jQueryもJSプラグインも使用せずに実装できるので、カスタマイズもしやすいですね。

ポートフォリオサイトには特に使いやすいパーツだと思いますので、ぜひ実際に使ってみてください!

僕も自分のポートフォリオサイトで使用してますので、気になる方は参考にしてくださいね。

記事が役に立ったらサポートしてください

Web制作に関する
記事案を募集中!

Web制作について知りたいこと、質問等ありましたら、以下のフォームから気軽に投稿してください。
要望が多かったものは解説記事を作成します。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次