スクロールした分だけ透明度が変化するJavaScript

スクロールした分だけ透明度が変化するJavaScript

SOUNDPEATS CC イヤーカフ ワイヤレスイヤホンレビュー

【期間限定】2024年12月31日まで!
カフ型ワイヤレスイヤホン 特別クーポン配布中!

縦スクロールを横スクロールに変換するJavaScript

#222 スクロール変換

WEB制作

縦にスクロールした分だけ、横にスクロールするJavaScriptを応用して、その場にとどめながら透明度を変化させるJavaScriptを作成しました。 スクロールしてもその場にとどまるので画像をしっかり見てもらえるようにするのが目的です。

公開日:
最終更新日:

商品リンクにアフィリエイト広告を利用しています

サイトマップサイトマップ

JavaScript 縦スクロールを横スクロールに変換してとどめたい

スクロールしても一定量、スクロールしないと動かない、そんな仕組みを作りたいと思いまして。

何言ってるか伝わりづらいと思うのですが、ページを閲覧するときにスクロールしますよね。
で、縦にスクロールするのが普通だと思うんですが、それの移動量を横スクロールに変換したいんです。

正確に言うと、横スクロールに変換することで、その領域に強制的にとどめる仕組みを作りたかったと。

なお、この手のどこに需要があるのかわからない解説記事は大体自分のために書いております。

言葉で伝えてもわかりづらいと思うので以下のサンプルをご覧ください。

横スクロールエリア

一定量、スクロールしないと先に進みません。
スクロール量に応じて、画像が変化します。

Kazakiri 剃刀 Kazakiri 剃刀
a
b

サンプル画像のカミソリは「kazakiri」というブランドの1枚刃の替刃が安いオススメのカミソリです。
よかったらこちらの記事もどうぞ。

画像をじっくり見てほしいわけです。

なんでこんなものを作ったかと言いますと、画像をじっくりみて欲しいためです。
正確には、変化する画像をちゃんと見てほしい、というか、強制的に見させたかったと。

動作の仕様と仕組みの解説

どういう仕組みで動いているかというと、スクロールをとどめるCSSの「postion:sticky」を使って表示エリアを固定します。

裏側で、横スクロールエリアが設定されていまして、縦スクロールを横スクロールに変換しています。

そのスクロール量を%(パーセンテージ)に変換して、画像のアルファ値(透明度)を変換して、画像を徐々に変化させております。

画像変化について

同じサイズの画像を用意しまして、同じ位置に画像を重ねます。

Kazakiri 剃刀
ベースの画像 最初に表示される
Kazakiri 剃刀
上に置く画像 透明度を変化させる

ベースの画像を最初において、徐々に出てくる画像を後ろに置いて、2枚目の後ろに置いてある画像に対してアルファ値の変更をかけるわけです。

それにより、スクロールに応じて画像が徐々に変化するモーションが作れます。

コード解説と謝辞

思いついたきっかけとして、スクロールを無視するアクションの方法を考えたときに、確か横スクロールさせるスクリプトあったよな、と思い検索しました。

そこで参考にさせていただいたのが UNICO LABO様の以下のページです。

読み込み中

横スクロールさせるスクリプトはいくつか有ったのですが、解説がわかりやすく、素のJavaScriptで作られていて、応用するのが簡単そうだったため。

ベースのコードはUNICO LABO様をご覧ください

横スクロールさせる仕組みは大本のUNICO LABO様のページをご覧いただくのがわかりやすいと思います。
サンプルのコードも公開されていて、DEMOページもご用意されています。

改造を施した箇所とそれぞれの数値変化

コードはサンプルをそのまま貼り付ければ、実装できるわけですが、それを応用しようとすると、どういう仕組みで動いているかを理解する必要があります。

加えて、私が実現したい内容がちょっと今回のものと異なるので、こちらをベースに手を加えていきます。

基本動作を再確認する

改造したJavaScriptの全体です。

このBLOGで使うために、2カラムでも動くように変更したのと、横スクロールを発動させる係数をちょっといじりました。
その他、クラス名やタグをちょっと変更しております。

スクロールエリアのHTML

