[Flutter] 토스 스타일의 Domino Text 인터렉션 만들기

윤달·2026년 3월 1일

Animation

목록 보기
3/3
post-thumbnail

들어가며

토스 앱을 사용하다 보면, 텍스트가 한 번에 나타나지 않고 글자 하나하나가 마치 도미노처럼 순차적으로 떠오르는 애니메이션을 볼 수 있습니다. 단순히 문장이 통째로 등장하는 것을 넘어, 글자 단위로 미세한 시차를 두고 나타나는 디테일한 UX에 큰 매력을 느끼게 되어 직접 구현해 보게 되었습니다.

이번 포스팅에서는 텍스트를 한 글자씩 분리하여 순차적으로 애니메이션을 적용하는 DominoText 인터렉션을 플러터에서 구현하는 과정을 공유하려 합니다. 문자열을 분리해 개별 AnimationController를 할당하는 방법부터, 약간의 무작위성(Jitter)을 부여해 기계적이지 않고 자연스러운 타이밍을 만드는 디테일한 구현 과정까지 설명드리겠습니다.

💡 본 포스팅에서 소개하는 DominoText 위젯을 쉽게 적용할 수 있도록 패키지를 개발하였습니다. 향후 더 많은 멋진 인터렉션을 해당 패키지에 추가할 예정이니 많은 관심 부탁드립니다!

package : cool_animation_flutter
github : https://github.com/yundal8755/cool_animations


1. 구현할 인터렉션 정의하기

저는 이 인터렉션을 글자들이 도미노처럼 차례대로 반응한다는 특징에 착안하여 DominoText 위젯이라고 정의했습니다.

가장 큰 특징은 텍스트가 통째로 움직이는 것이 아니라, “1분 만에 대출 금리·한도 확인할 수 있다”라는 문장이 주어졌을 때 1, 분, ' ', 만, 에 … 이런식으로 각 글자가 아주 짧은 시차를 두고 아래에서 위로 떠오르며 페이드 인 된다는 점입니다. 이를 통해 텍스트 자체에 생동감을 부여할 수 있습니다.


2. 다중 AnimationController로 개별 제어하기

이전 포스팅이었던 SlideUpFadeIn에서는 하나의 위젯을 통째로 제어했기 때문에 단일 AnimationController로 충분했습니다. 하지만 글자마다 애니메이션의 시작 시점이 달라야 하는 도미노 텍스트의 특성상, 이번에는 텍스트 길이만큼의 다중 컨트롤러를 리스트로 관리해야 합니다.

🤔 왜 컨트롤러를 여러 개 사용할까요?

하나의 컨트롤러 안에서 Interval 등을 사용하여 쪼갤 수도 있지만, 텍스트의 길이가 가변적이고 긴 문장일 경우 0.0~1.0 사이의 값을 수십 개로 쪼개어 연산하는 것은 관리와 확장이 까다로울 수 있습니다. 따라서 각 글자마다 독립적인 생명주기를 가지는 AnimationController를 매핑해주는 직관적인 방식을 채택했습니다.

void _initControllers() {
  // 줄바꿈 문자를 제외한 순수 글자 수를 계산합니다.
  final charCount = widget.text.replaceAll('\n', '').length;

  for (int i = 0; i < charCount; i++) {
    final controller = AnimationController(
      vsync: this,
      duration: widget.duration,
    );

    final curved = CurvedAnimation(
      parent: controller,
      curve: widget.curve,
    );

    _controllers.add(controller);
    _curvedAnimations.add(curved);
  }
}

이렇게 하면 _controllers 리스트 안에 각 글자의 애니메이션을 책임질 컨트롤러들이 준비됩니다. 화면에 그려질 때는 Wrap 위젯 안에서 각 글자를 쪼개어 AnimatedBuilder로 감싸고, 이전 포스팅에서 다루었던 lerpDouble을 활용해 투명도와 Y축 위치를 제어해주면 됩니다.


3. Staggered Delay와 Jitter로 도미노 효과 만들기

컨트롤러들을 만들었다면, 이제 이 컨트롤러들을 언제 실행시킬 것인지가 이 위젯의 핵심 퀄리티를 결정합니다. 단순히 for문을 돌리며 일정한 staggerDelay만 주면 너무 기계적인 느낌이 들 수 있습니다.

그래서 저는 jitterMs라는 변수를 추가했습니다. 여기서는 각 글자가 등장하는 딜레이 시간에 약간의 랜덤한 오차를 부여하여, 훨씬 더 리듬감 있는 타이핑/도미노 효과를 만들어냅니다.

Future<void> _startAnimation() async {
  if (widget.delay > Duration.zero) {
    await Future<void>.delayed(widget.delay);
    if (!mounted) return;
  }

  final random = Random();

  for (int i = 0; i < _controllers.length; i++) {
    if (!mounted) return;

    if (i > 0) {
      // Jitter 값에 따라 딜레이 시간에 약간의 랜덤 오차를 줍니다.
      final jitter = widget.jitterMs > 0
          ? random.nextInt(widget.jitterMs * 2 + 1) - widget.jitterMs
          : 0;
          
      final effectiveDelay = Duration(
        milliseconds: (widget.staggerDelay.inMilliseconds + jitter).clamp(0, 9999),
      );
      
      // 계산된 딜레이만큼 기다린 후 다음 글자 애니메이션을 시작합니다.
      await Future<void>.delayed(effectiveDelay);
      if (!mounted) return;
    }

    // 개별 컨트롤러 실행!
    _controllers[i].forward();
  }
}

이 로직을 통해 글자들이 차례대로 나타나게되며, 앞서 적용한 lerpDouble 보간 함수에 의해 각 글자가 스르륵 제자리를 찾아가게 됩니다.


마무리하며

이번 글에서는 토스나 감각적인 UI를 가진 서비스에서 자주 활용하는 타이포그래피 도미노 애니메이션을 구현해 보았습니다.

단일 애니메이션 위젯에서 한 단계 더 나아가, 다중 AnimationController를 관리하고 Jitter 개념을 도입하여 애니메이션에 섬세한 리듬감을 불어넣는 방법을 알아보았습니다.

앱의 메인 온보딩 화면이나 특정 혜택을 강조해야 하는 타이틀에 이 DominoText 위젯을 적용해 보세요. 텍스트 하나만으로도 앱의 첫인상이 훨씬 고급스러워질 것입니다. 해당 기능이 포함된 cool_animation_flutter 패키지에도 많은 관심 부탁드립니다.

긴 글 읽어주셔서 감사합니다!


원하시는 추가적인 인터렉션 분석이나 코딩 도움이 필요하시다면 언제든 말씀해주세요!

profile
Interested in Flutter

1개의 댓글

comment-user-thumbnail
2026년 3월 2일

항상 잘 보고 있습니다. 애니메이션에 Jitter로 디테일 살리신 부분 정말 감탄하고 갑니다! 오늘도 좋은 글 감사합니다.

답글 달기