Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Add option to smoothly animate stepped mouse scroll deltas #32120

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
ds84182 opened this issue May 5, 2019 · 81 comments
Open

Add option to smoothly animate stepped mouse scroll deltas #32120

ds84182 opened this issue May 5, 2019 · 81 comments
Labels
a: desktop Running on desktop a: fidelity Matching the OEM platforms better a: quality A truly polished experience c: new feature Nothing broken; request for a new capability customer: crowd Affects or could affect many people, though not necessarily a specific customer. f: scrolling Viewports, list views, slivers, etc. found in release: 3.7 Found to occur in 3.7 found in release: 3.10 Found to occur in 3.10 framework flutter/packages/flutter repository. See also f: labels. has reproducible steps The issue has been confirmed reproducible and is ready to work on P2 Important issues not at the top of the work list platform-web Web applications specifically team-framework Owned by Framework team triaged-framework Triaged by Framework team workaround available There is a workaround available to overcome the issue

Comments

@ds84182
Copy link
Contributor

ds84182 commented May 5, 2019

Update 12/12/23


Steps to Reproduce

  1. Use a desktop embedder, or another platform that allows you to scroll with a mouse's scroll wheel.
  2. Scroll any scrollable content (e.g. list demo in Flutter Gallery)
  3. Observe how the scroll offset abruptly jumps between scroll notches instead of smoothly transitioning (like Chrome, Firefox, UWP apps, etc.)

Gif example:
2019-05-05_11-10-31

@stuartmorgan-g stuartmorgan-g added a: desktop Running on desktop platform-mac Building on or for macOS specifically platform-windows Building on or for Windows specifically platform-linux Building on or for Linux specifically labels May 5, 2019
@ds84182
Copy link
Contributor Author

ds84182 commented May 6, 2019

I ported Chrome's rather complex smooth scrolling algorithm (see https://bit.ly/smoothscrolling)

2019-05-06_10-23-09

I can clean it up a bit (make the code a bit more Dart-y) and make a PR, if using Chrome's smooth scrolling is acceptable.

@stuartmorgan-g
Copy link
Contributor

/cc @gspencergoog

It seems like this is going to need a design proposal, or at least discussion, for how it would be controlled, since it should almost certainly be optional. Smooth scrolling with scroll wheels is definitely not universal behavior on desktop platforms.

@ds84182
Copy link
Contributor Author

ds84182 commented May 6, 2019

Notably, Windows (UWP apps) and Mac OS have their own smooth scrolling behaviors. Linux doesn't really have a default, so I feel that using Chrome's implementation (at least on Linux) would be good enough.

@ds84182
Copy link
Contributor Author

ds84182 commented May 6, 2019

For UWP, a lot of the logic can be found here (Scroller.cpp), and some keyboard scrolling logic can be found here (ScrollViewer.cpp)

@gspencergoog gspencergoog added framework flutter/packages/flutter repository. See also f: labels. and removed platform-mac Building on or for macOS specifically platform-windows Building on or for Windows specifically platform-linux Building on or for Linux specifically labels Jul 26, 2019
@gspencergoog
Copy link
Contributor

Marking this as a framework issue, since I don't think it's specific to any one platform, and the solution is probably in the framework and not in the engine.

@allComputableThings
Copy link

allComputableThings commented Dec 28, 2019

Is anyone working on this? I see this problem in the Android emulator. Using the scrollwheel leads to very erratic behavior. Sometimes it scrolls the list down, them pops back. (is not limited to webviews - also happens with listviews)

@Hixie Hixie modified the milestones: Stretch Goals, New Stretch Goals Jan 7, 2020
@ghost
Copy link

ghost commented Jan 18, 2020

@ds84182 Your smooth scroll algorithm looks pretty good from what I can see in the GIF. Could you share your code on how you implemented it? I have flutter web project and a smooth scroll algorithm would greatly increase the user experience. As far as I can tell, every browser supports smooth scrolling and so a flutter website feels a bit odd in comparison to other native websites when being scrolled.

