メインコンテンツまでスキップ

スクロールビュー

スクロールビューは、大きなコンテンツの一部を表示し、ユーザーがコンテンツをドラッグしたりマウスホイールを回したりして、残りの部分を表示できるようにします。リスト、インベントリ、長いテキスト、マップは、いずれもスクロールビューを使います。スクロールビューは、いくつかのエンティティで構成されます。

scroll view グループエレメントとScrollViewコンポーネント
├── viewport マスクになっているイメージエレメント:表示される領域
│ └── content ビューポートより大きく、入力が有効なエレメント
│ └── … スクロールされるアイテム
└── scrollbar イメージエレメントとScrollbarコンポーネント(省略可)
└── handle 入力が有効なイメージエレメント。バーに沿ってドラッグされる

ビューポートはマスクなので、コンテンツはその内側にだけ描画されます。何もない部分でもドラッグできるように、コンテンツでは入力を有効にする必要があります。

スクロールビューは、コンテンツのローカル位置を設定してコンテンツを動かします。ローカル位置は、コンテンツが左上までスクロールされたときに0, 0になります。そのため、コンテンツはビューポートの左上隅か上辺全体にアンカーし、左上のピボット[0, 1]を指定します。それ以外のピボットでは、コンテンツの一部が最初から表示範囲の外に出てしまいます。

Scroll View

スクロールビューの作成​

この300 × 400のスクロールビューは、それより縦に長い列を垂直方向にスクロールし、右側にスクロールバーがあります。

// スクロールビュー。スクリーンの中央に300 × 400で配置する
const scrollView = new pc.Entity('scroll view');
scrollView.addComponent('element', {
type: pc.ELEMENTTYPE_GROUP,
anchor: [0.5, 0.5, 0.5, 0.5],
pivot: [0.5, 0.5],
width: 300,
height: 400
});
screen.addChild(scrollView);

// ビューポートは、右側のスクロールバー用の20単位を除いてスクロールビューを埋める
const viewport = new pc.Entity('viewport');
viewport.addComponent('element', {
type: pc.ELEMENTTYPE_IMAGE,
anchor: [0, 0, 1, 1],
margin: [0, 0, 20, 0],
mask: true
});
scrollView.addChild(viewport);

// コンテンツはビューポートの左上隅からぶら下がる
const content = new pc.Entity('content');
content.addComponent('element', {
type: pc.ELEMENTTYPE_GROUP,
anchor: [0, 1, 0, 1],
pivot: [0, 1],
width: 280,
height: 1200,
useInput: true
});
viewport.addChild(content);

// 右端に沿ったスクロールバーと、そのハンドル
const scrollbar = new pc.Entity('scrollbar');
scrollbar.addComponent('element', {
type: pc.ELEMENTTYPE_IMAGE,
anchor: [1, 0, 1, 1],
pivot: [1, 1],
margin: [0, 0, 0, 0],
width: 20,
color: new pc.Color(0.16, 0.18, 0.23)
});
scrollView.addChild(scrollbar);

const handle = new pc.Entity('handle');
handle.addComponent('element', {
type: pc.ELEMENTTYPE_IMAGE,
anchor: [0, 1, 1, 1],
pivot: [1, 1],
margin: [0, 0, 0, 0],
color: new pc.Color(0.5, 0.55, 0.65),
useInput: true
});
scrollbar.addChild(handle);
scrollbar.addComponent('scrollbar', {
orientation: pc.ORIENTATION_VERTICAL,
handleEntity: handle
});

scrollView.addComponent('scrollview', {
viewportEntity: viewport,
contentEntity: content,
verticalScrollbarEntity: scrollbar,
horizontal: false,
vertical: true,
scrollMode: pc.SCROLL_MODE_BOUNCE,
bounceAmount: 0.1,
friction: 0.05
});

