Skip to main content

Scroll Views

A scroll view shows part of a larger piece of content, and lets the user drag it, or turn the mouse wheel, to bring the rest into view. Lists, inventories, long text and maps all use one. It is made of several entities:

scroll view group element, and the scroll view component
├── viewport image element that is a mask: the visible area
│ └── content element larger than the viewport, with input enabled
│ └── … the items being scrolled
└── scrollbar image element, and a scrollbar component (optional)
└── handle image element with input enabled, dragged along the bar

The viewport is a mask, so the content is only drawn inside it. The content needs input enabled, so that it can be dragged by its empty areas too.

The scroll view moves the content by setting its local position, which is 0, 0 when the content is scrolled to its top-left. So anchor the content to the top-left corner of the viewport, or across its top edge, and give it a top-left pivot, [0, 1]. With any other pivot, part of the content starts out of view.

Scroll View

Creating a Scroll View​

This 300 × 400 scroll view scrolls a taller column vertically, with a scrollbar on its right:

// The scroll view, 300 × 400 in the middle of the screen
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);

// The viewport fills the scroll view, apart from 20 units on the right for the scrollbar
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);

// The content hangs from the top-left corner of the 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);

// A scrollbar along the right edge, and its handle
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
});

Register pc.ScrollViewComponentSystem and pc.ScrollbarComponentSystem when you create the application, and give it a pc.Mouse for dragging with the mouse. A scroll view created from code has no settings of its own: it doesn't scroll on either axis until you set horizontal or vertical, and without a scrollMode, bounceAmount and friction it logs warnings, doesn't bounce back and stops dead when released. Pass all of them when you add it, as here.

Scrolling Behavior​

PropertyEffect
horizontal, verticalThe axes the content can scroll on
scrollModeClamp stops the content at its edges. Bounce lets it go past them and springs it back. Infinite lets it scroll forever
bounceAmountHow slowly the content springs back in Bounce mode. 0 snaps it back at once, 0.1 feels like scrolling on a phone, and larger values are slower
frictionHow quickly the content slows down after it is flung, from 0 to 1. At 1 it stops at once
dragThresholdHow far, in screen units, the content moves in a drag before the elements inside it stop receiving input. 10 by default
useMouseWheel, mouseWheelSensitivityWhether the mouse wheel scrolls the content, and how fast on each axis

The content follows the pointer as soon as a drag starts. Once it has moved further than dragThreshold, the elements inside it stop receiving input until the drag ends, so dragging a list doesn't press the buttons in it, while a press that barely moves still does. The mouse wheel scrolls the content when the pointer is over an input-enabled element in the scroll view, such as the content, and doesn't bounce.

Scrollbars​

A scrollbar is an image element, the track, with a scrollbar component and a handle child. The scroll view sets the size of the handle to show how much of the content is visible, and its position to show the scroll position, and dragging the handle scrolls the content. Its Visibility (horizontalScrollbarVisibility and verticalScrollbarVisibility) can hide it when the content fits the viewport:

  • pc.SCROLLBAR_VISIBILITY_SHOW_ALWAYS shows the scrollbar all the time. This is the default.
  • pc.SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED hides it when the content isn't larger than the viewport.

A scrollbar also works on its own, as a slider. See Sliders.

Sizing the Content​

The content's size decides how far it can scroll, and a scroll view never changes it for you. A list whose content is shorter than its items can't be scrolled to its end. When a layout group arranges the content's children, size the content from the layout group's reflow event, which fires with the bounds of the children after each layout:

content.addComponent('layoutgroup', {
orientation: pc.ORIENTATION_VERTICAL,
spacing: [0, 10],
padding: [10, 10, 10, 10],
widthFitting: pc.FITTING_STRETCH
});
content.layoutgroup.on('reflow', ({ bounds }) => {
// Make the content as tall as its rows, plus the top and bottom padding
content.element.height = bounds.w + 20;
});

With the content anchored to the top of the viewport and a top pivot, as above, a taller content grows downwards, and the first rows stay in place.

Scrolling from Code​

scroll is the scroll position, a pc.Vec2 whose values go from 0 to 1 on each axis. On the vertical axis, 0 is the top of the content and 1 is the bottom:

// Scroll to the end of a chat log
scrollView.scrollview.scroll = new pc.Vec2(0, 1);

// React to the user scrolling
scrollView.scrollview.on('set:scroll', (scroll) => {
loadMoreButton.enabled = scroll.y > 0.95;
});

See Also​