Mobile Development 22 min read

HarmonyOS Dynamic Gradient Backgrounds: From Property Animation to Keyframe Animation

This article details two approaches for creating dynamic gradient backgrounds in HarmonyOS apps: a simple property animation for color transitions and a high-performance keyframe animation that moves an oversized gradient layer to achieve complex flowing and rotating effects, with complete code examples and a comparison table.

51CTO HarmonyOS Developer Community
51CTO HarmonyOS Developer Community
51CTO HarmonyOS Developer Community
HarmonyOS Dynamic Gradient Backgrounds: From Property Animation to Keyframe Animation

Introduction

In HarmonyOS app development, visual experience is crucial for user satisfaction. Gradient backgrounds are widely used for their rich color layers and modern feel, but static gradients lack vitality. This article shares practical implementations for dynamic gradient backgrounds, progressing from basic property animation to more powerful keyframe animation.

Assumes basic HarmonyOS development knowledge. Static gradient implementation is covered in official documentation. All examples use LinearGradient with intuitive variable naming.

Approach 1: Property Animation — Simple Color Transitions

1. Principle

Property animation interpolates between start and end values when an animatable property changes. For gradient backgrounds, the entire LinearGradientOptions configuration (angle and color array) is treated as a single animatable property.

Note: Property animation support for gradient angle changes is imperfect; discussed later.

2. Implementation Steps

2.1 Define State Variables