アプリケーションを作成するときに、pc.ScrollViewComponentSystemとpc.ScrollbarComponentSystemを登録し、マウスでドラッグできるようにpc.Mouseも渡します。コードから作成したスクロールビューには、デフォルトの設定がありません。horizontalかverticalを設定するまではどちらの軸にもスクロールせず、scrollMode、bounceAmount、frictionがないと警告をログに出力し、跳ね返らず、離した瞬間にぴたりと止まります。ここでのように、追加するときにすべてを渡してください。

スクロールの動作​

プロパティ効果
horizontal, verticalコンテンツをスクロールできる軸
scrollModeClampはコンテンツを端で止めます。Bounceは端を越えて動かし、ばねのように引き戻します。Infiniteはどこまでもスクロールさせます
bounceAmountBounceモードでコンテンツが引き戻される遅さ。0ではすぐに戻り、0.1ではスマートフォンでのスクロールのような感触になり、値が大きいほど遅くなります
frictionフリックされた後にコンテンツが減速する速さ(0から1)。1ではすぐに止まります
dragThresholdドラッグでコンテンツがどれだけ(スクリーンの単位で)動くと、その中のエレメントが入力を受け取らなくなるか。デフォルトは10
useMouseWheel, mouseWheelSensitivityマウスホイールでコンテンツをスクロールするかどうかと、各軸でのスクロールの速さ

コンテンツは、ドラッグが始まるとすぐにポインターに追従します。dragThresholdより遠くまで動くと、ドラッグが終わるまでその中のエレメントは入力を受け取らなくなります。そのため、リストをドラッグしてもその中のボタンは押されませんが、ほとんど動かない押下ではボタンが押されます。マウスホイールは、ポインターがコンテンツなど、スクロールビュー内の入力が有効なエレメントの上にあるときにコンテンツをスクロールし、バウンスはしません。

スクロールバー​

スクロールバーは、トラックとなるイメージエレメントで、Scrollbarコンポーネントと子のハンドルを持ちます。スクロールビューは、コンテンツのどれだけが見えているかを示すようにハンドルのサイズを設定し、スクロール位置を示すようにハンドルの位置を設定します。ハンドルをドラッグするとコンテンツがスクロールします。Visibility(horizontalScrollbarVisibilityとverticalScrollbarVisibility)を使うと、コンテンツがビューポートに収まるときにスクロールバーを隠せます。

  • pc.SCROLLBAR_VISIBILITY_SHOW_ALWAYSは、スクロールバーを常に表示します。これがデフォルトです。
  • pc.SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIREDは、コンテンツがビューポートより大きくないときにスクロールバーを隠します。

スクロールバーは単独でスライダーとしても使えます。スライダーを参照してください。

コンテンツのサイズ設定​

コンテンツのサイズによって、どこまでスクロールできるかが決まります。スクロールビューがこのサイズを自動で変えることはありません。コンテンツがアイテムより短いリストは、最後までスクロールできません。レイアウトグループでコンテンツの子を配置する場合は、レイアウトグループのreflowイベントでコンテンツのサイズを設定します。このイベントは、レイアウトのたびに子の範囲を渡して発火します。

content.addComponent('layoutgroup', {
orientation: pc.ORIENTATION_VERTICAL,
spacing: [0, 10],
padding: [10, 10, 10, 10],
widthFitting: pc.FITTING_STRETCH
});
content.layoutgroup.on('reflow', ({ bounds }) => {
// コンテンツの高さを、行の高さに上下のパディングを加えたものにする
content.element.height = bounds.w + 20;
});

上記のように、コンテンツをビューポートの上端にアンカーしてピボットを上にしておくと、コンテンツは高くなるにつれて下に伸び、最初の行はその位置にとどまります。

コードからのスクロール​

scrollはスクロール位置で、各軸の値が0から1の範囲をとるpc.Vec2です。垂直軸では、0がコンテンツの上端、1が下端です。

// チャットログの最後までスクロールする
scrollView.scrollview.scroll = new pc.Vec2(0, 1);

// ユーザーのスクロールに反応する
scrollView.scrollview.on('set:scroll', (scroll) => {
loadMoreButton.enabled = scroll.y > 0.95;
});

関連情報​