<div class="sc_change_wrapper">
    <div class="sc_sticky">
        <h2>横スクロールエリア</h2>
        <p>一定量、スクロールしないと先に進みません。<br>
        スクロール量に応じて、画像が変化します。</p>
        <div class="sc_item_set">
            <img src="画像のパス" width="760" height="507" > 
            <img src="画像のパス" width="760" height="507" id="test" class="abs">
        </div>
        <div class="sc_scroll_wrapper">
            <div>
              a
            </div>
            <div>
              b
            </div>
         </div>  
     </div>
</div>        

改造したJavaScript

"use strict";

window.addEventListener("load", () => {
    const screenWidth = window.innerWidth;
    const blog_body = document.getElementById("blog_body");

    let window_size = document.body.clientWidth;
    let window_margin = 0;

    if (screenWidth > 768) {
        window_size = blog_body.clientWidth;
        window_margin = window_size;
    }

    const stickyContainers = document.querySelectorAll(".sc_change_wrapper");

    stickyContainers.forEach((stickyContainer, index) => {
        const stickyItem = stickyContainer.querySelector(".sc_sticky");
        const scroller = stickyContainer.querySelector(".sc_scroll_wrapper");

        scroller.classList.add("nobar");

        const updateStickyHeight = () => {
            const stickyHeight = scroller.scrollWidth - scroller.clientWidth + stickyItem.clientHeight;
            stickyContainer.style.setProperty("--sticky-container-height", `${stickyHeight}px`);
        };

        updateStickyHeight();
        new ResizeObserver(updateStickyHeight).observe(scroller);

        const syncScroll = () => {
            const rect = stickyContainer.getBoundingClientRect();
            if (rect.top <= 0 && rect.bottom >= window.innerHeight) {
                scroller.scrollLeft = rect.top * -1;
            }else if(rect.top >= 1){
                scroller.scrollLeft = 0;
            }
            const alfa = scroller.scrollLeft / (scroller.scrollWidth - window_size);
            document.getElementById("test").style.opacity = alfa;
            document.getElementById("test2").style.opacity = alfa;
        };

        const boundSyncScroll = syncScroll.bind(this);
        const observer = new IntersectionObserver(
            (entries) => {
                entries.forEach((entry) => {
                    if (entry.isIntersecting) {
                        window.addEventListener("scroll", boundSyncScroll, { passive: true });
                        syncScroll();
                    } else {
                        window.removeEventListener("scroll", boundSyncScroll);
                    }
                });
            },
            { threshold: 0 }
        );
        observer.observe(stickyContainer);
    });
});

スクロール発動のJavaScriptを解説

const syncScroll = () => {
    const rect = stickyContainer.getBoundingClientRect();
    if (rect.top <= 0 && rect.bottom >= window.innerHeight) {
        scroller.scrollLeft = rect.top * -1;
    }else if(rect.top >= 1){
        scroller.scrollLeft = 0;
    }
    const alfa = scroller.scrollLeft / (scroller.scrollWidth - window_size);
    document.getElementById("test").style.opacity = alfa;
    document.getElementById("test2").style.opacity = alfa;
};

stickyContainerは、stickyのかかる領域になります。

const rect = stickyContainer.getBoundingClientRect();
で、stickyContainerの位置情報を取得し、そのTOPの位置が0以下の場合、かつ、下端がビューポートの下部よりしたの場合に発動します。

それがこのif文です。
if (rect.top <= 0 && rect.bottom >= window.innerHeight)

rectで指定されているエリアの位置情報の変動がよくわからなかったので、console.logで確認しながら、望み通りの数値が取れるように調整しました。
数値変動を表記させたものが以下です。

横スクロールのエリアはheight:0にして非表示にしていますが、それも表示するようにしてあります。

Kazakiri 剃刀 Kazakiri 剃刀
a
b

▼変動する数値の詳細

rect_top:0
rect_bottom:0
window.innerHeight:0
scroller.scrollWidth:0
window_size:0
scrollLeft:0
alfa:0
window.scrollY:0

スクロール量の取得と変換

rect.topが指しているのが以下。
const rect = stickyContainer.getBoundingClientRect();

stickyContainerがヴューポートの上端からどれだけ離れているかを取得できます。

stickyは常に指定した位置に留まるのですが、それは指定した要素の上部に疑似的に固定されているだけで、要素(divコンテナ)はスクロールされているため、数値が変動します。

なので、この数値をそのまま横スクロール量に当てました。
ただ、上端が上に行くほどマイナス値になるので、-1を変えて整数に変換しています。

