Web サイト制作で言う Parallax (パララックス・視差) とは、ユーザのスクロール操作に応じて複数の要素を異なる速さで動かすことにより立体感や奥行きのある効果・演出方法のこと。
完成イメージ
サンプルだと簡単過ぎてピンとこないですが、つくり込んだ例を見たければこちら。
教材
github からダウンロードしてください。

外部サイトへの移動は github のみで大丈夫です。
ソースコードはなるべく以下で補足します。
はじめに
Parallax はその特徴から、少し戸惑う動きになります。
そのサイトに適しているかどうか判断してから採用するようにしましょう。
多用せずにポイントで効果的に使うと印象に残る表現が可能です。
背景画像を入れる
背景を入れるため、body タグ内に下記 HTML を記述します。(index.html)
<div class="parallax-background"></div>
次に CSS で画像を画面いっぱいにします。(css/style.css)
.parallax-background {
height: 200vh;
background-image: url(../images/background.jpg);
background-size: cover;
background-position: top center;
background-repeat: no-repeat;
}
高さを画面サイズより大きくすることでスクロールするための余白を入れてあります。
(200vh, このあと前景を入れたいので2倍のサイズにしています)
また、background-size: cover を指定することで背景画像で要素を埋め尽くします。
スクロールイベントを監視
JavaScript でスクロールに応じて処理を実行します。
まずは、スクロール量を console.log に表示します。(js/parallax.js)
window.addEventListener('scroll', function (e) {
console.log(window.pageYOffset);
});
window.pageYOffset でスクロールした量を取得できます。実際にどんな値が表示されるかコンソールで確認してみましょう。
スクロール位置を利用して、実際より背景の位置を遅らせてみます。pageYOffset にはスクロールした pixel 量が入っているので、移動量を 0.8 倍にしてみます。
(1.0 倍だと背景が動かず固定されます)
JavaScript は先ほどのものを消して下記に置き換えます。
今回は CSS の background-position を JavaScript で調整します。
(JavaScript で変数名にハイフンは使えないので backgroundPosition となることに注意)
let background = document.getElementsByClassName('parallax-background')[0];
window.addEventListener('scroll', function (e) {
let scroll = window.pageYOffset * 0.8;
background.style.backgroundPosition = 'top ' + scroll + 'px center';
});
今回の例では、スクロールした量に応じて背景画像も下方向に移動しているので遅れて動いているように見えると言うわけです。
クラス名はドキュメント内に複数指定可能なので、getElementByClassName の戻り値は配列になります。先頭の要素(今回は指定のクラスはひとつしかないため)を取得するため末尾に [0] を付けています。
(ひとつだけなら id 属性を指定して getElementById で取ってきても良いです)
このような JavaScript の省略した書き方は、以下のように考えましょう。
まずは getElementByClassName で指定のクラス名がついている要素を取得します。クラスはページ内に複数あっても良いので戻り値は配列(リスト)になります。
let backgroundList = document.getElementByClassName('parallax-background');
このとき配列 backgroundList から、下記のようにして先頭の要素を取り出せます。
let background = backgroundList[0];
JavaScript ではこれをまとめて記述できます。
(ふたつ目の式の backgroundList がひとつめの式に置き換わるイメージ)
let background = document.getElementsByClassName('parallax-background')[0];
さらに、その後の下記の要素の位置を調整する記述もまとめて記述することもできます。
下記のコードの部分もまとめると…
background.style.backgroundPosition = 'top ' + scroll + 'px center';
↓
document.getElementsByClassName('parallax-background')[0].style.backgroundPosition = 'top ' + scroll + 'px center';
まとめすぎるとコードの可読性が下がるので、ほどほどに。
この場合はスクロールイベントが発生するたびに DOM から getElementByClassName で要素を探すことになるため、講座のコードでは最初に要素を取得して、スクロールイベント発生時は位置のみ調整するよう処理をふたつに分けています。
背景のスクロール (background-position) に関して、古い端末だと処理がカクついていたので、多少ですが改善しました。詳細は github のコードを確認ください。
対応内容としては、処理が重複しないように実行中のフラグを用意し、前の処理が終了するまでは次の処理を実行しないようにしたことが 1 点。もう一つは translate3d を使用することで GPU (グラフィック処理専用の CPU)での処理となるため描画速度の改善が期待できます。
※それに伴い2枚の重ね合わせの処理が変わってしまうので、CSS も変更を加えています (コード内にコメントを補足しています)
要素を追加してみる
ひとつだけゆっくりスクロールしても意味がないので、他の要素を追加してみます。
HTML に前景用の <div> 要素を背景の次(内側ではなく兄弟要素)に追加してみましょう。(index.html)
<div class="parallax-foreground"></div>
CSS にもいくつか追記します。
背景と前景を重ねるため、背景に position: relative を追記。
.parallax-background {
position: relative; // 前景が背景に重ねられるようにこの行を追記
height: 200vh;
...
前景には下記スタイルを適用します。
.parallax-foreground {
position: absolute;
top: 80vh;
background-image: url(../images/foreground.png);
background-size: cover;
background-position: top center;
background-repeat: no-repeat;
width: 100%;
height: 120vh;
}
ふたつの重ね合わせがズレないよう、absolute の余白 80vh と高さ 120vh をたすと、背景の 200vh と一致するように調整しています。
前景は JavaScript による移動量の調整は行わず、元のスクロールのままにしています。
レイヤーの数を増やして、それぞれ速度を変えて調整すると複雑な動きになります。
スクロールに応じて横方向に移動する、といったアレンジも可能です。
おまけ
今回は JavaScript ライブラリを使っていませんが、もっと簡単に実装したければ Rellax というライブラリがあります。
※コードは 03 参照
https://dixonandmoe.com/rellax/
やっていることはややこしい気がしますが、コード量は少ないのでライブラリを使わなくてもできる内容です。
おまけ2
似たような表現で固定背景を使うのもアリです。こちらは CSS のみで実現できます。
※コードは 04 参照
こういうやつ
用語集
CSS / background-size: cover
https://developer.mozilla.org/ja/docs/Web/CSS/background-size
CSS / vh (相対的な長さの単位)
CSS / background-position
https://developer.mozilla.org/ja/docs/Web/CSS/background-position
js / window.pageYOffset, window.scrollY
https://developer.mozilla.org/ja/docs/Web/API/Window/scrollY
js / getElementByClassName
https://developer.mozilla.org/ja/docs/Web/API/Document/getElementsByClassName