animateToPage method

void animateToPage(
  1. int page, {
  2. Duration duration = const Duration(milliseconds: 300),
  3. Curves curve = Curves.easeInOut,
})

Animates smoothly to the specified page index.

Note that animation is ignored when duration is zero, and the page index is not checked to be within the range of the scroll view's pages.

Example

final controller = PageController();
controller.animateToPage(
  2,
  duration: const Duration(milliseconds: 500),
  curve: Curves.easeIn,
);

Implementation

void animateToPage(
  int page, {
  Duration duration = const Duration(milliseconds: 300),
  Curves curve = Curves.easeInOut,
}) {
  if (_attachedElement == null) return;

  final isHoriz = direction == ScrollDirection.horizontal;

  // calculate page size
  final pageSize =
      (isHoriz
          ? _attachedElement!.clientWidth
          : _attachedElement!.clientHeight) *
      viewportFraction;

  // calculate target scroll offset
  final targetOffset = page * pageSize;

  // jump immediately if duration is zero or the user prefers less motion
  if (duration == Duration.zero ||
      window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
    jumpToPage(page);
    return;
  }

  // calculate starting offset and change
  final startOffset = isHoriz
      ? _attachedElement!.scrollLeft
      : _attachedElement!.scrollTop;

  // calculate change in scroll offset
  final change = targetOffset - startOffset;

  // setup animation variables
  final startTime = DateTime.now().millisecondsSinceEpoch;
  final totalDurationMs = duration.inMilliseconds;

  final element = _attachedElement!;

  // a newer animation takes over from this one
  final animation = ++_animation;

  void step() {
    if (_attachedElement == null || animation != _animation) return;

    // scroll snapping would pull every frame back to the nearest page, and
    // a rebuild clears attributes it did not set, so this is set every frame
    element.setAttribute(_animatingAttribute, '');

    // calculate animation progress
    final elapsed = DateTime.now().millisecondsSinceEpoch - startTime;
    final progress = (elapsed / totalDurationMs).clamp(0.0, 1.0);

    // calculate easing progress
    final easedProgress = curve.build(progress);

    // calculate current scroll position
    final current = startOffset + change * easedProgress;

    // update scroll position
    if (isHoriz) {
      _attachedElement!.scrollLeft = current;
    } else {
      _attachedElement!.scrollTop = current;
    }

    // notify listeners of scroll position changes
    notifyListeners();

    // continue animation if not done
    if (progress < 1.0) {
      onComponentRendered(step);
    } else {
      element.removeAttribute(_animatingAttribute);
    }
  }

  // start animation
  step();
}