Ionic动画效果 #

一、动画概述 #

1.1 Ionic动画系统 #

Ionic提供两种动画方式:

text
动画系统
    │
    ├── Ionic Animations API
    │   ├── 编程式动画
    │   ├── 性能优化
    │   └── 跨平台一致
    │
    └── CSS动画
        ├── 简单易用
        ├── 声明式
        └── 原生性能

1.2 动画类型 #

类型 说明 使用场景
入场动画 元素出现 页面进入、模态框打开
出场动画 元素消失 页面离开、模态框关闭
交互动画 用户反馈 点击、滑动、拖拽
过渡动画 状态切换 展开/收起、切换

二、Ionic Animations API #

2.1 基本用法 #

typescript
import { Component, ElementRef } from '@angular/core';
import { Animation, AnimationController } from '@ionic/angular';

@Component({
  selector: 'app-home',
  template: `
    <div #myElement class="box">动画元素</div>
    <ion-button (click)="playAnimation()">播放动画</ion-button>
  `
})
export class HomePage {
  private animation: Animation;
  
  constructor(
    private animationCtrl: AnimationController,
    private element: ElementRef
  ) {}
  
  ngAfterViewInit() {
    this.animation = this.animationCtrl
      .create()
      .addElement(this.element.nativeElement.querySelector('.box'))
      .duration(1000)
      .fromTo('opacity', '0', '1')
      .fromTo('transform', 'translateX(-100px)', 'translateX(0)');
  }
  
  playAnimation() {
    this.animation.play();
  }
}

2.2 动画属性 #

typescript
const animation = this.animationCtrl
  .create()
  .addElement(element)
  // 持续时间
  .duration(1000)
  // 延迟
  .delay(500)
  // 缓动函数
  .easing('ease-out')
  // 迭代次数
  .iterations(Infinity)
  // 方向
  .direction('alternate')
  // 填充模式
  .fill('forwards');

2.3 关键帧动画 #

typescript
const animation = this.animationCtrl
  .create()
  .addElement(element)
  .duration(2000)
  .keyframes([
    { offset: 0, transform: 'scale(1)', opacity: '1' },
    { offset: 0.5, transform: 'scale(1.2)', opacity: '0.8' },
    { offset: 1, transform: 'scale(1)', opacity: '1' }
  ]);

2.4 组合动画 #

typescript
const animationA = this.animationCtrl
  .create()
  .addElement(elementA)
  .fromTo('opacity', '0', '1');

const animationB = this.animationCtrl
  .create()
  .addElement(elementB)
  .fromTo('transform', 'translateY(100px)', 'translateY(0)');

// 并行执行
const parallel = this.animationCtrl
  .create()
  .addAnimation([animationA, animationB]);

parallel.play();

// 顺序执行
const sequence = this.animationCtrl
  .create()
  .addAnimation([animationA])
  .addAnimation([animationB]);

sequence.play();

2.5 动画事件 #

typescript
const animation = this.animationCtrl
  .create()
  .addElement(element)
  .duration(1000)
  .fromTo('opacity', '0', '1');

// 动画开始
animation.onStart(() => {
  console.log('动画开始');
});

// 动画结束
animation.onEnd(() => {
  console.log('动画结束');
});

// 动画迭代
animation.onIteration(() => {
  console.log('动画迭代');
});

animation.play();

2.6 动画控制 #

typescript
// 播放
animation.play();

// 暂停
animation.pause();

// 停止
animation.stop();

// 销毁
animation.destroy();

// 反向播放
animation.reverse();

// 跳转到特定时间点
animation.progressStart();
animation.progressStep(0.5);
animation.progressEnd(1);

三、CSS动画 #

3.1 过渡动画 #

scss
.box {
  transition: all 0.3s ease;
  transform: scale(1);
}

.box:hover {
  transform: scale(1.1);
}

.box:active {
  transform: scale(0.95);
}

3.2 关键帧动画 #

