1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【CSS/JS】スマホでスクロール量に応じてインジケーターバーが動くUIをJSライブラリなしで実装する

1
Posted at

こんにちは。

作成するUI

Flexboxで要素を横並びするUIのうち、

  • PCサイズの画面ではスクロールが発生せずにリスト要素を表示できる
  • SPサイズの画面ではスクロールが発生する
    • スクロールする際はスライダーの下にインジケーターをつけ、スクロール量を判別できるようにする

というものを想定します。

ui01.jpg

ui02.jpg

実装したコード

※レスポンシブでの表示が前提となるコードのため、以下のコードはCodePenに遷移したらDevToolでレスポンシブモードにして確認してください。

See the Pen scroll-progressbar-vanilla-js by n4gi-dev (@N4gi-dev) on CodePen.

CSS

UI上特に必要な箇所に絞って記載します。見た目のスタイリングの部分は省略していますのでご了承ください。

ulタグ(要素を横並べする部分)のCSS

.list{
  display: flex;
  overflow-x: scroll;
  scrollbar-width: none;
}

今回のUIでは横スクロールを想定しているのでoverflow-x: scroll;を記述します。

そのままだとブラウザ固有のスクロールバーが表示されます。
今回は代わりにインジケーターを表示させたいためscrollbar-width: none;でデフォルトのスクロールバーを非表示にします。

インジケーター部分のCSS

インジケーターの背景のクラスはmarginでスライダーとの距離をとりつつposition: relative;を設定し、バーはposition: absolute; left: 0 top: 0;で背景の上に乗っかるようにします。

.indicator{
  position: relative;
  margin-top: 20px;
  width: 100%;
  height: 4px;
  z-index: 1;
}

.indicator__bar{
  position: absolute;
  left: 0;
  top: 0;
  width: 100px;
  height: 4px;
  background-color: blue;
  z-index: 2;
}

JavaScript

以下のような関数を作成しました。

function updateIndicator(item1, item2){
  let scrollLeft = item1.scrollLeft;
  let scrollWidth = item1.scrollWidth;
  let clientWidth = item1.clientWidth;
  const visibleRatio = clientWidth / scrollWidth; 
  const scrollRatio = scrollLeft / (scrollWidth - clientWidth);
  
  if(scrollWidth === clientWidth){
    leftPercent = 0;
  }else{
    leftPercent = scrollRatio * (100 - visibleRatio * 100);
  }
  
  const widthPercent = visibleRatio * 100;
  item2.style.left = `${leftPercent}%`;
  item2.style.width = `${widthPercent}%`;
}
  • scrollLeft = item1.scrollLeft;
    • スクロール対象要素の左端からのスクロール量を取得
  • scrollWidth = item1.scrollWidth;
    • スクロール対象要素の「スクロール可能な領域の合計幅」を取得
    • ここでは親要素のwrapperの幅+隠れている幅の合計
  • clientWidth = item1.clientWidth;
    • スクロール対象要素の「要素の内側の幅」を取得
    • ここでは親要素のwrapperの幅を指す
  • const visibleRatio = clientWidth / scrollWidth;
    • 「要素の内側の幅」と「スクロール可能な領域の合計」の比率
    • 要素の内側=見えている領域は、スクロール対象の領域のうちどれだけかを示している
    • 1の場合、要素幅=スクロール領域となるため、スクロールは発生しないことになる
  • const scrollRatio = scrollLeft / (scrollWidth - clientWidth);
    • 左端からのスクロール量と、「スクロールで隠れている領域の幅」の比率
    • スクロールしていない場合だと0
    • スクロールできる限界まで右側にスクロールし切ると1となる
  • let leftPercent;
    • leftのpositionのパーセンテージを決める変数を定義
if(scrollWidth === clientWidth){
  leftPercent = 0;
}else{
  leftPercent = scrollRatio * (100 - visibleRatio * 100);
}
item2.style.left = `${leftPercent}%`;
  • スクロール可能な幅と要素の内側の幅が一致するケースは、スクロールが発生しない場合。その場合はインジケーターは常に左側に位置してれば良いのでleftPercent = 0;
  • そうでない場合(スクロールが発生する場合)
    • バーの全体幅はvisibleRatio の値
    • なので、「バーが移動できる幅の割合」は全体からvisibleRatioの割合を引いた数
    • scrollRatioはどれほどスクロールしたかの割合のため、scrollRatio * (100 - visibleRatio * 100);とすると移動量のパーセンテージを割り出せる
  • ここで設定したleftPercentの値がインジケーターのleftのパーセンテージになる
const widthPercent = visibleRatio * 100;
item2.style.width = `${widthPercent}%`;
  • const widthPercent = visibleRatio * 100;
    • 要素の内側の幅=初期画面で見えている部分の比率を百分率にしたもの
  • widthPercentの値がインジケータのwidthの値になる

上記のようにすることで、

  • 親要素のwidthとスクロール幅に応じてインジケーターの幅が可変する
  • スクロール量によってインジケーターが左右に適切に動く
    といったUIを実現できます。

最後に

ここまで読んで下さりありがとうございます。

SP版のみスライダーのインジケーターを付与するUIの実装をする際に、JSライブラリを使わずに実装する必要が出た時の参考にしていただけば幸いです。

参考資料

Element: scrollWidth プロパティ - Web API | MDN
https://developer.mozilla.org/ja/docs/Web/API/Element/scrollWidth
Element: clientWidth プロパティ - Web API | MDN
https://developer.mozilla.org/ja/docs/Web/API/Element/clientWidth
Element: scrollLeft プロパティ - Web API | MDN
https://developer.mozilla.org/ja/docs/Web/API/Element/scrollLeft

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?