@SriRam-Macha
Copy link

@ds84182 Your smooth scroll algorithm looks pretty good from what I can see in the GIF. Could you share your code on how you implemented it? I have flutter web project and a smooth scroll algorithm would greatly increase the user experience. As far as I can tell, every browser supports smooth scrolling and so a flutter website feels a bit odd in comparison to other native websites when being scrolled.

Do u have the smooth scroll algorithm can u share it ?

@kf6gpe kf6gpe added the P3 Issues that are less important to the Flutter project label May 29, 2020
@markusaksli-nc
Copy link
Contributor

Reproducible on the latest master 1.21.0-6.0.pre.160.

Minimal reproducible code sample
import 'dart:math';

import 'package:flutter/material.dart';

void main() => runApp(
     MaterialApp(
       home: Scaffold(
         appBar: AppBar(
           title: Text("Sample"),
         ),
         body: ListView(
           children: [
             ...List.generate(200, (index) => RandomBox()),
           ],
         ),
       ),
     ),
   );

class RandomBox extends StatelessWidget {
 static const List colors = [
   Colors.red,
   Colors.green,
   Colors.yellow,
   Colors.blue,
   Colors.purple,
   Colors.black,
   Colors.orange
 ];
 static Random random = new Random();

 @override
 Widget build(BuildContext context) {
   int index = random.nextInt(7);
   return Container(
     height: 50,
     color: colors[index],
   );
 }
}
flutter doctor -v
[✓] Flutter (Channel master, 1.21.0-6.0.pre.160, on Mac OS X 10.15.6 19G73, locale en-GB)
    • Flutter version 1.21.0-6.0.pre.160 at /Users/markus/development/flutter_master
    • Framework revision 31ee51a302 (11 hours ago), 2020-08-03 17:27:21 -0700
    • Engine revision fa16c3d0ba
    • Dart version 2.10.0 (build 2.10.0-2.0.dev 0f0e04ec3a)

 
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.1)
    • Android SDK at /Users/markus/Library/Android/sdk
    • Platform android-30, build-tools 30.0.1
    • Java binary at: /Users/markus/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/193.6626763/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 11.6)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 11.6, Build version 11E708
    • CocoaPods version 1.9.3

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 4.0)
    • Android Studio at /Users/markus/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/193.6626763/Android Studio.app/Contents
    • Flutter plugin version 48.0.2
    • Dart plugin version 193.7361
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)

[✓] Connected device (5 available)
    • sdk gphone x86 arm (mobile) • emulator-5554                            • android-x86    • Android 11 (API 30) (emulator)
    • Nevercode’s iPhone (mobile) • b668e524315069f3db3661ac11ff1f66afafebdb • ios            • iOS 13.6
    • macOS (desktop)             • macos                                    • darwin-x64     • Mac OS X 10.15.6 19G73
    • Web Server (web)            • web-server                               • web-javascript • Flutter Tools
    • Chrome (web)                • chrome                                   • web-javascript • Google Chrome 84.0.4147.105

• No issues found!

@markusaksli-nc markusaksli-nc added a: quality A truly polished experience f: scrolling Viewports, list views, slivers, etc. found in release: 1.21 Found to occur in 1.21 has reproducible steps The issue has been confirmed reproducible and is ready to work on labels Aug 4, 2020
@Hixie Hixie removed this from the - milestone Aug 17, 2020
@goderbauer goderbauer added the triaged-framework Triaged by Framework team label Jun 10, 2024
@Mikephii
Copy link

you can override void pointerScroll() on ScrollPosition to use animateTo() instead of forcePixels()
and track the interval between animations the same as clragons solution above

pointerScroll appears to only be called to handle PointerScrollEvent's which are scroll wheel events
CleanShot 2024-08-28 at 17 33 01@2x

