Vue.jsとCSSでパララックス効果(視差効果)を実現する。

## はじめに

自己ポートフォリオを作ってたとき、デザインの勉強をしていたところ、パララックス効果(視差効果)を知りました。

![qiita.mov.gif](https://qiita-image-store.s3.amazonaws.com/0/265961/751e86b9-b12f-35a0-5139-756861911fed.gif)


デザインで参考にしたサイトは「Digital Smile Academy」さんのサイトになります
http://dsa.clinic/

めっちゃかっこいいから作ってみようと。

JavaScriptでのライブラリを使ってもよかったのですが、アニメーションはなんでもCSSブームが自分の中できてたので
[【CSS3】話題のパララックス効果をスクリプト無しで実装する方法。](https://on-ze.com/archives/1947)
↑の記事を参考にさせていただき実装しました。

今回の完成形になります。
~~**Chromeでの動作確認のみしています。**~~
- firefoxとsafariとchromeでの動作確認しました。
Demo : https://sgnz-vuejs-parallax.herokuapp.com/
ソース: [NozomiSugiyama/vue-parallax](https://github.com/NozomiSugiyama/vue-parallax)

## 注意
~~**Chromeでの動作確認のみしています。** (何度でも言う)~~
- firefoxとsafariとchromeでの動作確認しました。
Vue.jsを使わないでの作成は中途半端な状態です。
ベストプラクティスが見つかっていないので、改善点が見つかり次第更新していく予定です。
まだまだ勉強中なので間違っているところがありましたら編集リクエスト、コメントじゃんじゃん飛ばしてください。

## Parallax Designとは
言葉で説明するより見ていただいた方が早そう。

http://muuuuu.org/

各要素のスクロール速度の差異によってそれぞれに奥行きを感じとれると思います。
なんかかっこいい。
普段使うアプリケーションなどでやると鬱陶しさを感じますがLPなどでうまく用いればインパクトを与えそうです。

## CSS
今回使っていくcssの説明をしていきます。

### perspective, perspective-origin, translateZ(), 

これがparallax実現のコアな部分になります。

<img width="662" alt="parallax" src="https://qiita-image-store.s3.amazonaws.com/0/265961/86946088-415b-b749-ae20-61d50dbdb06b.png">


#### perspective
> CSS の perspective プロパティは、3D 配置された要素に遠近感を与えるため、z=0 平面とユーザ間の距離を決めます。z>0 である 3D 要素はより大きく、z<0 である 3D 要素はより小さくなります。効果の強度はこのプロパティの値から決められます。
https://developer.mozilla.org/ja/docs/Web/CSS/perspective

#### perspective-origin
> CSS の perspective-origin プロパティは、閲覧者が見ている位置を決めます。これは perspective プロパティによって消失点として使われます。
https://developer.mozilla.org/ja/docs/Web/CSS/perspective-origin

#### translateZ
> translateZ() CSS 関数は、3 次元空間の z 軸に沿って、すなわちビューアーに近づくまたは遠ざかって、要素を移動させます。その結果は、<transform-function> データ型となります。
https://developer.mozilla.org/ja/docs/Web/CSS/transform-function/translateZ

#### Demo
これらの項目を使って試して見ます。
ファーストビューからでもparallaxコンテンツが表示されるようperspective-originを `top center` にしています。

```demo1.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>demo1</title>
    <style>
        body, div, section {
            margin: 0;
        }
        section {
            height: 100vh;
        }
        img {
            border: 1px solid black;
        }
        .base {
            perspective: 100px;
            perspective-origin: top center;
            height: 100vh;
            overflow-x: hidden;
            overflow-y: auto;
        }
        .float1 {
            transform: translateZ(0px);
            z-index: 1;
        }
        .float2 {
            transform: translateZ(30px);
            z-index: 2;
        }
        .float3 {
            transform: translateZ(40px);
            z-index: 3;
        }
        .float4 {
            transform: translateZ(50px);
            z-index: 4;
        }
        .row {
            display: flex;
            flex-direction: row;
            justify-content: center;
            align-items: center;
        }
    </style>
</head>
<body>
    <div class="base">
        <h1>parallax demo1</h1>
        <section>
            <h2>100 x 100</h2>
            <div class="row">
                <img class="float1" src="./img/100x100.png">
                <img class="float2" src="./img/100x100.png">
                <img class="float3" src="./img/100x100.png">
                <img class="float4" src="./img/100x100.png">
            </div>
        </section>
        <section>
            <h2>150 x 150</h2>
            <div class="row">
                <img class="float1" src="./img/150x150.png">
                <img class="float2" src="./img/150x150.png">
                <img class="float3" src="./img/150x150.png">
                <img class="float4" src="./img/150x150.png">
            </div>
        </section>
    </div>
</body>
</html>
```
Demoページ: https://sgnz-vuejs-parallax.herokuapp.com/demo1.html
ソース: https://github.com/NozomiSugiyama/vue-parallax/blob/master/public/demo1.html

画像は [placehold.jp](http://placehold.jp/)さんのを使わせていただいております。

スクロール速度に差があるのがわかると思います。


#### transform: scale()
> scale() という CSS 関数は、二次元平面要素の大きさの変形を定義します。変倍の量がベクトルで定義されるため、水平方向と垂直方向に対して異なる変倍で大きさを変えられます。結果は <transform-function> データ型となります。
https://developer.mozilla.org/ja/docs/Web/CSS/transform-function/scale

次にサイズの変更を行っていきます。
[Demo1](https://sgnz-vuejs-parallax.herokuapp.com/demo1.html) で確認できたかとおもいますが、100px × 100px の画像のサイズがtranslateZによってサイズが大きく変わってきてしまっています。

そこでscale関数にてサイズを元にもどしていきます。

なお、ここから先のコンテンツの配置についてはまだベストな方法を見つけれてないので、いい案がありましたらコメントください。　

今回はperspective を100pxに設定しているので簡単な計算でできます。

#### Demo
```demo2.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>demo2</title>
    <style>
        body, div, section {
            margin: 0;
        }
        section {
            height: 100vh;
        }
        img {
            border: 1px solid black;
        }
        .base {
            perspective: 100px;
            perspective-origin: top center;
            height: 100vh;
            overflow-x: hidden;
            overflow-y: auto;
        }
        .float1 {
            transform: translateZ(0px) scale(1);
            z-index: 1;
        }
        .float2 {
            transform: translateZ(30px) scale(0.7);
            z-index: 2;
        }
        .float3 {
            transform: translateZ(40px) scale(0.6);
            z-index: 3;
        }
        .float4 {
            transform: translateZ(50px) scale(0.5);
            z-index: 4;
        }
        .row {
            display: flex;
            flex-direction: row;
            justify-content: center;
            align-items: center;
        }
    </style>
</head>
<body>
    <div class="base">
        <h1>parallax demo2</h1>
        <section>
            <h2>100 x 100</h2>
            <div class="row">
                <img class="float1" src="./img/100x100.png">
                <img class="float2" src="./img/100x100.png">
                <img class="float3" src="./img/100x100.png">
                <img class="float4" src="./img/100x100.png">
            </div>
        </section>
        <section>
            <h2>150 x 150</h2>
            <div class="row">
                <img class="float1" src="./img/150x150.png">
                <img class="float2" src="./img/150x150.png">
                <img class="float3" src="./img/150x150.png">
                <img class="float4" src="./img/150x150.png">
            </div>
        </section>
    </div>
</body>
</html>

```

Demoページ: https://sgnz-vuejs-parallax.herokuapp.com/demo2.html
ソース: https://github.com/NozomiSugiyama/vue-parallax/blob/master/public/demo2.html

この段階でサイズが整ったかとおもいます。

#### transform-style: preserve-3d
> CSS の transform-style プロパティは、要素の子要素が 3D 空間に配置されるのか、平らにされて要素の面上に配置されるのかを決めます。
平らにされるなら、子要素自身は 3D 空間に存在しなくなります。
https://developer.mozilla.org/ja/docs/Web/CSS/transform-style

次に↑を使い、デバッグをしやすくします。
上下関係は今回3D回転を想定していないのでz-indexでの制御で問題ありませんが、[Demo2](https://sgnz-vuejs-parallax.herokuapp.com/demo2.html)の段階でDevelopment Toolを開いた際に気づいた方もいるかもしれませんが
<img width="1440" alt="Screen Shot 2018-06-24" src="https://qiita-image-store.s3.amazonaws.com/0/265961/25274308-66e7-b990-9c93-4b34179ee66b.png">
このように子要素が3D空間に配置されていないので、表示とDeveleopment Toolのところで差が生まれてしまっています。

#### DEMO
```demo3.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>demo3</title>
    <style>
        body, div, section {
            margin: 0;
        }
        section {
            height: 100vh;
        }
        img {
            border: 1px solid black;
        }
        .base {
            perspective: 100px;
            perspective-origin: top center;
            height: 100vh;
            overflow-x: hidden;
            overflow-y: auto;
        }
        /* 追加 */
        .base * {
            transform-style: preserve-3d;
        }
        .float1 {
            transform: translateZ(0px) scale(1);
            z-index: 1;
        }
        .float2 {
            transform: translateZ(30px) scale(0.7);
            z-index: 2;
        }
        .float3 {
            transform: translateZ(40px) scale(0.6);
            z-index: 3;
        }
        .float4 {
            transform: translateZ(50px) scale(0.5);
            z-index: 4;
        }
        .row {
            display: flex;
            flex-direction: row;
            justify-content: center;
            align-items: center;
        }
    </style>
</head>
<body>
    <div class="base">
        <h1>parallax demo3</h1>
        <section>
            <h2>100 x 100</h2>
            <div class="row">
                <img class="float1" src="./img/100x100.png">
                <img class="float2" src="./img/100x100.png">
                <img class="float3" src="./img/100x100.png">
                <img class="float4" src="./img/100x100.png">
            </div>
        </section>
        <section>
            <h2>150 x 150</h2>
            <div class="row">
                <img class="float1" src="./img/150x150.png">
                <img class="float2" src="./img/150x150.png">
                <img class="float3" src="./img/150x150.png">
                <img class="float4" src="./img/150x150.png">
            </div>
        </section>
    </div>
</body>
</html>
```


Demoページ: https://sgnz-vuejs-parallax.herokuapp.com/demo3.html
ソース: https://github.com/NozomiSugiyama/vue-parallax/blob/master/public/demo3.html

<img width="1440" alt="Screen Shot 2018-06-24" src="https://qiita-image-store.s3.amazonaws.com/0/265961/bdff885a-d75e-712d-eead-f9c4be907ba6.png">

ホバーすれば表示されているDOMとズレなくマッチされたと思います。

## 問題点
解決できない問題があり、横軸の制御です。 ~~つらい~~

<img width="919" alt="Screen Shot 2018-06-24 at 17.28.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/265961/dadf5860-d9cc-0178-5c42-fc095d718bdf.png">

見にくいですがおそらくこんな感じの問題。
配置してー 拡大してー 縮小してー
の際に軸がズレてる感じ。
Demoでの`display: flex;`の計算にtransformは影響されてないっぽい？

ここから先はVue.jsを使っていきます。

## Vue.js

### Component

#### Parallax Base Component

土台用のコンポーネントです。
このコンポーネント下にParallax Float Componentを使うとパララックス効果が発生するようになっています。
↑でのDemoと異なり、perspective-originをdefaultのcenterのままにしています。

```ParallaxBase.vue
<template>
  <div class="ParallaxBase" id="ParallaxBase">
    <slot></slot>
  </div>
</template>

<script>
export default {
  name: 'ParallaxBase'
}
</script>

<style scoped>
.ParallaxBase {
  height:100vh;
  overflow-x:hidden;
  overflow-y:auto;
  perspective:100px;
}
.ParallaxBase >>> * {
  transform-style: preserve-3d;
}
</style>
```

#### Parallax Float Component

視差効果を発生させるコンポーネントです。

```ParallaxFloat.vue
<template>
  <div class="ParallaxFloat" :style="style">
    <slot></slot>
  </div>
</template>

<script>
export default {
  name: 'ParallaxFloat',
  data: () => ({
    style: undefined,
  }),
  props: {
    offset: {
      default: 0.3,
      type: Number
    },
  },
  mounted: function() {
    const rect = this.$el.getBoundingClientRect()

    const left = ((window.innerWidth / 2) - rect.left - (rect.width / 2) ) * this.offset;
    const translateZ = this.offset * 100;
    this.style={ left: `${left}px`, transform: `translateZ(${translateZ}px) scale(${1 - this.offset})`, zIndex: `${this.offset * 10}` }
  },
}
</script>

<style scoped>
.ParallaxFloat {
  transform-style: preserve-3d;
  position: relative;
}
</style>
```

もうゴリ押し実装。
リサイズ非対応...

### 使い方
```vuejs

<parallax-base>
  <div class="space">parallax</div>
  <div class="horizontal">
    <parallax-float :offset="0.8">
      <h3>offset 0.8</h3>
      <img src="http://placehold.jp/150x150.png">
    </parallax-float>
    <parallax-float :offset="0.6">
      <h3>offset 0.6</h3>
      <img src="http://placehold.jp/120x120.png">
    </parallax-float>
    <parallax-float :offset="0.4">
      <h3>offset 0.4</h3>
      <img src="http://placehold.jp/180x180.png">
    </parallax-float>
  </div>
  <div class="horizontal">
    <parallax-float :offset="0.2">
      <h3>offset 0.2</h3>
      <img src="http://placehold.jp/100x100.png">
    </parallax-float>
    <div>
      <h3>Normal</h3>
      <img src="http://placehold.jp/200x200.png">
    </div>
    <parallax-float :offset="-0.2">
      <h3>offset -0.2</h3>
      <img src="http://placehold.jp/180x180.png">
    </parallax-float>
  </div>
  <div class="horizontal">
    <parallax-float :offset="-0.4">
      <h3>offset -0.4</h3>
      <img src="http://placehold.jp/150x150.png">
    </parallax-float>
    <parallax-float :offset="-0.6">
      <h3>offset -0.6</h3>
      <img src="http://placehold.jp/80x80x.png">
    </parallax-float>
    <parallax-float :offset="-0.8">
      <h3>offset -0.8</h3>
      <img src="http://placehold.jp/70x70.png">
    </parallax-float>
  </div>
</parallax-base>
```

`<parallax-base>`の中で自由に`<parallax-float>`をつかってもらう感じで動きます。
初回mount時にleft値を取得、その後transformを適応と同時にleft値を割り当てる形です。
parallax-floatのoffset値に高さを入れます。1に近いほど浮遊率が高い感じです。


Demoページ: https://sgnz-vuejs-parallax.herokuapp.com
ソース: https://github.com/NozomiSugiyama/vue-parallax/blob/master/src/App.vue

紹介してないですが自作アニメーションコンポーネントも合わせて使ってあります。
今後紹介用の記事をかけたらなと思います。

## まとめ

CSSを主にしたソースなのでJSがんがん書いてるのよりは軽いかも。

横幅100vwのみで使うなら横軸のズレは考慮しなくていいが、そうでなければ結構頭使う。
スクロール速度の変更の為に３Dの概念を考慮しながらの作成は結構つかれた。
想定された使用用途ではないって感じがやっぱり強く現状の問題点の解決策もなかなかみつからない。 

とまぁ、いろいろ問題多いけど作りながらいろいろ勉強になったので、まぁよし！