scss
@keyframes fadeIn {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.fade-in {
  animation: fadeIn 0.5s ease forwards;
}

3.3 常用动画 #

scss
// 淡入
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

// 淡出
@keyframes fadeOut {
  from { opacity: 1; }
  to { opacity: 0; }
}

// 滑入
@keyframes slideInUp {
  from {
    transform: translateY(100%);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}

// 滑出
@keyframes slideOutDown {
  from {
    transform: translateY(0);
    opacity: 1;
  }
  to {
    transform: translateY(100%);
    opacity: 0;
  }
}

// 缩放
@keyframes scaleIn {
  from {
    transform: scale(0);
    opacity: 0;
  }
  to {
    transform: scale(1);
    opacity: 1;
  }
}

// 旋转
@keyframes rotate {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

// 弹跳
@keyframes bounce {
  0%, 20%, 50%, 80%, 100% {
    transform: translateY(0);
  }
  40% {
    transform: translateY(-20px);
  }
  60% {
    transform: translateY(-10px);
  }
}

// 脉冲
@keyframes pulse {
  0% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.1);
  }
  100% {
    transform: scale(1);
  }
}

// 摇晃
@keyframes shake {
  0%, 100% {
    transform: translateX(0);
  }
  10%, 30%, 50%, 70%, 90% {
    transform: translateX(-5px);
  }
  20%, 40%, 60%, 80% {
    transform: translateX(5px);
  }
}

3.4 动画类 #

scss
// 动画类
.animate-fade-in {
  animation: fadeIn 0.5s ease forwards;
}

.animate-slide-up {
  animation: slideInUp 0.5s ease forwards;
}

.animate-scale {
  animation: scaleIn 0.3s ease forwards;
}

.animate-bounce {
  animation: bounce 1s ease infinite;
}

.animate-pulse {
  animation: pulse 1s ease infinite;
}

.animate-shake {
  animation: shake 0.5s ease;
}

.animate-rotate {
  animation: rotate 1s linear infinite;
}

// 延迟类
.delay-100 { animation-delay: 100ms; }
.delay-200 { animation-delay: 200ms; }
.delay-300 { animation-delay: 300ms; }
.delay-500 { animation-delay: 500ms; }
.delay-1000 { animation-delay: 1000ms; }

// 持续时间类
.duration-100 { animation-duration: 100ms; }
.duration-200 { animation-duration: 200ms; }
.duration-300 { animation-duration: 300ms; }
.duration-500 { animation-duration: 500ms; }
.duration-1000 { animation-duration: 1000ms; }

四、页面过渡动画 #

4.1 自定义页面过渡 #

typescript
import { Component } from '@angular/core';
import { Animation, AnimationController } from '@ionic/angular';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html'
})
export class HomePage {
  constructor(private animationCtrl: AnimationController) {}
  
  // 自定义进入动画
  enterAnimation = (baseEl: HTMLElement) => {
    const animation = this.animationCtrl
      .create()
      .addElement(baseEl)
      .duration(500)
      .fromTo('opacity', '0', '1')
      .fromTo('transform', 'translateX(100%)', 'translateX(0)');
    
    return animation;
  }
  
  // 自定义离开动画
  leaveAnimation = (baseEl: HTMLElement) => {
    const animation = this.animationCtrl
      .create()
      .addElement(baseEl)
      .duration(500)
      .fromTo('opacity', '1', '0')
      .fromTo('transform', 'translateX(0)', 'translateX(-100%)');
    
    return animation;
  }
}

4.2 模态框动画 #

typescript
import { Component } from '@angular/core';
import { ModalController } from '@ionic/angular';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html'
})
export class HomePage {
  constructor(private modalCtrl: ModalController) {}
  
  async openModal() {
    const modal = await this.modalCtrl.create({
      component: DetailModalComponent,
      enterAnimation: this.enterAnimation,
      leaveAnimation: this.leaveAnimation
    });
    
    await modal.present();
  }
  
  enterAnimation = (baseEl: HTMLElement) => {
    const backdropAnimation = this.animationCtrl
      .create()
      .addElement(baseEl.querySelector('ion-backdrop')!)
      .fromTo('opacity', '0.01', '0.4');
    
    const wrapperAnimation = this.animationCtrl
      .create()
      .addElement(baseEl.querySelector('.modal-wrapper')!)
      .fromTo('transform', 'translateY(100%)', 'translateY(0)')
      .fromTo('opacity', '0', '1');
    
    return this.animationCtrl
      .create()
      .addElement(baseEl)
      .easing('ease-out')
      .duration(300)
      .addAnimation([backdropAnimation, wrapperAnimation]);
  }
  
  leaveAnimation = (baseEl: HTMLElement) => {
    return this.enterAnimation(baseEl).direction('reverse');
  }
}

五、交互动画 #

5.1 点击反馈 #

scss
.button-press {
  transition: transform 0.1s ease;
  
  &:active {
    transform: scale(0.95);
  }
}

5.2 滑动动画 #

