[css+jQuery]パララックスっぽいものを自作

パララックスとは視差効果。
要はスクロールした時とかに「おや?」って感じになるやつ。

環境

jQuery3.5.1

javascriptよりもjQueryに慣れてるのと他のとこでも使ってるから流用するのと、まあそんな感じのやつ。

やろうとしてること

端的に言えばこれが作りたい。
これっぽい動きのやつを作る。

パララックス

画面いっぱいに画像が表示されている。
画面いっぱいというのはwidth:100vw;、height:100vh;。
スクロールするとまた同じ状態で画像が表示される。
画像表示枠はスクロールするけど、画像自体はスクロールしない。
要はbackground-attachment: fixed;。
画像に加えてテキストも表示する。
テキストも画像表示枠に表示領域を依存しつつ、画像と同じようにスクロールしても位置は固定。今回は上下位置的な意味で画面中央。

ナビゲーション

スライドショーのナビゲーション的なものを合わせて設置する。
画像が表示されている限りは画面上に固定表示する。
画面上に画像表示領域がなければ表示しない。
ナビゲーションにはリンクを仕込んで、クリックしたらスムーススクロールさせる。

パララックスの作成

html

<ul class="imagelist">
  <li>
    <div class="wrap">
      <div class="txt">
        <div class="inner">
          <div class="content">
            <p>テキストテキストテキストテキストテキスト</p>
          </div>
        </div>
      </div>
      <div class="img">
        <div class="inner" style="background-image: url('img/01.png');"></div>
      </div>
    </div>
  </li>
</ul>

こんな感じ。
後述のナビゲーションは<li>と対応する。
positionとかを結構使うから階層多めで組む必要がある。

画像を1枚で済ませるならli自体に背景画像を指定すればいいけど、1つの枠に複数並べたいとか、spでは背景として使わないとかを考えたらまあこんな感じにしたほうが無難。

ということで、<li>のサイズは「.img .inner」の個数で依存する。

css

/*	.imageslist
--------------------------------- */
ul.imageslist {
    width: 100%;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    list-style: none;
}
ul.imageslist li {
    width: 100%;
    display: flex;
    position: relative;
    overflow: hidden;
}
ul.imageslist li .wrap {
    width: 100%;
    height: 100%;
    display: block;
    position: relative;
}
ul.imageslist li .wrap .img {}
ul.imageslist li .wrap .img .inner {
    width: 100%;
    height: 100vh;
    background-size: cover;
    background-position: center;
    background-attachment: fixed;
}
ul.imageslist li .wrap .txt {
    width: 100%;
    height: 200px;
    margin-top: -100px;
    position: fixed;
    overflow: hidden;
    top: 50%;
}
ul.imageslist li .wrap .txt .inner {
    overflow: hidden;
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    height: 0;
}
ul.imageslist li.active .wrap .txt .inner {
    height: 100%;
}
ul.imageslist li .wrap .txt .inner .content {
    height: 200px;
    position: absolute;
    bottom: 0;
    left: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
}
ul.imageslist li .wrap .inner .content p {
    color: #fff;
}

こんな感じ。

厄介なのがテキスト部。
普通にfixedとかabsoluteにすると領域を無視して表示され続ける。
不定形だとキツいんでテキスト領域の高さに200px用意して、その中でどうのこうのしますよというルールを作る。
.txtは200px、その中の.innerは下揃えで配置。その中の.contentも200pxに指定。.innerのheightによって.contentの表示部分が変わり、200pxに満たない場合上部が削られる。逆に言うと、.innerのheightが増えると.contentの下の方から表示されていく。jsで数値をうまく指定すれば表示領域と合わせてスクロールに沿ってテキストが表示されるようになる。
表示後消えていく分については<li>の方で勝手に削ってくれるから指定は不要。

jQuery

$(function () {
    var imgslist = 'ul.imageslist';
    var imgsListLiNum = $(imgslist).children('li').length;
    var liTxtHeight = $(imgslist).children('li').find('.txt').height();
    $(window).on('load scroll resize', function () {
        var imgslistHeight = $(imgslist).height();
        var imgslistPosition = $(imgslist).offset().top;
        var imgsListImgHeight = $(imgslist).children('li').find('.img .inner').height();
        var imgslistPositionEnd = imgslistPosition + imgslistHeight - (imgsListImgHeight / 2);
        if ($(window).width() >= 768) {
            for (var i = 1; i <= imgsListLiNum; i++) {
                var liName = imgslist + ' li:nth-child(' + i + ')';
                var liPosition = $(liName).offset().top;
                var liStart = liPosition - ($(window).height() / 2) - (liTxtHeight / 2);
                var liEnd = liStart + $(liName).height();
                var scrollPosition = $(window).scrollTop();
                var slideHeight = scrollPosition - liStart;
                if (liStart <= scrollPosition && scrollPosition < liEnd) {
                    $(liName).addClass('active');
                    $(liName).find('.txt .inner').css('height', slideHeight + 'px');
                } else {
                    $(liName).removeClass('active');
                }
                if ($(liName).hasClass('active')) {
                    $(liName).find('.txt .inner').fadeIn();
                } else {
                    $(liName).find('.txt .inner').fadeOut();
                }
            }
        } else {
            $(imgslist).children('li').removeClass('active');
            $(imgslist).children('li').find('.txt .inner').css('height', liTxtHeight).show();
        }
    })
})

PCのみで使用するとして、その判断はブラウザ幅に依存するとして、768pxで切り替えるようにした。

.txtの高さは指定しないと不安定すぎるからルール化する代わりに、<li>の数は青天井でいけるようにって組んだ。

