【CSS&JS】スクロールに合わせて蛍光ペン風マーカーを引く方法

記事内で文章を強調したいときには、よくCSSマーカーが使われていますよね。

スマホで記事を流し読みすることが一般的になった今、マーカーで強調することで読者に伝えたい内容を読んでもらいやすくなります。

今回はこれに一手間加えて、スクロールで文字が画面内に入ると自動でマーカーを引いてくれる機能をJavaScriptで実装していきます!

実装内容はDEMOページを用意してますので、まずはそちらをご確認ください。

ここではjQueryを使わず、素のJavaScriptを使って実装しています。

また、各種ブラウザ対応の方法も説明していますので、そちらも参考にしてみてください。

目次

スクロール連動マーカー制作のステップ

まずはCSSとJavaScriptのそれぞれの役割から見ていきましょう。

  • CSS…マーカーの見た目とアニメーションの動作を設定
  • JS…要素を監視して、画面に出入りするときにクラスを付ける

大まかな役割はこんな感じになります。以下で説明していきます!

【CSSの役割】マーカーの見た目やアニメーションの動作を設定する

まずCSSで、マーカーを引くアニメーションを発火する前と、発火後の見た目を作ります。

最初の段階で、色・太さ・マーカーを引く速度等を設定しておきます。

画面に入ると同時にマーカーを引きたいので、最初はマーカー自体の横幅を0にして見えない状態にしておきます。

そしてテキストが画面内に入った段階で、マーカーの横幅を100%にしてマーカーを見えるようにします。

このとき、画面にテキストが出入りするのを監視し、アニメーションを発火させるのが、次で説明するJavaScriptの役割になります。

【JSの役割】要素を監視して、画面に出入りするときにクラスを付ける

CSSで装飾部分が完了したら、JavaScriptでアニメーションを発火させます。

要素の位置を監視し、画面内に入るとCSSのクラスを付与します。

そのクラスにマーカーの横幅を100%にするコードを書いておくことで、画面に入った段階でマーカーが引かれるという仕組みですね。

大まかに説明しましたが、次で実際のコードを見ていきましょう!

スクロール連動マーカーのコード

HTML/CSS/JavaScriptそれぞれのコードを解説していきます。

DEMOページをまだ確認していない方は、以下からどうぞ。

HTML

<span class="js-marker">ここにマーカーで装飾するテキストを入れる</span>

まず装飾したい文字をspanタグで囲みます。

その後js-markerというクラスを付け、ここにマーカーのスタイルを書きます。

CSS