@State gradientOptions: LinearGradientOptions = {  angle: 45,  colors: [    ["#f09819", 0],   // start color at 0%    ["#edde5d", 1]    // end color at 100%  ]};@State flag: number = 1; // tracks current gradient combination (1 or 0)
gradientOptions

: gradient configuration including angle and color array. Each color element is [color, stop] with stop in [0, 1]. flag: simple flag indicating which color combination is active; toggled on animation completion to create a loop.

2.2 Start Animation in onDidBuild

onDidBuild(): void {  // trigger first gradient switch  this.gradientOptions = {    angle: 45,    colors: [      ["#ffee113e", 0],      ["#ff1a3864", 1]    ]  };}

The initial orange-yellow gradient switches to a purple-red gradient; the transition is smoothed by the .animation() modifier.

2.3 Apply Gradient and Bind Animation

Column()  .width(200)  .height(200)  .linearGradient(this.gradientOptions)  // bind gradient  .animation({    duration: 2000,         // 2-second duration    curve: Curve.EaseInOut, // easing curve    onFinish: () => {      // switch to other gradient on completion      if (this.flag === 1) {        this.flag = 0;        this.gradientOptions = {          angle: 45,          colors: [            ["#edde5d", 0],            ["#f09819", 1]          ]        };      } else {        this.flag = 1;        this.gradientOptions = {          angle: 45,          colors: [            ["#ffee113e", 0],            ["#ff1a3864", 1]          ]        };      }    }  })
duration

: animation duration in milliseconds. curve: easing curve; Curve.EaseInOut for gentle start/end. onFinish: callback that modifies gradientOptions to trigger the next cycle.

3. Effect

A smooth 45-degree transition between orange-yellow and purple-red, 2 seconds per switch, auto-reversing infinitely.

4. Limitations

Advantage: simple, minimal code, ideal for basic color switching. Limitation: poor support for gradient angle changes . Changing angle simultaneously with colors causes jerky, discontinuous transitions because property animation cannot smoothly interpolate compound objects like LinearGradientOptions, especially non-linear properties like angle. For dynamic direction, color count, or stop position changes, keyframe animation is needed.

Approach 2: Keyframe Animation — Complex, Free-Form Dynamic Gradients

1. Principle

Create a gradient layer far larger than the parent container (e.g., 500% width/height), then use keyframe animation to control the layer's position, revealing different portions through the fixed "window" of the clipped parent.

Analogy: moving a giant canvas behind a window :

Outer container : fixed size, clip(true) acts as the window; only content within bounds is visible.

Inner gradient layer : 500% size, complex multi-color gradient, the "giant canvas".

Position animation : keyframes drive the inner layer's position; as it moves, the visible gradient region changes, creating flow, rotation, etc.

Benefits: excellent performance (moves pre-rendered layer, avoids repeated gradient recalculation), high freedom (arbitrary color, direction, path combinations), stunning visuals (flow, rotation, shimmer).

2. Implementation Steps

2.1 Define Position State

@State pos: Position = { x: "0%", y: "0%" };
Position

: HarmonyOS type with x and y offsets.

Initial (0%, 0%) aligns inner layer's top-left with outer container's top-left.

2.2 Build UI Structure

Column() { // outer container  Column() // gradient layer    .position(this.pos)    .width("500%")    .height("500%")    .linearGradient({      angle: 60,      colors: [        ["#ffed5d8b", 0],        ["#ffb7f019", 0.2],        ["#ffae8215", 0.4],        ["#ff354bb8", 0.6],        ["#ffd3ed5d", 0.8],        ["#ff590f86", 1]      ]    })}.width(200).height(200).clip(true) // key: clip overflow for window effect

Outer: fixed 200×200, transparent background, clip(true) hides overflow.

Inner: 1000×1000 (5×), 60-degree gradient with 6 color stops. position(this.pos) binds layer position to state.

2.3 Define Keyframe Animation

private uiContext?: UIContext;aboutToAppear() {  this.uiContext = this.getUIContext(); // obtain UIContext}ani(): void {  this.uiContext?.keyframeAnimateTo(    { iterations: -1 }, // infinite loop    [      // each object = one animation segment      { duration: 1, event: () => { this.pos = { x: "0%", y: "-400%" } } },      { duration: 3000, event: () => { this.pos = { x: "0%", y: "0%" } } },      { duration: 3000, event: () => { this.pos = { x: "-400%", y: "0%" } } },      { duration: 3000, event: () => { this.pos = { x: "-400%", y: "-400%" } } },      { duration: 3000, event: () => { this.pos = { x: "0%", y: "-400%" } } },      { duration: 4242, event: () => { this.pos = { x: "-400%", y: "0%" } } },      { duration: 3000, event: () => { this.pos = { x: "0%", y: "0%" } } },      { duration: 4242, event: () => { this.pos = { x: "-400%", y: "-400%" } } },      { duration: 3000, event: () => { this.pos = { x: "0%", y: "-400%" } } },    ]  );}
keyframeAnimateTo

: HarmonyOS keyframe API; first param = config, second = keyframe array. iterations: -1 = infinite loop.

Each keyframe has duration (segment length) and event (property changes); system interpolates between them.

Position range -400% to 0% because layer is 500% size; -400% shifts layer by 4× its width/height, revealing different regions.

Keyframe path illustration : the animation moves the layer among four corners, like a giant gradient curtain moving behind a fixed window, creating rich, fluid, non-repeating dynamic backgrounds.

2.4 Start Animation

onDidBuild(): void {  this.ani();}

3. Effect

Colorful, complex-path dynamic gradient flowing and rotating like a living background.

4. Advantages & Considerations

Advantages :

Performance : moves single layer, GPU-friendly, more efficient than frequent gradient redraws.

Unlimited possibilities : adjust keyframe paths, colors, layer size for dreamlike effects.

Smooth : keyframe interpolation guarantees fluid motion.

Considerations :

Memory : large layer but vector gradient keeps memory manageable; bitmaps would need caution.

Boundary handling : ensure keyframe positions stay within layer bounds to avoid uncovered window areas.

Responsive design : if container size changes, recalculate position ranges; use @Watch to listen for size changes and dynamically adjust animation parameters.

Performance Optimization & Advanced Techniques

1. Use @Watch for Size Changes

@State containerWidth: number = 200;@State containerHeight: number = 200;@Watch('onContainerSizeChange')@State pos: Position = { x: "0%", y: "0%" };onContainerSizeChange() {  // recalculate inner size and animation params on container resize  // can restart animation}

2. Encapsulate with @Builder for Reuse

@Componentstruct DynamicGradientBackground {  @Prop width: number;  @Prop height: number;  @State pos: Position = { x: "0%", y: "0%" };  private uiContext?: UIContext;  aboutToAppear() {    this.uiContext = this.getUIContext();    this.startAnimation();  }  startAnimation() {    // keyframe animation code  }  build() {    Column() {      Column()        .position(this.pos)        .width("500%")        .height("500%")        .linearGradient({          angle: 60,          colors: [/* custom gradient */]        })    }    .width(this.width)    .height(this.height)    .clip(true)  }}

Usage:

DynamicGradientBackground({ width: 300, height: 300 })

3. Control Animation State

@State isAnimating: boolean = true;startAnimation() {  if (!this.isAnimating) return;  // start keyframe animation}onPageHide() {  this.isAnimating = false;  // stop animation (needs extra logic)}onPageShow() {  this.isAnimating = true;  this.startAnimation();}

Pause when page hidden to save resources.

4. Dynamically Adjust Gradient Parameters

In keyframe approach, gradient colors/angle are static. To change gradient itself (e.g., colors over time), combine with property animation to modify gradient config while moving the layer; watch performance cost.

Comparison & Selection Guide

Complexity : Property Animation = Simple; Keyframe Animation = Medium.

Performance : Property Animation = Medium (frequent gradient redraw); Keyframe Animation = High (layer move only).

Angle Change Support : Property Animation = Poor; Keyframe Animation = Good (via movement).

Complex Path Support : Property Animation = No; Keyframe Animation = Yes.

Infinite Loop : Property Animation = Via callback; Keyframe Animation = Built-in.

Best For : Property Animation = Simple color switching; Keyframe Animation = Complex flow, rotation effects.

Selection advice :

Only need a few color switches, no angle change → Property Animation (concise code).

Need rainbow flow, light rotation, or dynamic direction → Keyframe Animation .

Complete Demo Code

Full runnable example including both approaches with component encapsulation:

import { UIContext } from '@ohos.arkui.UIContext';@Entry@Componentstruct DynamicGradientDemo {  @State currentScheme: number = 0; // 0: property, 1: keyframe  build() {    Column() {      // scheme selector      Row() {        Button("Property Animation")          .onClick(() => this.currentScheme = 0)          .margin(10)        Button("Keyframe Animation")          .onClick(() => this.currentScheme = 1)          .margin(10)      }      .width("100%")      .height(50)      .justifyContent(FlexAlign.Center)      // display area      if (this.currentScheme === 0) {        PropertyAnimationDemo()      } else {        KeyframeAnimationDemo()      }    }    .width("100%")    .height("100%")    .backgroundColor(Color.White)  }}// Approach 1: Property Animation@Componentstruct PropertyAnimationDemo {  @State gradientOptions: LinearGradientOptions = {    angle: 45,    colors: [["#f09819", 0], ["#edde5d", 1]]  };  @State flag: number = 1;  aboutToAppear() {    setTimeout(() => {      this.gradientOptions = {        angle: 45,        colors: [["#ffee113e", 0], ["#ff1a3864", 1]]      };    }, 100);  }  build() {    Column()      .width(200)      .height(200)      .linearGradient(this.gradientOptions)      .animation({        duration: 2000,        curve: Curve.EaseInOut,        onFinish: () => {          if (this.flag === 1) {            this.flag = 0;            this.gradientOptions = {              angle: 45,              colors: [["#edde5d", 0], ["#f09819", 1]]            };          } else {            this.flag = 1;            this.gradientOptions = {              angle: 45,              colors: [["#ffee113e", 0], ["#ff1a3864", 1]]            };          }        }      })  }}// Approach 2: Keyframe Animation@Componentstruct KeyframeAnimationDemo {  @State pos: Position = { x: "0%", y: "0%" };  private uiContext?: UIContext;  aboutToAppear() {    this.uiContext = this.getUIContext();    this.startAnimation();  }  startAnimation() {    this.uiContext?.keyframeAnimateTo(      { iterations: -1 },      [        { duration: 1, event: () => { this.pos = { x: "0%", y: "-400%" } } },        { duration: 3000, event: () => { this.pos = { x: "0%", y: "0%" } } },        { duration: 3000, event: () => { this.pos = { x: "-400%", y: "0%" } } },        { duration: 3000, event: () => { this.pos = { x: "-400%", y: "-400%" } } },        { duration: 3000, event: () => { this.pos = { x: "0%", y: "-400%" } } },        { duration: 4242, event: () => { this.pos = { x: "-400%", y: "0%" } } },        { duration: 3000, event: () => { this.pos = { x: "0%", y: "0%" } } },        { duration: 4242, event: () => { this.pos = { x: "-400%", y: "-400%" } } },        { duration: 3000, event: () => { this.pos = { x: "0%", y: "-400%" } } },      ]    );  }  build() {    Column() {      Column()        .position(this.pos)        .width("500%")        .height("500%")        .linearGradient({          angle: 60,          colors: [            ["#ffed5d8b", 0],            ["#ffb7f019", 0.2],            ["#ffae8215", 0.4],            ["#ff354bb8", 0.6],            ["#ffd3ed5d", 0.8],            ["#ff590f86", 1]          ]        })    }    .width(200)    .height(200)    .clip(true)  }}

Summary

This article covers two mainstream approaches for dynamic gradient backgrounds in HarmonyOS apps:

Property Animation : suited for simple color switching, concise and beginner-friendly.

Keyframe Animation : moves a giant gradient canvas to achieve complex, fluid effects with excellent performance and high freedom.

Through comparison and complete code, developers can choose the right approach for their visual needs, or even combine both for unique experiences.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

HarmonyOSMobile UIArkUIProperty AnimationAnimation PerformanceDynamic GradientKeyframe AnimationLinearGradient
51CTO HarmonyOS Developer Community
Written by

51CTO HarmonyOS Developer Community

The HarmonyOS Developer Community is a learning-oriented community for developers to learn, communicate, ask questions, and share.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.