CSS Deep-Dive - フレーム完璧なカスタム スクロールバーのための matrix3d()

カスタム スクロールバーはめったにありません。ほとんどの場合、 スクロールバーは 編集不可(日付選択ツール)です。 JavaScript を使用して独自に作成することもできますが、それには費用がかかり、 遅延を感じる場合がありますこの記事では、 型にはまらない CSS マトリックスを使用して、カスタム スクローラーを作成します。 スクロール中に JavaScript を生成します。

要約

小さなことは気にしないでしょ?必要なのは にんちゃんのデモ 図書館を手に入れるの?デモのコードは GitHub リポジトリ

LAM;WRA(Long と数学的、いずれにせよ読み上げられる)

以前、Google はパララックス スクローラーを開発しました。 該当する記事をご覧ください。 時間をかける価値は十分にあります)。CSS 3D を使用して要素をプッシュバックする 要素が実際のスクロール速度より遅く移動しました。

内容のまとめ

まず、パララックス スクローラーの仕組みを復習しましょう。

アニメーションに示すように、要素をプッシュすることで視差効果を実現しています。 3D 空間の Z 軸に沿った「後方」ドキュメントをスクロールすると、 Y 軸に沿った平行移動が表示されますたとえば 100 ピクセル分だけにスクロールした場合、 要素は 100 ピクセル上方向に翻訳されます。これはすべての要素に当てはまりますが、 「さらに後戻り」のジョブでもかまいませんしかしなぜ彼らは 観測された画面上の動きが 100 ピクセル未満であることから、 目的の視差効果を適用します。

もちろん要素を空間に戻すと小さくなりますが 要素をスケールアップして修正します。正しい計算方法を見つけました Google が開発した パララックス スクローラー 詳しい説明は省きますが

ステップ 0: 何をしたいのか?

スクロールバー。私たちが構築するのは、これだけです。データ アナリストが 業務内容についてですもちろんです。スクロールバーは 利用可能なコンテンツの進捗度進捗度 考えなければなりません下にスクロールすると、スクロールバーが 最終段階に近づいていることを示すことができます。すべての内容が スクロールバーは通常は非表示になります コンテンツの高さがビューポートの 2 倍の場合、 スクロールバーがビューポートの高さの 2 分の 1 まで表示されます。高さの 3 倍のコンテンツ ビューポートがスクロールバーをビューポートの 3 分の 1 などに拡大縮小します。 スクロールする代わりに、スクロールバーをクリックしてドラッグし、 サイトをより迅速に提供できます目立たない人にとっては 目立たない人にとっては といった要素です。一つずつ戦いましょう。

ステップ 1: 逆に並べる

CSS 3D を使用すると、要素の移動速度をスクロール速度より遅くすることができます 視差スクロールの記事で概説されているように、逆に、元のバージョンを 方向性は?それが可能なこと、そしてそれが フレームにぴったりなカスタム スクロールバーです。この仕組みを理解するには まずは CSS 3D の基本から説明します。

数学的な視点から見た投影法は、 最終的には 同種座標。 その機能とその理由については詳しく説明しませんが、 w という 4 つ目の座標が追加された 3D 座標のようなものです。この 座標は、視点の歪みを持たせる場合を除き、1 にする必要があります。水 ここでは何も使用しないため、w の詳細は気にする必要はありません。 指定することもできます。したがって、すべての点はこれから 4 次元ベクトルになります。 [x, y, z, w=1] なので、行列は 4x4 にします

ある例として、CSS では フードは、 matrix3d() 関数を使用します。matrix3d は 16 個の引数を取ります(行列が 4x4 など)を使用して、列を次々と指定します。この関数を使用して 回転や翻訳などを手動で指定できますが その w 座標がおかしいということです。

matrix3d() を使用する前に、3D コンテキストが必要です。これは、 3D コンテキストでは遠近感の歪みはなく、 同次座標系が作成されます。3D コンテキストを作成するには、 perspective と、その内部で新しく変換できる要素 作成しました。対象 :

CSS コードを使って、この CSS の要素を使って div を変形させる
    パースペクティブ属性。

Perspect コンテナ内の要素が CSS エンジンによって処理される 次のとおりです。

  • 要素の各角(頂点)を同次座標系に変換する [x,y,z,w](パースペクティブ コンテナを基準とする)
  • 要素のすべての変換を右から左に行列として適用します。
  • 視点要素がスクロール可能な場合は、スクロール マトリックスを適用します。
  • 遠近感マトリックスを適用します。