typescript
import { Component } from '@angular/core';
import { Gesture, GestureController } from '@ionic/angular';

@Component({
  selector: 'app-home',
  template: `
    <div #card class="swipe-card">滑动卡片</div>
  `
})
export class HomePage {
  private gesture: Gesture;
  
  constructor(private gestureCtrl: GestureController) {}
  
  ngAfterViewInit() {
    this.gesture = this.gestureCtrl.create({
      el: this.card.nativeElement,
      gestureName: 'swipe',
      onMove: (detail) => {
        this.card.nativeElement.style.transform = 
          `translateX(${detail.deltaX}px)`;
      },
      onEnd: (detail) => {
        if (Math.abs(detail.deltaX) > 100) {
          // 滑出屏幕
          this.card.nativeElement.style.transform = 
            `translateX(${detail.deltaX > 0 ? 500 : -500}px)`;
        } else {
          // 回弹
          this.card.nativeElement.style.transform = 'translateX(0)';
        }
      }
    });
    
    this.gesture.enable();
  }
}

5.3 拖拽动画 #

typescript
import { Component, ElementRef } from '@angular/core';
import { Gesture, GestureController } from '@ionic/angular';

@Component({
  selector: 'app-home',
  template: `
    <div #draggable class="draggable">拖拽我</div>
  `
})
export class HomePage {
  private gesture: Gesture;
  private startX = 0;
  private startY = 0;
  
  constructor(
    private gestureCtrl: GestureController,
    private element: ElementRef
  ) {}
  
  ngAfterViewInit() {
    const draggable = this.element.nativeElement.querySelector('.draggable');
    
    this.gesture = this.gestureCtrl.create({
      el: draggable,
      gestureName: 'drag',
      onStart: () => {
        const rect = draggable.getBoundingClientRect();
        this.startX = rect.left;
        this.startY = rect.top;
      },
      onMove: (detail) => {
        draggable.style.transform = 
          `translate(${detail.deltaX}px, ${detail.deltaY}px)`;
      }
    });
    
    this.gesture.enable();
  }
}

六、加载动画 #

6.1 加载指示器 #

typescript
import { Component } from '@angular/core';
import { LoadingController } from '@ionic/angular';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html'
})
export class HomePage {
  constructor(private loadingCtrl: LoadingController) {}
  
  async showLoading() {
    const loading = await this.loadingCtrl.create({
      message: '加载中...',
      spinner: 'crescent',
      duration: 2000
    });
    
    await loading.present();
  }
}

6.2 骨架屏动画 #

scss
.skeleton {
  background: linear-gradient(
    90deg,
    #f0f0f0 25%,
    #e0e0e0 50%,
    #f0f0f0 75%
  );
  background-size: 200% 100%;
  animation: skeleton-loading 1.5s infinite;
}

@keyframes skeleton-loading {
  0% {
    background-position: 200% 0;
  }
  100% {
    background-position: -200% 0;
  }
}

七、动画性能优化 #

7.1 使用transform #

scss
// 推荐:使用transform
.element {
  transition: transform 0.3s ease;
}

.element:hover {
  transform: translateX(100px);
}

// 不推荐:改变位置属性
.element {
  transition: left 0.3s ease;
}

.element:hover {
  left: 100px;
}

7.2 使用will-change #

scss
.animated-element {
  will-change: transform, opacity;
}

7.3 减少重绘 #

scss
// 使用GPU加速
.gpu-accelerated {
  transform: translateZ(0);
  backface-visibility: hidden;
  perspective: 1000px;
}

八、最佳实践 #

8.1 动画时长 #

scss
// 推荐的动画时长
$duration-fast: 150ms;    // 微交互
$duration-normal: 300ms;  // 常规动画
$duration-slow: 500ms;    // 复杂动画

8.2 缓动函数 #

scss
// 常用缓动函数
$ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
$ease-out: cubic-bezier(0, 0, 0.2, 1);
$ease-in: cubic-bezier(0.4, 0, 1, 1);
$bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);

8.3 无障碍考虑 #

scss
// 尊重用户偏好
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

九、总结 #

9.1 动画要点 #

要点 说明
Ionic Animations 编程式动画,性能优化
CSS动画 简单易用,声明式
页面过渡 自定义进入/离开动画
交互动画 点击、滑动、拖拽
性能优化 transform、will-change

9.2 下一步 #

掌握了动画效果后,接下来让我们学习 路由基础,了解Ionic的导航系统!

最后更新:2026-03-28