Smooth Scrolling With Flutter Web

Smooth scrolling with flutter web is not yet implemented in the Flutter framework, but we are going to solve this problem by doing our own smooth scrolling component.

The scrolling on mobile looks great, but scrolling with the mouse scroll on desktop is jerky. The reason is, the scrolling animation just snaps when you are scrolling with the mouse wheel.

Jerky scroll

We can fix it, by adding a wrapper around our scrollable widget, that will animate the scrolling using the ScrollController’s animateTo function.

SmoothScroll(
    child: _getChild(),
    controller: controller,
),

First we need to disable the default physics of the scrollable widget, we would like to use the SmoothScroll on. NeverScrollableScrollPhysics should be used on the desktop version of your page. Unfortunatelly it disables the scrolling on mobiles as well, but you should be able to separate the mobile and desktop views, so you would only use NeverScrollableScrollPhysics, when the desktop version is loaded.

Widget _getChild() {
    return Container(
      child: ListView(
        physics: NeverScrollableScrollPhysics(),
        controller: controller,
        children: [
          ...
        ],
      ),
    );
}

Smooth Scroll Inputs

  • Widget child: This is going to be our scrollable widget.
  • ScrollController controller: This is the same ScrollController that we will attach to the child as well.

In addition, we will need an inner variable to keep track off the scrolling, which will has a double value. It is always a great practise to name some constant values
in order to avoid using magic numbers in the code. In our case these constants will be:

  • NORMAL_SCROLL_ANIMATION_LENGTH_MS: This constant holds the length of the animation in milliseconds.
  • SCROLL_SPEED: This is the ammount the page will go down, or go up, by just one scrolling click.

We can modify the animation length that will result in faster or slower scrolling animation and also modify the scroll speed.
You can play with it and optimize it the way you like it. The default values that worked for me the most are 250ms of animation, and 130 for the scroll speed.

Scroll animation

First we need a way to listent to the mouse scroll event. We can use the Listener widget for that. The Listener widget has an onPointerSignal named parameter, which is a function that catches PointerSignalEvents.

Listener(
  onPointerSignal: (pointerSignal) {
    if (pointerSignal is PointerScrollEvent) {
          
    }
  },
  child: child,
);

We are going to catch PointerScrollEvents, which activates anytime the mouse scroll wheel is moved. We need some additional information in this event.

  • In which direction did the scroll move?
  • Are we going to exceed the limits of the scrollable widget?

The direction of the scroll can be determined via the pointerSignal.scrollDelta.dy, which is the vertical scroll delta of the mouse wheel. Positive when scrolling down, negative, when scrolling up. Now we can add or extract the SCROLL_SPEED from the scroll variable.

if (pointerSignal.scrollDelta.dy > 0) {
    scroll += SCROLL_SPEED;
} else {
    scroll -= SCROLL_SPEED;
}

We need to check the bounds of the scroll extent. We want to keep the scroll at it’s bounds, and we don’t want to overshoot. For better feel, we are going halve the milliseconds neccessary when we are about to exceed the bounds.

if (scroll > controller.position.maxScrollExtent) {
    scroll = controller.position.maxScrollExtent;
    millis = NORMAL_SCROLL_ANIMATION_LENGTH_MS ~/ 2;
} else if (scroll < 0) {
    scroll = 0;
    millis = NORMAL_SCROLL_ANIMATION_LENGTH_MS ~/ 2;
}

At last, we are going to animateTo the calculated position, within the given time, and with a linear curviture. I have tested numerous Curves, but I found that the most generic curve for the desktop, is the linear curve.

Listener( 
    onPointerSignal: (pointerSignal) {
        int millis = NORMAL_SCROLL_ANIMATION_LENGTH_MS;
        if (pointerSignal is PointerScrollEvent) {
            if (pointerSignal.scrollDelta.dy > 0) {
              scroll += SCROLL_SPEED;
            } else {
              scroll -= SCROLL_SPEED;
            }
            if (scroll > controller.position.maxScrollExtent) {
              scroll = controller.position.maxScrollExtent;
              millis = NORMAL_SCROLL_ANIMATION_LENGTH_MS ~/ 2;
            } else if (scroll < 0) {
              scroll = 0;
              millis = NORMAL_SCROLL_ANIMATION_LENGTH_MS ~/ 2;
            }

            controller.animateTo(
              scroll,
              duration: Duration(milliseconds: millis),
              curve: Curves.linear,
            );
        }
    },
    child: child,
);

That’s it, we are done. And here is the result.

Smooth Scrolling

Conclusion

Although Flutter Web is in beta stages, and lack some basic features on desktop, we can easily create our own component, that will fit on desktop, so our site will blend in as a generic webpage, and not stand out with it’s lack of desktop features.

You can clone the example project at this link. Or you can get the package from pubdev.

Advertisement

4 thoughts on “Smooth Scrolling With Flutter Web

  1. Hi, really nice job I liked the smooth scrolling but I was wondering if there is a way to use it and scroll_to_index simultaneously. I can’t use both in the same ListView because they both need to be attached as controllers.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s