スクロール マトリックスは y 軸に沿った変換です。下にスクロールすると、 400 ピクセルの場合は、すべての要素を 400 ピクセル分上に移動する必要があります。パースペクティブ マトリックスは 点を 3 次元で消失点の近くに「引っ張る」行列 できます。このようにすると、小さくなったときに 逆方向になり、翻訳する際に「動きが遅くなります」。 そのため、要素がプッシュバックされた場合、400 ピクセルの移動により要素が 画面上で 300px だけ移動します

詳細は、 CSS の仕様 概要を示していますが、この記事では説明のため、簡略化して アルゴリズムです。

ボックスは、perspective の値が p の視点コンテナ内にあります。 コンテナがスクロール可能で、 n ピクセル。

視点行列 × スクロール 行列 × 要素変換行列
  4 行目が 4 行 4 列の単位行列で、p に対してマイナス 1 が等しい
  3 列目に 4 行 4 列の単位行列を乗算し、2 列目にマイナス n とする
  行 4 列目に要素変換行を掛けた値。

1 つ目の行列は視点行列、2 つ目の行列はスクロールです 表します。まとめると、スクロール マトリックスの役割は、スクロール アクションによって要素が上に移動することです。 下にスクロールするため、負の符号が付けられます。

一方、スクロールバーでは反対の要素、つまり 下方向にスクロールする。ここで、 ボックスの角の w 座標を反転します。w 座標が -1 の場合、すべての翻訳が反対の方向に有効になります。どのように ?CSS エンジンがボックスの角を w を 1 に設定します。matrix3d() が輝くときがやってきました!

.box {
  transform:
    matrix3d(
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, -1
    );
}

この行列は w を否定するだけです。CSS エンジンが 各隅を [x,y,z,1] 形式のベクトルに変換すると、行列は [x,y,z,-1] に変換します。

4 行目が p よりマイナス 1 の 4 行 4 列の単位行列
  3 列目に 4 行 4 列の単位行列を乗算し、2 列目にマイナス n とする
  行 4 列目に 4 行 4 列の単位行を掛け、マイナス 1 が
  第 4 行 4 列 x 4 次元ベクトル x, y, z, 1 は 4
  4 行目の 3 列目にマイナス 1 が 4 の単位行列となります。
  2 行目の 4 列目にマイナス n、4 行目に 1 をマイナスします。
  4 列目は、4 次元ベクトル x、y と n、z、z を引いた値と等しくなります
  されます。

要素変換の効果を示す中間ステップをリストしました。 表します。行列の計算が苦手な場合でも問題ありません。ユーレカ号 最後の行では、スクロールオフセット n を y の値に 座標の差を求めるためです。要素は下方向へ翻訳されます にスクロールします。

しかし、このマトリックスをモデルに 要素は表示されません。これは、CSS の仕様で、すべての Pod に w < である頂点0 は要素のレンダリングをブロックします。Z は 座標が現在 0、p が 1 の場合、w は -1 になります。

幸い、z の値を選択できます。w=1 にするには、次が必要です。 z = -2 に設定します。

.box {
  transform:
    matrix3d(
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, -1
    )
    translateZ(-2px);
}

見よ、私たちの box が戻ってきた

ステップ 2: 移動する

今のこの箱はこうして出来上がっています 学びます現時点では、視点コンテナはスクロールできないため、 要素は見えても要素が逆向きになることがわかっています スクロールしました。では、コンテナをスクロールしてみましょう。追加することもできます。 スペースを占有するスペーサー要素:

<div class="container">
    <div class="box"></div>
    <span class="spacer"></span>
</div>

<style>
/* … all the styles from the previous example … */
.container {
    overflow: scroll;
}
.spacer {
    display: block;
    height: 500px;
}
</style>

次に ボックスをスクロールしてください。 赤いボックスが下に移動します。

ステップ 3: サイズを指定する

ページを下にスクロールすると下に移動する要素があります。これこそが 手間がかかります次はスクロールバーのように見えるように スタイルを設定し インタラクティブにできます。

スクロールバーは通常、「つまみ」と「トラック」で構成されますが、トラックは「トラック」ではありません 表示されます。親指の高さは 表示されます。

<script>
    const scroller = document.querySelector('.container');
    const thumb = document.querySelector('.box');
    const scrollerHeight = scroller.getBoundingClientRect().height;
    thumb.style.height = /* ??? */;
</script>

scrollerHeight はスクロール可能な要素の高さで、 scroller.scrollHeight は、スクロール可能なコンテンツの高さの合計です。 scrollerHeight/scroller.scrollHeight は、コンテンツに対する次の割合です。 表示されます。つまみカバーの縦方向のスペースの比率は、 表示されるコンテンツの割合:

<ph type="x-smartling-placeholder">
</ph> サムドット スタイルのドットの高さがスクローラーの高さを超える場合、スクローラーの高さと等しい
  スクロールバーのドットの高さが親指ドットのスタイルでドット高さに設定されている場合のみ
  スクローラーの高さにスクローラーの高さのスクローラーの高さを乗算した値に等しい
  あります。