you can create a customScrollPosition and CustomScrollController to use your custom scroll position.

This allows you to use any scroll physics and also touch scroll and drag

working great for me not sure of the consequences of my actions but thats usual

@iapicca
Copy link
Contributor

iapicca commented Aug 28, 2024

you can override void pointerScroll() on ScrollPosition to use animateTo() instead of forcePixels() and track the interval between animations the same as clragons solution above

pointerScroll appears to only be called to handle PointerScrollEvent's which are scroll wheel events
[...]
you can create a customScrollPosition and CustomScrollController to use your custom scroll position.

This allows you to use any scroll physics and also touch scroll and drag

working great for me not sure of the consequences of my actions but thats usual

@Mikephii
sounds interesting, mind to share a code sample of your workaround? I'd like to test it

@oelburk

This comment was marked as off-topic.

@Mikephii
Copy link

heres a minimal example of my solution. I think this can be used by the flutter team and can easily be a boolean flag in the scroll controller. Unsure of any side effects but it works fine for me and doesnt break
touch scroll or drag or any of that.

import 'package:flutter/rendering.dart';
import 'dart:math' as math;

class ExampleSmoothScroll extends StatefulWidget {
  const ExampleSmoothScroll({
    super.key,
  });

  @override
  State<ExampleSmoothScroll> createState() => _ExampleSmoothScrollState();
}

class _ExampleSmoothScrollState extends State<ExampleSmoothScroll> {
  final ScrollController _scrollController = MyCustomSmoothScrollController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView.builder(
        physics: BouncingScrollPhysics(),
        controller: _scrollController,
        itemCount: 1000,
        itemBuilder: (context, index) {
          return ListTile(
            tileColor: index % 2 == 0 ? Colors.blue : Colors.lightBlue,
            title: Text('Item $index'),
          );
        },
      ),
    );
  }
}

class MyCustomSmoothScrollController extends ScrollController {
  MyCustomSmoothScrollController({
    double initialScrollOffset = 0.0,
    bool keepScrollOffset = true,
    String? debugLabel,
  }) : super(
          initialScrollOffset: initialScrollOffset,
          keepScrollOffset: keepScrollOffset,
          debugLabel: debugLabel,
        );

  @override
  ScrollPosition createScrollPosition(
    ScrollPhysics physics,
    ScrollContext context,
    ScrollPosition? oldPosition,
  ) {
    //use the customscroll position with smooth scrolling
    return CustomSmoothScrollPosition(
      physics: physics,
      context: context,
      initialPixels: initialScrollOffset,
      keepScrollOffset: keepScrollOffset,
      oldPosition: oldPosition,
      debugLabel: debugLabel,
    );
  }
}

class CustomSmoothScrollPosition extends ScrollPositionWithSingleContext {
  int previousScrollTime = DateTime.now()
      .millisecondsSinceEpoch; //used to track time between scrolls same as clragons code

  CustomSmoothScrollPosition({
    required ScrollPhysics physics,
    required ScrollContext context,
    double initialPixels = 0.0,
    bool keepScrollOffset = true,
    ScrollPosition? oldPosition,
    String? debugLabel,
  }) : super(
          physics: physics,
          context: context,
          initialPixels: initialPixels,
          keepScrollOffset: keepScrollOffset,
          oldPosition: oldPosition,
          debugLabel: debugLabel,
        );