scroller.scrollLeft = rect.top * -1;

アルファ値の取得と変換

スクロール量に応じてアルファ値(透明度)に変換します。

const alfa = scroller.scrollLeft / (scroller.scrollWidth - window_size);

横スクロールの移動量を表示領域に対して、何%移動したかを取得しています。

window_sizeはブログの記事エリアの横幅を取得しています。
レスポンシブでPCとスマホで横幅が変わるので表示サイズに応じて取得する領域を変更しています。

const screenWidth = window.innerWidth;
const blog_body = document.getElementById("blog_body");

let window_size = document.body.clientWidth;
let window_margin = 0;

if (screenWidth > 768) {
    window_size = blog_body.clientWidth;
    window_margin = window_size;
}

スクロールエリアの横幅から、表示領域を引いて、それをスクロール量で割ることで、パーセンテージを算出し、それをアルファ値にあてていると。

スクロール量を増やせば透明化の速度を調整できる

横スクロールエリアの要素を増やしたり、横幅のサイズを調整すると、スクロール量が増えます。
ので、増えた分だけ透明化の速度が遅くできます。

<div class="sc_scroll_wrapper">
    <div>
      a
    </div>
    <div>
      b
    </div>
</div>      

透明度の調整以外もいろいろできそうな予感

まだやってないんですけど、この仕組みを応用することで、色々おもしろいことができそうな予感があります。

CSSのアニメーションを組み合わせたり、シンプルに横スクロールでなにかを表示させたり。

効果的に使えるかどうかが未知数ですが、なにか思いついたら組み込んでみたいと思っています。

ユーザー体験としてどうなのかがちょっと心配

参照元のUNICO LABOさんも書かれていましたが、ユーザー体験的に、スクロールしてもスクロールしないという動作が受け入れられるかどうかがちょっと不安ですね。

スクロールしたことで起きている変化がわかりづらいと、とまどいを与えそうではあります。

そういう違和感を起こさせること自体は必ずしも悪いことじゃないと個人的には思っていますが、それが不快に感じられるとマイナスにしかならないので、使い方を間違えないようにしたいと思います。

使用上の注意と対策

色々試行錯誤をして、違和感なく動くようにひとまず作れたのですが、1つだけ問題点があります。

sticky要素の高さが、横スクロールエリアよりも小さい場合、スクロールが途中で終了します。

これは、stickyでとどめていた要素の高さ分、横スクロールに変換しているのですが、縦スクロールが終わってしまうと、変換するスクロール量がなくなるため。

これを防ぐために、stickyエリアに充分な要素を確保する必要があります。

インターセクションオブザーバーを覚えておいてよかった

今回のコードに交差監視のインターセクションオブザーバーがでてきましたが、すでに私は解説記事を通して理解を深めておいた良かったと思います!

ここの仕組みをちゃんと理解してなければ(ちゃんとは理解してないんだけど)、あそこで躓いていたと思います。

インターセクションオブザーバーの解説記事はこちら

横スクロールの作り方はこちらにあります

WEBデザイナー・プログラミング学習にオススメ

記事内で紹介している商品リスト
(価格はAmazon参考価格)

Kazakiri シュタルクウッド 感動深剃り カミソリ 【プロ理容師が監修】 髭剃り 顔剃り 替刃40刃(20枚)付き 西洋剃刀 メンズ

2,580円(税込)

Kazakiri シュタルクウッド 感動深剃り カミソリ 【プロ理容師が監修】 髭剃り 顔剃り 替刃40刃(20枚)付き 西洋剃刀 メンズ

独習JavaScript 新版

2,950円(税込)

独習JavaScript 新版

ステップアップJavaScript フロントエンド開発の初級から中級へ進むために

2,376円(税込)

ステップアップJavaScript フロントエンド開発の初級から中級へ進むために

JavaScript 第7版

5,060円(税込)

JavaScript 第7版

いちばんやさしいJavaScriptの教本 第2版 ECMAScript 2017(ES8)対応 人気講師が教えるWebプログラミング入門 「いちばんやさしい教本」シリーズ

2,372円(税込)

いちばんやさしいJavaScriptの教本 第2版 ECMAScript 2017(ES8)対応 人気講師が教えるWebプログラミング入門 「いちばんやさしい教本」シリーズ

図解! JavaScriptのツボとコツがゼッタイにわかる本 “超”入門編