<script>
    // …
    thumb.style.height =
    scrollerHeight * scrollerHeight / scroller.scrollHeight + 'px';
    // Accommodate for native scrollbars
    thumb.style.right =
    (scroller.clientWidth - scroller.getBoundingClientRect().width) + 'px';
</script>

親指の太さは いい感じ、 進化のスピードが速すぎますここで 高度な分析手法から パララックス スクローラーです。要素をさらに戻ると、移動が遅くなり、 使用できます。サイズを修正するには、スケールアップします。ただし、 どうすれば復元できるでしょうか。ご想像のとおり、計算をしてみましょう。今回が最後です。 あります。

重要なのは、親指の下端を スクロール可能な要素を下端までスクロールしたときに、下端に揃えられる できます。つまり、スクロールした場合、 scroller.scrollHeight - scroller.height ピクセルの場合、親指を scroller.height - thumb.heightに翻訳されました。スクローラーのピクセルごとに、 次のようにして、親指を 1 ピクセル動かすことができます。

係数は、スクローラーのドットの高さ - スクローラー上の親指ドットの高さを引いた値に等しい
  ドットのスクロール高さからスクローラーのドット高さを引いた値になります。

これがスケーリング ファクタです。次に、スケーリング ファクタを 視差スクロールですでに行った Z 軸に沿った平行移動 の記事を参照してください。「 仕様の関連セクション: スケーリング ファクタは p/(p - z) と等しくなります。この方程式を z について解くと、 親指を z 軸にどの程度平行移動させるかを考えてみましょう。でも、 w 座標の乱用により、1 次元の座標を 1 次元に z 方向にある追加の -2px。また、要素の変換は複数の要素に適用され、 右から左に表記する必要があります この特別なマトリックスより前には ただし、この特別なマトリックスより後の翻訳はすべて反転します。では、 体系化してください。

<script>
    // ... code from above...
    const factor =
    (scrollerHeight - thumbHeight)/(scroller.scrollHeight - scrollerHeight);
    thumb.style.transform = `
    matrix3d(
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, -1
    )
    scale(${1/factor})
    translateZ(${1 - 1/factor}px)
    translateZ(-2px)
    `;
</script>

Google には、 scrollbar! そしてこれは DOM 要素にすぎません。自由にスタイルを設定できる要素です。一つは、 アクセシビリティに関して重要なのは、親指を 多くのユーザーはこの方法でスクロールバーを操作できるため、 このブログ投稿を長くするため、ここでは説明は その部分の詳細が表示されます。詳しくは、 ライブラリ コード をご覧ください。

iOS についてはどうですか?

旧友の iOS Safari だ。パララックススクロールと同様に 見てみましょう。要素上をスクロールしているため、 -webkit-overflow-scrolling: touchします。ただし、その場合は 3D のフラット化と スクロール効果が機能しなくなりますこの問題はパララックス スクローラーで解決されました iOS Safari を検出し、回避策として position: sticky を使用する ここではまったく同じことをします。詳しくは、 パララックス記事 思い出してください。

ブラウザのスクロールバーについてはどうですか?

システムによっては、永続的なネイティブ スクロールバーの処理が必要になります。 これまでは、スクロールバーを非表示にすることはできませんでした(ただし、 非標準疑似セレクタ)。 隠すには、(数学のない)ハッキングに頼る必要があります。Google は overflow-x: hidden を使用してコンテナ内のスクロール要素を作成し、 スクロール要素の幅がコンテナより広くなります。ブラウザのネイティブのスクロールバーは 非表示になります。

Fin

これらをまとめると、フレームに最適なカスタム スクロールバーを ニャン猫のデモ

にんちゃんが見えない場合は、 報告したバグが (親指をクリックして Nyan 猫が表示される) Chrome は不要な作業を避けるのが得意です 画面に表示されないものを描画したり アニメーション化したりできます残念ながら マトリックスの不正プログラムにより、ねこに猫の GIF が実際に画面に表示されないようになっている。 この問題が早急に解決されることを願っております。

これで準備は完了です。これは大変な作業でした。最後まで読んでくれたことを感謝します あります。これは、 手間がかかるので 労力に見合う価値は ほとんどありませんが ただし、カスタマイズされたスクロールバーがエクスペリエンスに不可欠な要素である場合は除きます。しかし、 可能だとわかってよかったです。これほど難しいのは カスタム スクロールバーは、CSS 側で行うべき作業があることを示します。でも大丈夫! 将来的には HoudiniAnimationWorklet は、 スクロールに連動してフレームに完全に収まる効果を出す方がはるかに簡単です。