  @override
  void pointerScroll(double delta) {
    // If an update is made to pointer scrolling here, consider if the same
    // (or similar) change should be made in
    // _NestedScrollCoordinator.pointerScroll.
    if (delta == 0.0) {
      goBallistic(0.0);
      return;
    }

    final double targetPixels =
        math.min(math.max(pixels + delta, minScrollExtent), maxScrollExtent);
    if (targetPixels != pixels) {
      goIdle();
      updateUserScrollDirection(
        -delta > 0.0 ? ScrollDirection.forward : ScrollDirection.reverse,
      );
      final double oldPixels = pixels;
      // Set the notifier before calling force pixels.
      // This is set to false again after going ballistic below.
      isScrollingNotifier.value = true;

      //--------------------  NEW CODE STARTS HERE  --------------------
      int deltaScrollTime =
          DateTime.now().millisecondsSinceEpoch - previousScrollTime;
      int animationDuration = deltaScrollTime < 500 ? deltaScrollTime : 500;

      if (animationDuration < 32) {
        //60fps is 16ms per frame so less than 32ms is pointless as its less than 2 frames
        //also avoids calling animateTo too many times in quick succession which seems to be buggy and throws exceptions for elapsed<0  that might be related to github.com/flutter/flutter/issues/106277
        super.pointerScroll(delta);
        return;
      }
      animateTo(targetPixels,
          duration: Duration(milliseconds: animationDuration),
          curve: Curves.linear);
      previousScrollTime = DateTime.now().millisecondsSinceEpoch;
      //--------------------  NEW CODE ENDS HERE  --------------------

      //-------------------- THIS IS THE ORIGINAL FORCE PIXELS IMPLEMENTATION OF super.pointerScroll(delta) --------------------
      // forcePixels(targetPixels);
      // didStartScroll();
      // didUpdateScrollPositionBy(pixels - oldPixels);
      // didEndScroll();
      // goBallistic(0.0);
    }
  }
}

@ReinisSprogis
Copy link

A bit salty here but why should we bother with a workaround on a thing that should just work? This issue has been live since 2019. Any official update from the flutter team regarding this?

I agree. Mouse scrolling on lists are confusing. Items simply disappear off the screen. If not numbered, it's hard to keep track on what I am looking at.

@kszczek
Copy link
Contributor

kszczek commented Oct 24, 2024

I've just published the scroll_animator package, which aims to be a direct port of Chromium's smooth scrolling algorithm (ported along with related unit tests, just to make sure it works as expected). There are some edge cases which I know about and plan to resolve in the coming days, but there's probably a bunch more of them I don't know about yet, so I wouldn't use it in production just yet, but I would be grateful for testing and bug reports. When the package is stable enough, I'll probably try to refactor the code and try to upstream it.

In regards to the previous attempts at resolving this problem, it turns out we don't have to know if the user is scrolling using a mouse wheel or a trackpad. Chromium developers solved that problem by adjusting the algorithm so it works well with small, frequent scroll deltas as well as large, infrequent scroll deltas. See this bug report for more details.

@BennyKirschner
Copy link

I've just published the scroll_animator package, which aims to be a direct port of Chromium's smooth scrolling algorithm (ported along with related unit tests, just to make sure it works as expected). There are some edge cases which I know about and plan to resolve in the coming days, but there's probably a bunch more of them I don't know about yet, so I wouldn't use it in production just yet, but I would be grateful for testing and bug reports. When the package is stable enough, I'll probably try to refactor the code and try to upstream it.

In regards to the previous attempts at resolving this problem, it turns out we don't have to know if the user is scrolling using a mouse wheel or a trackpad. Chromium developers solved that problem by adjusting the algorithm so it works well with small, frequent scroll deltas as well as large, infrequent scroll deltas. See this bug report for more details.

I just gave it a try but when scrolling up/down quickly, the scroll seems to stop completely every now and again. Once fixed, this packages seems like a great fix to the problem.

@kszczek
Copy link
Contributor

kszczek commented Oct 24, 2024

I just gave it a try but when scrolling up/down quickly

Yeah, I'll probably have to get a new mouse, my current one has a broken wheel, when scrolling in one direction it sporadically decides to go the other way, so I wasn't able to verify quick scrolls, I'll look into that.

Also, regarding upstreaming into Flutter, I think it would be best if Flutter provided an abstract animation factory (like the one in my package), so that third-party packages could provide alternative scroll animation implementations. Flutter could include a single default implementation, but then there would be pub packages for Mac-like or Edge-like scrolling. The recently proposed solution with just a boolean would lack that customizability.

