DEV Community

HarmonyOS
HarmonyOS

Posted on

Synchronizing Multiple Text Marquee Animations

Read the original article:Synchronizing Multiple Text Marquee Animations

Context

When implementing a text marquee card feature in HarmonyOS applications, developers need to create a coordinated animation system where multiple text elements scroll horizontally. The challenge lies in ensuring that when one text element reaches the boundary of its container, it pauses and waits for all other text animations to complete before resetting. This synchronization is essential for creating a polished, professional user experience where all carousel elements remain coordinated throughout their animation cycles.

Description

The issue involves implementing a text carousel feature with the following requirements:

  • Multiple text strings need to scroll horizontally within fixed-width containers
  • Each text element scrolls at its own pace based on its length
  • When a text element reaches the end of its scrollable area, it must pause
  • The animation should only reset to the beginning when ALL text elements have completed their scrolling
  • The entire process should repeat automatically in a synchronized manner

The HarmonyOS framework provides two key components for this functionality:

  • Scroll: A scrollable container component that allows content to scroll when child components exceed the parent component's dimensions
  • Scroller: A controller for scrollable container components that can be bound to a Scroll component to programmatically control scrolling behavior, including methods like isAtEnd() to detect scroll position

Solution

The solution uses Scroller to control Scroll component animations with timer-based automatic scrolling. The isAtEnd() method monitors scroll state to determine when text has completely displayed, ensuring coordinated animation control.

Entry Component:

@Entry
@Component
struct Index {
  @State textList: string[] = [
    'this is a test string1 this is a test string1 this is a test string1.',
    'this is a test string2 this is a test string2.',
    'this is a test string3 this is a test string3 this is a test string3 this is a test string3.',  ]
  build() {
    Row() {
      Column() {
        myMarqueeCard({
          textList: this.textList,
        })
      }
      .width('100%')
    }
    .height('100%')
  }
}
Enter fullscreen mode Exit fullscreen mode

Custom Marquee Card Component:

@Component
struct myMarqueeCard {
  @Prop textList: string[]
  scroller1: Scroller = new Scroller()
  scroller2: Scroller = new Scroller()
  scroller3: Scroller = new Scroller()
  build() {
    Column() {
      this.SingleText(this.textList[0], this.scroller1)
      this.SingleText(this.textList[1], this.scroller2)
      this.SingleText(this.textList[2], this.scroller3)
    }
  }
  @Builder
  SingleText(text: string, scroller: Scroller) {
    Scroll(scroller) {
      Row() {
        Text(text).fontSize(30)
      }
    }
    .width(300)
    .scrollable(ScrollDirection.Horizontal)
    .enableScrollInteraction(false)
    .scrollBar(BarState.Off)
    .onAppear(() => {
      this.handleScroll(scroller)
    })
  }
  handleScroll(scroller: Scroller) {
    let timer: number = setInterval(() => {
      const curOffset: OffsetResult = scroller.currentOffset()
      scroller.scrollTo({
        xOffset: curOffset.xOffset + 50, yOffset: curOffset.yOffset, animation: {
          duration: 1000,
          curve: Curve.Linear
        }
      })
      if (scroller.isAtEnd()) {
        clearInterval(timer);
        if (this.scroller1.isAtEnd() && this.scroller2.isAtEnd() && this.scroller3.isAtEnd()) {
          this.scroller1.scrollTo({xOffset: 0, yOffset: 0, animation: { duration: 0 }})
          this.scroller2.scrollTo({xOffset: 0, yOffset: 0, animation: { duration: 0 }})
          this.scroller3.scrollTo({xOffset: 0, yOffset: 0, animation: { duration: 0 }})
        }
      }
    }, 500)
  }
}
Enter fullscreen mode Exit fullscreen mode

Implementation Details:

  1. Create Multiple Scroller Instances: Each text element gets its own Scroller controller for independent control
  2. Implement Automatic Scrolling: Use setInterval to increment scroll offset by 50 pixels every 500ms with 1000ms animation duration
  3. Monitor Scroll State: Utilize the isAtEnd() method to detect when each text element reaches the end
  4. Synchronize Reset: Check if all scrollers have reached the end before resetting all animations simultaneously to position 0 with zero animation duration

Key Takeaways

  • Use Scroller controllers with timer-based offset increments to create custom marquee animations in HarmonyOS
  • The isAtEnd() method is essential for detecting scroll completion and coordinating multiple animations
  • Disable scroll interaction (enableScrollInteraction(false)) to prevent manual scrolling interference with animations
  • Synchronize multiple scrollers by checking all their states before performing collective actions like resets
  • Set animation duration to 0 when resetting scroll positions to create seamless loop transitions
  • Each text element can scroll at different speeds naturally based on content length, but all pause and reset together for coordinated behavior

Written by Emincan Ozcan

Top comments (0)