/* アニメーション前のスタイル */
.js-marker {
  display: inline;
  position: relative;
  background-image: linear-gradient(90deg, #ffff66, #ffff66); /* 単色の場合は同じ色、グラデーションさせる場合は別々の色 */
  background-repeat: no-repeat;
  background-position: bottom left;
  background-size: 0 30%; /* '30%'の部分にマーカーの太さを記入 */
  transition: all 1s ease-in-out; /* マーカーを引く速度を調整 */
  font-weight: bold; /* ついでに太字にしたい場合 */
}

/* アニメーション発火時 */
.js-marker.inview {
  background-size: 100% 30%; /* '30%'の部分は上で設定した太さに合わせる */
}

js-markerのクラスにマーカーのスタイルを記述します。

マーカーの色・太さ・マーカーを引く速度等をお好みで設定してください。特にこだわりがなければそのままで大丈夫です。

ここでのポイントは、background-sizeの値の1つ目を0にしておくこと。

こうすることで初期の段階ではマーカーは非表示になります。

そしてJSによってinviewクラスが付与されると、マーカーの横幅が100%になり、マーカーが表示されます。

JavaScript

要素が画面に入ったかの判定には、Intersection Observer APIを使用します。

この機能はまだ対応していないブラウザがいくつかあるため、polyfillを使って対応をする必要があります。

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

最初にpolyfillがくるように、以下のように読み込んでください。

  <script src="path/intersection-observer.js"></script><!-- polyfillを最初に読み込む -->
  <script src="path/main.js"></script><!-- スクロール連動マーカーのファイルをpolyfillより後に読み込む -->
</body>
</html>

以下がコードの全体像です。

document.addEventListener('DOMContentLoaded', function() {

  var markerText = document.querySelectorAll('.js-marker'); // 監視対象を選択
  var markerTextArr = Array.prototype.slice.call(markerText); // 監視対象をArrayに変換(IE対策)

  /* IntersectionObserverに渡すコールバック関数 */
  var cb = function(entries, observer) {
    entries.forEach((entry) => {

      if(entry.isIntersecting) {
        /* 監視対象が画面内に入ったときのアクション */
        entry.target.classList.add('inview'); // 画面内に入った要素にinviewクラスを付与
        observer.unobserve(entry.target); // 1度発火した後監視を止める
      }

    });
  }
  
  /* IntersectionObserverに渡すコールバック関数 */
  var options = {
    rootMargin: "-50px 0px"
  }

  /* IntersectionObserver初期化 */
  var io = new IntersectionObserver(cb, options);
  io.POLL_INTERVAL = 100; // Polyfillの設定

  markerTextArr.forEach(el => {
    io.observe(el);
  });

});

少し長いので、細かく分けながら説明していきます。

スクロール連動マーカーの要素を取得する

var markerText = document.querySelectorAll('.js-marker'); // 監視対象を選択
var markerTextArr = Array.prototype.slice.call(markerText); // 監視対象をArrayに変換(IE対策)

まずは、スクロール連動マーカーを実装したい要素を取得します。

querySelectorAllを使って、ドキュメント内にあるjs-markerクラスが付いている要素を全て選択します。

次の行で、IE対策のため、選択した要素をいったんArrayに変換します。

こうすることで、IEでもquerySelectorAllで取得した要素に対してforEachメソッドが使えるようになります。

Intersection Observerに渡すコールバック関数

/* IntersectionObserverに渡すコールバック関数 */
  var cb = function(entries, observer) {
    entries.forEach((entry) => {

      if(entry.isIntersecting) {
        /* 監視対象が画面内に入ったときのアクション */
        entry.target.classList.add('inview'); // 画面内に入った要素にinviewクラスを付与
        observer.unobserve(entry.target); // 1度発火した後監視を止める
      }

    });
  }

ここに要素が画面に出入りしたときに起こしたいアクションを書きます。

まずentriesには、最初のステップで取得したjs-markerクラスが付いたすべての要素が入っています。

これをforEachでひとつひとつ取り出し、それぞれをentryとしてif文内の処理を実行しています。

if(entry.isIntersecting)は、“要素が画面内にはいったら”という条件式です。

画面内に入ると、inviewというクラスが付与される様にしています。

次の行のobserver.unobserveの部分は、一度マーカーを引き終わったら監視を停止させるためです。

これがないと、要素が画面から一旦出て、再び入ったときにまた同じコードが発火してしまいます。

Intersection Observerに渡すオプション

/* IntersectionObserverに渡すコールバック関数 */
var options = {
  rootMargin: "-50px 0px"
}

ここにはIntersection Observerに渡せるオプションを設定できます。

rootMargin以外にもいくつか設定できる項目はありますが、ここでは使わないので省略します。詳しくはMDNをご確認ください。

rootMarginの値が"-50px 0px"となっていますが、これは要素が画面に入ってからさらに50pxスクロールするとアニメーションが発火するという仕組みになっています。

CSSのショートハンドの書き方と同じで、rootMargin: 上 右 下 左のように設定できます。

左右は常に0pxでOKです(0のときも’px’を付ける必要があるので注意)。

デフォルトのままだと、要素が画面と交錯した瞬間にアニメーションが発火してしまうので、アニメーションのスピードによっては見逃してしまう可能性があります。

こういう理由から、今回は50pxの余白をもたせました。自身の好みに合わせて調整してみてください。

Intersection Observerを呼び出す(初期化)

/* IntersectionObserver初期化 */
var io = new IntersectionObserver(cb, options);
io.POLL_INTERVAL = 100; // Polyfillの設定

markerTextArr.forEach(el => {
  io.observe(el);
});

ここでIntersection Observerを呼び出します。

引数には上で設定したコールバック関数とオプションが入ります。

io.POLL_INTERVAL = 100;

この部分はpolyfillを使用するのに必要なコードです。

Intersection Observerが使えないブラウザでは、代わりにscrollイベントが使われます。

scrollイベントはスクロール中に連続的に呼び出されるので、処理が重くなります。

これを0.1秒に1回呼び出すことで処理を軽くするという意味があります。

markerTextArr.forEach(el => {
  io.observe(el);
});

最後に、Arrayに格納したすべての要素をobserveメソッドで監視するというコードです。

JSコードは以上になります!

【応用編】画面に出入りするたびに何度もマーカーを引くコード

これまでは、スクロールに合わせて1度だけマーカーを引くコードを紹介しました。

ここでは、画面に出入りするたびにマーカーを引くコードを紹介します!

まずはDEMOページで実装内容をご覧ください。

実装内容が確認できたら、実際に作っていきます。

HTMLとCSSは同じものを使用

HTMLとCSSは上で紹介したものと全く同じなので、そちらをお使いください。

JavaScript

document.addEventListener('DOMContentLoaded', function() {

  var markerText = document.querySelectorAll('.js-marker'); // 監視対象を選択
  var markerTextArr = Array.prototype.slice.call(markerText); // 監視対象をArrayに変換(IE対策)

  /* IntersectionObserverに渡すコールバック関数 */
  var cb = function(entries, observer) {
    entries.forEach((entry) => {

      if(entry.isIntersecting) {
        /* 監視対象が画面内に入ったときのアクション */
        entry.target.classList.add('inview'); // 画面内に入った要素にinviewクラスを付与
      } else {
        entry.target.classList.remove('inview'); // 画面外に出た要素のinviewクラスを外す
      }

    });
  }
  
  /* IntersectionObserverに渡すコールバック関数 */
  var options = {
    rootMargin: "-50px 0px"
  }

  /* IntersectionObserver初期化 */
  var io = new IntersectionObserver(cb, options);
  io.POLL_INTERVAL = 100; // Polyfillの設定

  markerTextArr.forEach(el => {
    io.observe(el);
  });

});

変更点はたったの2点だけなので、これらの解説をしていきます。

if(entry.isIntersecting) {
  /* 監視対象が画面内に入ったときのアクション */
  entry.target.classList.add('inview'); // 画面内に入った要素にinviewクラスを付与
} else {
  entry.target.classList.remove('inview'); // 画面外に出た要素のinviewクラスを外す
}

まず先程のコードに入っていた、以下の行を削除します。

observer.unobserve(entry.target);

こちらのコードは、1度画面内に入った要素に対して監視を止めるというコードでしたね。

今回は画面内に入るたびにマーカーを引きたいので、監視し続ける必要があります。

そうなるとこのコードは不要になるので、削除します。

if(entry.isIntersecting) {
  /* 監視対象が画面内に入ったときのアクション */
  entry.target.classList.add('inview');
} else {
  /* 監視対象が画面外に出たときのアクション */
  entry.target.classList.remove('inview'); // 画面外に出た要素のinviewクラスを外す
}

次に、if文に新しくelseのスコープを作り、ここに要素が画面から出たときのアクションを書いていきます。

ここでは、画面外に要素が出るとinviewクラスを外す、というコードを書いています。

inviewクラスが外れるとマーカーの横幅が0になるので、マーカーが見えなくなりますね。

そして画面内に入るとまたinviewクラスが付与されるため、マーカーが何度も引かれるというわけです。

以上でコードの解説は終わりです!

スクロール連動マーカーのブラウザ対応について

今回の実装で使用しているIntersection Observerは、JavaScriptの比較的新しめの機能なので、未対応のブラウザもあります。

とはいえ全体の90%以上のブラウザでは対応されているので、ほぼ問題はないでしょう。

また、polyfillを読み込むことで未対応のブラウザにも最適化してくれます。

Githubのページにも記載してあるとおり、poolyfillを導入することでブラウザ対応における問題はほぼありません。

モダンブラウザのみを考慮するならば、polyfillなしでも大丈夫そうですね。

【まとめ】スクロール連動マーカーで記事をオシャレに装飾しよう

スクロール連動マーカーを使用することで、より読者の注意を惹きやすくなるはずですので、ぜひ使ってみてください。

以下の記事で紹介したグラデーションマーカーを併用してみても、面白いかもしれません。

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

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

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

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

コメント

コメントする

目次