@kszczek
Copy link
Contributor

kszczek commented Oct 25, 2024

@BennyKirschner unfortunately I wasn't able to reproduce your issue, but I've just published a new version with an additional diagnostic example, available here: https://pub.dev/packages/scroll_animator/example. If you could try to reproduce your issue while capturing this diagnostic data, I might be able to synthetically reproduce your scroll inputs and hopefully the issue itself. Also, I suggest opening an issue over at the scroll_animator repository, so we don't spam this issue.

@kszczek
Copy link
Contributor

kszczek commented Oct 31, 2024

I wanted to share a little progress update, since I've published the initial version of scroll_animator last week, quite a bit changed:

  • Introduced support for programmatic scrolling with automatic animation parameters.

    Using Flutter's ScrollController.animateTo method requires explicitly providing an animation curve and duration, which can lead to inconsistencies in your app if you use programmatic scroll animations in multiple places.

    Starting with version 0.2.0, using scroll_animator's AnimatedScrollController.animateTo method, you can omit the curve and duration, and they will be determined automagically based on the scroll distance.
    This makes the API similar to the scrollTo method you might know from JavaScript. Actually, the scroll animation should feel exactly the same as in a Chromium-based browser, pixel-by-pixel.

  • Introduced support for scrolling with arrows and page up/down keys (or any other custom keys)

    Although Flutter has support for scrolling with the keyboard out-of-the box, it's not really customizable, and is partly broken (try holding down an arrow key to scroll faster). With version 0.3.0, scroll_animator introduces a subclass of Flutter's default scroll action, the AnimatedScrollAction. This action respects the ScrollAnimationFactory specified by the user, and also fixes the aforementioned hold-down scroll issue.

  • Improved developer experience

    Version 0.3.0 of scroll_animator introduces the AnimatedPrimaryScrollController. This widget creates and manages the AnimatedScrollController instance for you, so you don't have to worry about creating stateful widgets to store the scroll controllers. This also makes the keyboard scrolling work out-of-the box, by wrapping your subtree with a PrimaryScrollController.

    The example application in the package's README was revamped to exhaustively demonstrate all features of the package.

@water-mizuu
Copy link

@kszczek, First I have to say, wonderful work with the package. It works flawlessly, and honestly it's better than some of the previous solutions. I have to ask, if it's possible for you to add support for the package scrollable_positioned_list? As it uses its own scroll controller class. Cheers!

@kszczek
Copy link
Contributor

kszczek commented Nov 19, 2024

@water-mizuu thanks for the feedback, I appreciate it!

Regarding the package you've mentioned, I've glanced over its source and it seems like it has the base ScrollController type hardcoded, so there is nothing I can do in my package to make this work. To integrate scroll_animator with this package, we'd have to be able to provide a custom ScrollController instance to the list widget or make it source the scroll controller from a PrimaryScrollController ancestor.

I'd submit a PR as it's a quite simple thing to do, but it seems like the google/flutter.widgets repository where this package lives is not very active, with the last commit dating December 2023 and multiple stale PRs. If I were you I'd just clone this package to my machine, edit this line to instantiate an AnimatedScrollController and use the path dependency syntax.

A long-term solution for this issue is to integrate the smooth scrolling logic into the base ScrollController. I've shared a design document on this topic today, but it will likely take some time before we finalize the design and land it.

@hruzgar
Copy link

hruzgar commented Mar 14, 2025

I just tried @kszczek's scroll_animator package and holy crap it works perfectly. It feels like proper native scrolling instead of feeling like some bad mobile port.
Desktop flutter apps never really felt great to me and now I realize that scrolling was playing a big part in that. But this just works perfectly and I'm so amazed. Thank you so much for the hard work @kszczek!

@yjbanov
Copy link
Contributor

yjbanov commented Mar 14, 2025

