パララックスってどういうものなの?
ご存知の方も多いと思いますが、パララックスがどういったものか確認してみましょう。
以下のボタンをクリックすると本文に対してパララックスを適応します。
クリックしたら記事を適当にスクロールして下さい。コンテンツがふわっと表示されることに気付くでしょう。
Intersection Observer API
では、早速 Intersection Observer API を使ってみましょう。
MDN のドキュメントも併せてご確認下さい。
Intersection Observerオブジェクトをインスタンス化
まずは Intersection Observer がターゲットを見付けた時に呼び出される callback 関数と監視の元となる要素 (ルート要素) を含むオプションを定義して、オブジェクトをインスタンス化します。
オプションは任意となっているので、必要なら設定します。
ルート要素にはデフォルトのビューポート (画面に映っている領域) 以外では任意の要素 (典型的にはスクロールボックスなど) を指定できます。
const options = {
root : null, // デフォルトは null で、ビューポートを表す
rootMargin : "0px", // ルート要素を上下左右にどれだけ広げるかを CSS の margin プロパティと同じ指定方法で入力する (デフォルトは "0px")
threshold : 0.25, // ルート要素にマージンを加えた領域内にターゲット要素がどれだけ入った時に発火するかを0.0~1.0で指定する (デフォルトは1.0)
};
const observer = new IntersectionObserver(callback, options); // コールバック関数 callback は後述
これで子孫要素を監視する Intersection Observer オブジェクトが用意できました。
監視を行う子孫要素を指定すれば条件が整った段階で callback 関数が呼ばれます。
監視する子孫要素をセットする
続いて、ルート要素内の位置を監視する子孫要素を先ほど作った Intersection Observer オブジェクトに追加します。
observe メソッドに監視したい要素のセレクタを渡すだけなので簡単です。
また、一つの Intersection Observer オブジェクトで複数の要素を監視できます。
// id="main_content" 以下の画像全て
const imgs = document.getElementById("main_content").getElementsByTagName("img");
// 監視する要素を登録する
for(let i = 0, n = imgs.length; i < n; i++){
observer.observe(imgs[i]);
}
上記ではメインコンテンツ内の画像全てを監視対象にしてみました。
例えば、遅延読込させる画像などがこういった指定方法になります。
callback関数で処理を行う
最後に、API がルート要素内に先ほどの子孫要素が入ったことを検出した場合の処理を行います。
コールバック関数には検出した子孫要素のオブジェクト (IntersectionObserverEntry Object) 配列と監視を行う Intersection Observer のオブジェクトが渡されます。
渡される要素のオブジェクトには以下が含まれます。
- .boundingClientRect … Element.getBoundingClientRect() と同じ
- .intersectionRatio … ターゲット要素がルート要素内にどれだけ表示されているか (0.0~1.0)
- .intersectionRect … ルート要素内に表示されている領域のBoundingClientRect
- .isIntersecting … 表示されているか (true / false)
- .rootBounds … ルート要素のgetBoundingClientRect()
- .target … ターゲット要素のセレクタ
- .time … 検出時間
const callback = (entries, observer) => {
entries.forEach(entry => { // entriesは複数のターゲット要素が同時にコールバックされてくることがあるので配列
if(!entry.isIntersecting) return; // ルート要素内にターゲット要素が表示されているか (初回はなぜか全てのターゲット要素がコールバックされてくる)
// 以下やりたい処理を実行する
entry.target.src = entry.target.datase.src;
// ここまで
observer.unobserve(entry.target); // 以降、ターゲット要素の監視が不要なら監視を解除する
});
};
Intersection Observer API の使い方コードまとめ
以上のコードをまとめると以下になります。
const で関数を定義している場合は、巻き上げが起こらないので呼び出すより前で定義します。(var の場合はどこでもいい)
const options = {
root : null, // デフォルトは null で、ビューポートを表す
rootMargin : "0px", // ルート要素を上下左右にどれだけ広げるかを CSS の margin プロパティと同じ指定方法で入力する (デフォルトは "0px")
threshold : 0.25, // ルート要素にマージンを加えた領域内にターゲット要素がどれだけ入った時に発火するかを0.0~1.0で指定する (デフォルトは1.0)
};
// コールバック関数は先に定義
const callback = (entries, observer) => {
entries.forEach(entry => { // entriesは複数のターゲット要素が同時にコールバックされてくることがあるので配列
if(!entry.isIntersecting) return; // ルート要素内にターゲット要素が表示されているか (初回はなぜか全てのターゲット要素がコールバックされてくる)
// 以下やりたい処理を実行する
entry.target.src = entry.target.datase.src;
// ここまで
observer.unobserve(entry.target); // 以降、ターゲット要素の監視が不要なら監視を解除する
});
};
// インスタンス化
const observer = new IntersectionObserver(callback, options);
// id="main_content" 以下の画像全て
const imgs = document.getElementById("main_content").getElementsByTagName("img");
// 監視する要素を登録する
for(let i = 0, n = imgs.length; i < n; i++){
observer.observe(imgs[i]);
}
パララックスを行うコードサンプル
これまでのコードを参考に以下のようにすれば非常に簡単なコードでパララックスが実装できます。
(function(){
"use strict";
const options = {
root : null,
rootMargin : "0px",
threshold : 0.35,
};
const callback = (entries, observer) => {
entries.forEach(entry => {
if(!entry.isIntersecting) return;
entry.target.style.opacity = "";
setTimeout(() => {
entry.target.style.transition = "";
}, 1200);
observer.unobserve(entry.target);
});
};
const observer = new IntersectionObserver(callback, options);
window.addEventListener("DOMContentLoaded", () => {
const targets = document.querySelector("#contents").children;
for(let i = 0, n = targets.length; i < n; i++){
observer.observe(targets[i]);
targets[i].style.opacity = 0;
targets[i].style.transition = "1.2s";
}
}, false);
})();
このコードでは #contents 要素直下の全ての要素を一旦、透明 (opacity=0) にしておいて Intersection Observer によってビューポートに 35% が表示された段階で元の透明度に戻すようにしています。
透明度以外にも位置も動かしたい場合は以下のように拡張できます。(荒いやり方ですが)
(function(){
"use strict";
const options = {
root : null,
rootMargin : "0px",
threshold : 0.25,
};
const slideIn = function($target){
if($target.dataset.parallax === "off"){
// Run effects
setTimeout(()=>{
$target.style.position = "";
$target.style.left = "";
}, 800);
$target.style.left = "0";
delete $target.dataset.parallax;
} else{
// Prepare effects
console.log(window.getComputedStyle($target).position);
if(window.getComputedStyle($target).position === "static"){
$target.dataset.parallax = "off";
$target.style.position = "relative";
$target.style.left = "1.5em";
}
}
};
const slideUp = function($target){
if($target.dataset.parallax === "off"){
// Run effects
setTimeout(()=>{
$target.style.position = "";
$target.style.top = "";
}, 800);
$target.style.top = "0";
delete $target.dataset.parallax;
} else{
// Prepare effects
if(window.getComputedStyle($target).position === "static"){
$target.dataset.parallax = "off";
$target.style.position = "relative";
$target.style.top = "1.5em";
}
}
};
const effects = {
h2 : slideIn,
h3 : slideIn,
h4 : slideIn,
h5 : slideIn,
h6 : slideIn,
p : slideUp,
};
const callback = (entries, observer) => {
let effect;
entries.forEach(entry => {
if(!entry.isIntersecting) return;
entry.target.style.opacity = "";
setTimeout(() => {
entry.target.style.transition = "";
}, 800);
const tag = entry.target.tagName;
if(tag && (effect = effects[tag.toLowerCase()])){
effect(entry.target);
}
observer.unobserve(entry.target);
});
};
const observer = new IntersectionObserver(callback, options);
const targets = document.querySelector(".post-content").children;
for(let i = 0, n = targets.length, effect; i < n; i++){
observer.observe(targets[i]);
targets[i].style.opacity = 0;
targets[i].style.transition = "0.8s";
const tag = targets[i].tagName;
if(tag && (effect = effects[tag.toLowerCase()])){
console.log(tag, effect);
effect(targets[i]);
}
}
})();
ポリフィル
Intersection Observer API に対応していないブラウザでも使用できるようにするポリフィルが提供されていますので、モダンブラウザ以外やスマホでも使えるようにしたい場合は使用可否を判断して以下を読み込んで下さい。
・GitHub - W3C - IntersectionObserver / polyfill
まとめ
以上、Intersection Observer API を用いたパララックス効果の作り方でした。
個人的にはあまり好きではないですが、効果的にユーザ体験を向上させるデザインと併せて導入できればユーザの興味をより惹くコンテンツが作れるでしょう。
ぜひ、優秀なデザイナーと一緒にコンテンツを構築して下さい!