<li>を.eq()じゃなくて:nth-child()で指定したのは気分です。
cssと何かあるかなって思って統一した方がいいかなって思ったけど、完走して振り返ると何もなかった。

で、<li>の数に柔軟にする代わりに全部を対象に処理する組み方になった。forは便利だけどここらへんはね、もっとうまいやり方があるとは思うけど。

var liStart = liPosition - ($(window).height() / 2) - (liTxtHeight / 2);

ネックというかポイントというか。画面の真ん中に表示したテキスト部を切り替えるようにする、テキスト部の高さは200px、ってとこでこうなる。

組み方全般の解説を書くと面倒が勝つので割愛。

ナビゲーション

html

<section id="parallax">
<ul class="navigationlist">
	<li></li>
</ul>
<ul class="imagelist">
略</ul>
</section>

<li>はパララックスのそれと同じ数だけ生成する。
生成については手入力でもいいし、ついでだからjsでカウントして挿入でもいい。

装飾とかしたければ<li>の中に足せばいい。

css

#parallax { position:relative;}
/*	.navigationlist
--------------------------------- */
ul.navigationlist {
    display: flex;
    flex-direction: column;
    position: fixed;
    right: 40px;
    top: 50%;
    z-index: 10;
    transform: translate(0, -50%);
    display: none;
}
ul.navigationlist li {
    width: 20px;
    height: 20px;
    display: block;
    position: relative;
    cursor: pointer;
}
ul.navigationlist li::before {
    content: "";
    display: block;
    width: 1px;
    height: 20px;
    background: #fff;
    position: absolute;
    right: 50%;
    top: 0;
    transition: right 0.5s;
}
ul.navigationlist li.active::before, 
ul.navigationlist li:hover::before {
    right: 0;
}

まあ、無難な感じで。
.active付与もしくは:hoverで動くよって感じの。

jQuery

$(function () {
    var navList = 'ul.navigationlist';
    var imgslist = 'ul.imageslist';
    var imgsListLiNum = $(imgslist).children('li').length;
    var liTxtHeight = $(imgslist).children('li').find('.txt').height();
    $(window).on('load scroll resize', function () {
        var imgslistHeight = $(imgslist).height();
        var imgslistPosition = $(imgslist).offset().top;
        var imgsListImgHeight = $(imgslist).children('li').find('.img .inner').height();
        var imgslistPositionEnd = imgslistPosition + imgslistHeight - (imgsListImgHeight / 2);
        if ($(window).width() >= 768) {
            for (var i = 1; i <= imgsListLiNum; i++) {
                var liName = imgslist + ' li:nth-child(' + i + ')';
                var liPosition = $(liName).offset().top;
                var liStart = liPosition - ($(window).height() / 2) - (liTxtHeight / 2);
                var liEnd = liStart + $(liName).height();
                var scrollPosition = $(window).scrollTop();
                var slideHeight = scrollPosition - liStart;
                if (liStart <= scrollPosition && scrollPosition < liEnd) {
                    $(liName).addClass('active');
                    $(liName).find('.txt .inner').css('height', slideHeight + 'px');
                } else {
                    $(liName).removeClass('active');
                }
                if ($(liName).hasClass('active')) {
                    $(liName).find('.txt .inner').fadeIn();
                    $(navList + ' li:nth-child(' + i + ')').addClass('active');
                } else {
                    $(liName).find('.txt .inner').fadeOut();
                    $(navList + ' li:nth-child(' + i + ')').removeClass('active');
                }

                if (imgslistPosition <= scrollPosition && scrollPosition < imgslistPositionEnd) {
                    $(navList).fadeIn();
                } else {
                    $(navList).fadeOut();
                }
            }
        } else {
            $(imgslist).children('li').removeClass('active');
            $(imgslist).children('li').find('.txt .inner').css('height', liTxtHeight).show();
            $(navList).hide();
        }
    })
    $(navList + ' li').on('click', function () {
        var navIndex = $(navList + ' li').index(this);
        var speed = 300;
        var target = $(imgslist).children('li').eq(navIndex);
        var position = target.offset().top;
        $('body,html').animate({
            scrollTop: position
        }, speed, 'swing');
    })
})

パララックスが絡むからナビ部のみを抽出した書き方は諦めた。こんなかんじ。

「$(navList + ‘ li’)」とかでliの手前にスペース付けてるのは変数をセレクタにするために必要なやつだから必須。

ナビゲーションの表示部分はこの画像群すべて合わせた領域に紐づくから

        var imgslistHeight = $(imgslist).height();
        var imgslistPosition = $(imgslist).offset().top;
        var imgslistPositionEnd = imgslistPosition + imgslistHeight - ($(window).height() / 2);

これが生きてくる。

まとめというか

スクロールを取得するってのを組む場合、○pxの移動をどうのこうのとかで組んだところにめっちゃ早くスクロールされると数値が取れなくてうまくいかない事が多い。これもご多分に漏れずそう。だからfadeOut()にして、残ったとしてもどうせ消えるし時間差は雰囲気のやつなんですよみたいな言い訳を作って逃げた。つらい。ゆっくりスクロールしても程度問題で、取得する数値自体は飛び飛びだったりするから、ちょっとここは調べないとだ。

「’load scroll resize’」については、コンテンツの途中で再読込されたときの対策、本来の動作として当然、で、「resize」。画面幅が変わると表示位置がズレることはままある話なので、これ関係を仕込むときは入れておいたほうがいい。

コメント

タイトルとURLをコピーしました