2,138円(税込)

図解! JavaScriptのツボとコツがゼッタイにわかる本 “超”入門編

JavaScript コードレシピ集

3,212円(税込)

JavaScript コードレシピ集

JavaScript[完全]入門

2,653円(税込)

JavaScript[完全]入門

スクロール変換の関連ページ

SNSでこの記事をシェアできます

ブログ デイリーアクセスランキング

NAS DS224+
1位

NAS DS224+

SynologyのNAS DS224+を購入。 メモリ増設が可能なため一緒に16GBのメモリも購入して増設してみました。

SOUNDPEATS CC イヤーカフイヤホン
2位

SOUNDPEATS CC イヤーカフイヤホン

SOUNDPEATSのカフタイプのワイヤレスイヤホンのレビューです! はじめてのカフタイプイヤホンは初体験がたくさんあり!

配色ジェネレーター
3位

配色ジェネレーター

色相環からカラーを選択し、クリックすると配色例を表示します! 24色×16トーンからお好みのカラーに合わせて利用可能!

ノートPCスタンド
4位

ノートPCスタンド

ノートパソコンを横にしまうと結構な専有面積があるのですが、立てて収納できればわずか数cmで使えるので本当に快適です!

PHPで作るカレンダー
5位

PHPで作るカレンダー

forとifとforeachがメインのPHPによるカレンダーの作り方です。 祝日設定がちょっとめんどくさいんですよね。

車用スマホホルダー
6位

車用スマホホルダー

スマホをカーナビとして使うと熱さで充電ができなくなるので、熱対策と1年間使って外れることがなかったスマホホルダーの紹介。

2台用PCスタンド
7位

2台用PCスタンド

ノートパソコンを2台置けるタイプのノートパソコンスタンドのレビューです。 購入して失敗したなぁというポイントも紹介!

アンカーリンクナビ
8位

アンカーリンクナビ

JavaScriptの「Intersection Observer」を使い、ナビゲーションが変化するアンカーリンクを作りました。

東京タワー夜桜撮影
9位

東京タワー夜桜撮影

東京タワーと夜桜をPentax Q7で撮影。お花見スポット「芝公園」より「増上寺」よりも豆腐屋の前からの景色が最高でした。

iPhone16一括購入
10位

iPhone16一括購入

iPhone16をAmazonのアップルストアで一括購入。 残価設定のプログラムと比較してオトクかシミュレーション。

カテゴリ一覧

オススメ商品レビュー

オススメ商品レビュー

世の中にある比較サイトや、いかがでしたか系サイトが嫌いなんです。使ってないのにえらそうなこと言うなと。なので体験談です。

WEB制作

WEB制作

WEBサイトを作れますと一言に言っても色々な技術があるわけですよ。アウトプットの形は一緒でもいろいろこだわりがあるんです。

カメラ

カメラ

2018年4月に購入したソニーのミラーレス一眼「α7Ⅲ」に関連する記事一覧ページです。 作例集やカメラグッズレビューなど。

美味しいもの

美味しいもの

食に関してはあまりこだわりがないんですけどね、こだわりがない分美味しいと思ったものは本当においしいと思ったものなんですよ

Amazonプライム動画

Amazonプライム動画

Amazonプライム会員なら無料で利用できるAmazonプライム動画から、いろいろな動画をみたレビュー記事のまとめページ

旅LOG

旅LOG

家族旅行やおでかけした際の旅ブログです。夏休みの家族旅行(4人家族)で訪れた観光名所の感想などを記しています。

プロフィール

プロフィール

Start-Point.netの管理人のプロフィール紹介を兼ねた、自分の中のルールや決め事やエピソードなでお書いていきます

日記

日記

日記と言っても色々な日記があるわけで、記録的な日記や心理描写を色濃く描いた日記などをまとめたページです。

thanks

thanks

自分を表現することって意外と難しいんですよね。照れがあったり、間違ったこといってないかとよくわからない何かと戦ったりして。

マネーリテラシー

マネーリテラシー

お金や税金、資産形成に関する情報や体験談をブログでお伝え!

記事内で紹介している商品

Kazakiri シュタルクウッド 感動深剃り カミソリ 【プロ理容師が監修】 髭剃り 顔剃り 替刃40刃(20枚)付き 西洋剃刀 メンズ

2,580
(税込)