Just cross-linking to a related issue over on the W3C side: w3c/uievents#337

@hasali19
Copy link

I get why this is currently blocked for web, but is there anything preventing this from being implemented for desktop platforms?

@kszczek
Copy link
Contributor

kszczek commented Mar 15, 2025

I get why this is currently blocked for web

I don't think differentiating between trackpad and mouse wheel events should be considered a blocker for web/desktop implementation. In fact, I believe the reason stated for this blockage is a misunderstanding.

I've mentioned this previously, but it's worth reiterating: some years ago, Chromium developers also implemented a heuristic for differentiating between trackpad and mouse wheel inputs. However, this approach caused more issues than it resolved. So, instead of disabling scroll smoothing specifically for trackpads, Chromium developers improved the scroll smoothing algorithm to handle high-frequency, low-delta inputs better (see https://issues.chromium.org/issues/41210665#comment16).

All of the above is highlighted in the scroll_animator's readme:

Trackpads typically provide precise and frequent scroll deltas, which often leads to the assumption that scroll smoothing isn't necessary. However, some trackpads can produce larger, less frequent deltas, similar to mouse wheels. Therefore, this package handles all scroll inputs without making assumptions based on the pointer type.

See this Chromium bug for discussion on this topic.

As described above, scroll_animator handles trackpad and mouse wheel inputs identically, and yet scrolling behaves as expected (at least on Linux and Windows - I haven't tested this on a MacBook's trackpad yet, so feedback on its behavior would be greatly appreciated!).

I was in the process of upstreaming my work into Flutter last year, but got overwhelmed with other work. I plan to get back to this soon though, here's a rough roadmap:

  1. Design an API for implementing smooth scrolling algorithms (done - https://flutter.dev/go/smoothing-discrete-scroll-inputs)
  2. Refactor some existing Flutter components to make them compatible with the new API, as outlined in the doc.
  3. Implement and expose the bare API, without any actual implementations.
  4. Convert scroll_animator package into a "catalogue" of scroll smoothing algorithms - initially including Chrome and MS Edge algorithms (IIRC Windows UWP scroll smoothing algo is open source, so should be easy to port also).
  5. Implement a platform-agnostic scroll smoothing algorithm and include it in the framework(?)

@kszczek
Copy link
Contributor

kszczek commented Mar 15, 2025

(although knowing that a trackpad is used for scrolling is still useful info. It would allow us to implement fling gestures. But its still not a blocker - only a cherry on top)

@JHubi1
Copy link

JHubi1 commented Apr 4, 2025

It's really bad right now, and I don't get why this was downgraded from P1 to P2. It definitely deserves highest priority and should be considered a severe bug.
There's no real fix available that does not make the UX significantly worse in other spots. All proposed seem to break something or are a pain to implement consistently.
An issue this bad really stops devs from using Flutter in any serious project that's supposed to run on any of the Desktop platforms. Really needs a fix

@shroot91

This comment has been minimized.

@iapicca

This comment has been minimized.

@Piinks

This comment has been minimized.

@Krushiler
Copy link

iapicca Now it looks like it's better not to target for web and Windows. Every app has scrollable components but it's very unconfortable to scroll with mouse and this issue still remains open. After 6 years

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a: desktop Running on desktop a: fidelity Matching the OEM platforms better a: quality A truly polished experience c: new feature Nothing broken; request for a new capability customer: crowd Affects or could affect many people, though not necessarily a specific customer. f: scrolling Viewports, list views, slivers, etc. found in release: 3.7 Found to occur in 3.7 found in release: 3.10 Found to occur in 3.10 framework flutter/packages/flutter repository. See also f: labels. has reproducible steps The issue has been confirmed reproducible and is ready to work on P2 Important issues not at the top of the work list platform-web Web applications specifically team-framework Owned by Framework team triaged-framework Triaged by Framework team workaround available There is a workaround available to overcome the issue
Projects
Status: In Progress