Flutter for Single-Page Scrollable Websites with Navigator 2.0 — Part 3: Scroll to Page

In the second part of this series, we discussed building a single-page scrollable website (SPSW) using a ListView
whose sections are built lazily (on-demand) and have equal height. In this article, the sample app will be very similar to the previous app with one difference: In the ColorSections
widget, instead of ListView
, we will use PageView
.
If you haven’t read the previous articles, I would strongly suggest starting from the first part of this series since the implementation details of the common widgets will not be mentioned in this part.
- Part 1: Introduction
- Part 2: Scroll to Position
- Part 3: Scroll to Page
- Part 4: Ensure Visible
- Part 5: Scroll to Index
- Part 6: Navigation
- Part 7: Query Params

ColorSections
PageView
widget is a scrollable list whose children have the same size which is equal to the viewport size by default. Each item in the list is called a page.
PageController
extends the ScrollController
. It allows setting which page is visible in a PageView
using animateToPage
, jumpToPage
, nextPage
, previousPage
methods. We can set the page size relative to the viewport size using its viewportFraction
property.
We can consider the PageView
widget as a ListView
but more tailored for items of equal size. We will build a PageView
in a vertical scroll direction lazily using its builder
constructor. By default, PageView
widget locks the viewport to a certain page when a scrolling gesture ends. We will disable this feature by setting its pageSnapping
property to false
.


Listen to Color Code Update
We start listening for the colorCodeNotifier
updates as soon as the ColorSections
widget is inserted into the tree. At this moment, initState
method is called, and the PageController
doesn’t have any clients because the PageView
is not yet laid out.
When we receive an update, if the PageController
is attached to the PageView
, and the source of the update is not scrolling, we provide the page index number to the animateToPage
method of the PageController
which scrolls to the destination index programmatically.

Notify Color Code
While the user is scrolling, we want to update the colorCodeNotifier
listeners as the first visible color code section (trailingIndex
) changes.
When we receive a UserScrollNotification
, we get the current page index from the PageController
, and set a new colorCode
value for the corresponding page index.

Resizing Browser Window
In our use case, we set each page’s height equal to the viewport height. When resizing the browser window we want to make sure that we should be able to show the page content even in the smallest height that browser allows. We will use viewportFraction
property of the PageController
to set a minimum height for pages.

The viewportFraction
property is declared as final
in PageController
so, we can’t dynamically set its value. We will wrap the PageView
with a LayoutBuilder
class to be able create new PageController
for the PageView
according to the new size constraints.
… the framework calls the builder function at layout time and provides the parent widget’s constraints. This is useful when the parent constrains the child’s size and doesn’t depend on the child’s intrinsic size. The LayoutBuilder’s final size will match its child’s size.
We will calculate the viewportFraction
for the available size according to this formulation:
- We first decide on the minimum height that is enough to show our content in a page. In this sample, we set it to 800 px.
- The builder function of LayoutBuilder will be called the first time the widget is laid out and when the parent widget passes different layout constraints. We need to construct a new
PageController
each time this method is called. - If the available height that the
PageView
can be laid out is greater than the_minPageHeight
, then theviewportFraction
equals to 1 which makes the page height equal to the viewport height. - If the available height is less than the
_minPageHeight
, we calculate theviewportFraction
by dividing_minPageHeight
by the available height. For example, if the viewport height equals to 400 px, we need to set theviewportFraction
to 2 so that the page height will be 800 px.
Here is the full code for the ColorsSection
widget:
Issues with Resizing
We actually don’t have any problem when the viewportFraction
is set to less than or equal to 1. However, as of Flutter stable version 2.2.0, I experience issues using viewportFraction
when it is greater than 1.
- Clipped First and Last Pages
PageView
widget is obsessed with snapping the content to the center of the viewport. When the viewportFraction
that is greater than 1, it calculates _initalPageOffset
value to be added to the minScrollContent
and subtracted from the maxScrollContent
to snap the content to the center. This decision leads to clipping the first and last pages.

- Updating page index after the offset
To get the current page index from the PageController
, we access the page
property and floor it to an integer. For some reason, the page does not update until some offset value.
In the recording below, you can notice that the page index is correctly displayed when the viewportFraction
is 1. As the browser height gets smaller, we set the viewportFraction
to greater than 1. In the recording, the current page index is less than 1, although the PageView
is actually displaying the 1st index.

Note that these two issues might be expected behavior or bugs. Please write in comments if you have solutions, or I am missing something.
Conclusion
In this article, we explored lazily building the sections of a single page scrollable website using a PageView
widget. In the fourth part of this series, we will learn how to live with building all the sections at once in a SingleChildScrollView
+ Column
.
You can run this sample app by right clicking on the main_003.02.dart
file in the project source code. If you liked this article, please press the clap button, and star the Github repository of the sample apps.
