Animating the slide component by binding a gesture to the animation state

Another way to get a wow experience from users is to have great-looking introduction slides. A typical app would have three to five slides to describe what the app does and how it will benefit the users. Today, many apps even add videos or interactive screens so that the users can get a feel for how the app may work. Such an interactive animation will require some internal development to bind touch gestures to the animation state. Animating based on a specific state is very difficult because you really have to get granular gesture data. On the other hand, it's a lot easier to just animate at the beginning or ending of a state. For example, you could animate an object inside a slide when the slide completely shows up on the screen after a left swipe. However, this animation effect is not as interesting or attractive as binding the animation during the touch movement.

The app you will build in this section will have three slides that will animate when you swipe left or right:

Animating the slide component by binding a gesture to the animation state

You will see fade in and fade out animation effects between slides. The following Angular logo also moves up when you swipe left from the second slide:

Animating the slide component by binding a gesture to the animation state

Getting ready

There is no need to test the app in a physical device because the animation is done via HTML and JavaScript. However, it's recommended to test the app in your device to evaluate to animation performance.

How to do it...

Here are the instructions:

  1. Create a new SliderAnimation app using the blank template, as follows, and go to the SliderAnimation folder:
    $ ionic start SliderAnimation blank --v2
    $ cd SliderAnimation
    
  2. Open the ./src/pages/home/home.html file and modify its content with the following code:
    <ion-content class="home">
      <div class="slides-float">
        
        <div class="slide-float" #slidefloat1>
          <ion-icon name="ios-ionic"></ion-icon> 
          <h1>Ionic 2</h1>
        </div>
    
        <div class="slide-float" #slidefloat2>
          <ion-icon name="logo-angular"></ion-icon> 
          <h1>Angular 2</h1>
        </div>
    
        <div class="slide-float" #slidefloat3>
          <ion-icon name="logo-javascript"></ion-icon> 
          <h1>Both</h1>
        </div>
    
      </div>
      
      <ion-slides #myslides pager (ionDrag)="onMove()">
    
        <ion-slide>
          <h2>is Beautiful</h2>
        </ion-slide>
    
        <ion-slide>
          <h2>is Fast</h2>
        </ion-slide>
    
        <ion-slide>
          <h2>are Awesome</h2>
        </ion-slide>
    
      </ion-slides>
    </ion-content>

    This template mainly uses the <ion-slides> tag. However, there are some layers to float on top of the <ion-slide> tag in order to animate them separately.

  3. After this, replace the content of ./src/pages/home/home.ts with the following:
    import { Component, ViewChild } from '@angular/core';
    import { NavController } from 'ionic-angular';
    
    @Component({
      selector: 'page-home',
      templateUrl: 'home.html'
    })
    export class HomePage {
      @ViewChild('myslides') myslides;
      @ViewChild('slidefloat1') slidefloat1;
      @ViewChild('slidefloat2') slidefloat2;
      @ViewChild('slidefloat3') slidefloat3;
      private rAf: any;
      private bindOnProgress: boolean = false;
      
      constructor(public navCtrl: NavController) {
        this.rAf = (function(){
          return  (window as any).requestAnimationFrame || (window as any).webkitRequestAnimationFrame || (window as any).mozRequestAnimationFrame ||
            function( callback ){
              window.setTimeout(callback, 1000 / 60);
            };
        })();
      }
      
      onMove() {
        if (!this.bindOnProgress) {
          this.bindOnProgress = true;
          
          this.myslides.slider.on('onProgress', (swiper, progress) => {
            
            // (0, 1) - (0.25, 0) ==> (0-1)/(0.25-0) => -1/0.25 * x + 1
            let firstQuarter = () => {
              let slidefloat1Opacity = -1/0.25 * progress + 1;
              console.log('slidefloat1Opacity: ' + slidefloat1Opacity);
              this.slidefloat1.nativeElement.style.opacity =  slidefloat1Opacity;
              this.slidefloat2.nativeElement.style.opacity = 0;
            }
            
            // (0.25, 0) - (0.5, 1) ==> (1-0)/(0.5-0.25) => 1/0.25 * x - 1 = 4*x - 1
            let secondQuarter = () => {
              let slidefloat2Opacity = 4 * progress - 1;
              console.log('slidefloat2Opacity: ' + slidefloat2Opacity);
              this.slidefloat2.nativeElement.style.opacity =  slidefloat2Opacity;
              this.slidefloat2.nativeElement.style.transform = 'translateY(0px)';
              this.slidefloat1.nativeElement.style.opacity = 0;
            }
            
            // (0.5, 0) - (0.75, -250) ==> (-250-0)/(0.75-0.5) = -250/0.25 => -1000*x + 500
            let thirdQuarter = () => {
              let slidefloat2transform = -1000 * progress + 500;
              console.log('slidefloat2transform: ' + slidefloat2transform);
              this.slidefloat2.nativeElement.style.transform = 'translateY(' + slidefloat2transform + 'px)';
              this.slidefloat3.nativeElement.style.opacity = 0;
            }
            
            // (0.75, 0) - (1, 1) ==> (1-0)/(1-0.75) => 1/0.25 * x - 0.75*4 = 4*x - 3
            let fourthQuarter = () => {
              let slidefloat3Opacity = 4 * progress - 3;
              console.log('slidefloat3Opacity: ' + slidefloat3Opacity);
              this.slidefloat3.nativeElement.style.opacity = slidefloat3Opacity;
              this.slidefloat2.nativeElement.style.transform = 'translateY(-250px)';
            }
            
            // Animate per quarter of the total 3 slides
            if (progress <= 0.25) { 
              this.rAf(firstQuarter);
            } else if ((progress > 0.25) && (progress <= 0.5 )) {
              this.rAf(secondQuarter);
            } else if ((progress > 0.5) && (progress <= 0.75 )) {
              this.rAf(thirdQuarter);
            } else if ((progress > 0.75) && (progress <= 1 )) {
              this.rAf(fourthQuarter);
            }
            
          });
          
        }
        
      }
    }

    Note that the comments are useful to calculate an animation formula for each object.

  4. Edit ./app/pages/home/home.scss with the following:
    .slides-float {
      .slide-float {
        top: 0;
        position: fixed;
        width: 100%;
        margin-top: 20px;
      }
    }
    
    .home {
      background-color: DarkSlateBlue;
      
      h2 {
        font-size: 3rem;
      }
      
      ion-slide {
        color: white;
        background-color: transparent;
      }  
      
      .slides-float {
        color: white;
        text-align: center;
        
        > .slide-float:nth-child(2), > .slide-float:nth-child(3) {
          opacity: 0;  
        }
      }
      
      .slide-float {
        ion-icon {
          font-size: 150px;
        }
        
        h1 {
          font-weight: lighter;
          font-size: 60px;
          margin-top: 0;
        }
      }
    }
  5. Go to your Terminal and run the app with the following command:
    $ ionic serve
    

How it works…

This is the general process for animation:

  1. Since there are three slides, the user has to swipe twice to reach the end. This means that the first swipe will be at 50% progress.
  2. When a user swipes left to 25%, the Ionic logo will fade out.
  3. When a user swipes to 50%, the Angular logo will fade in for the second slide.
  4. When a user swipes to 75%, the Angular logo will move up to disappear instead of fading out.
  5. Finally, in the last 75% to 100%, the JavaScript logo will fade in.

You probably noted that the amount of fade or movement will depend on the progress percentage. Thus, if you swipe left and right a little bit, you can see the animation responding to the gesture right away. There are two layers in the template. The floating static layer, as illustrated, must be on top and it must stay at the same position regardless of which slide is current:

  <div class="slides-float">
    
    <div class="slide-float" #slidefloat1>
      <ion-icon name="ios-ionic"></ion-icon> 
      <h1>Ionic 2</h1>
    </div>

    <div class="slide-float" #slidefloat2>
      <ion-icon name="logo-angular"></ion-icon> 
      <h1>Angular 2</h1>
    </div>

    <div class="slide-float" #slidefloat3>
      <ion-icon name="logo-javascript"></ion-icon> 
      <h1>Both</h1>
    </div>

  </div>

The bottom layer is your typical <ion-slides>, as shown:

  <ion-slides #myslides pager (ionDrag)="onMove()">

    <ion-slide>
      <h2>is Beautiful</h2>
    </ion-slide>

    <ion-slide>
      <h2>is Fast</h2>
    </ion-slide>

    <ion-slide>
      <h2>are Awesome</h2>
    </ion-slide>

  </ion-slides>

When you swipe, it's actually moving <ion-slide>. However, it also triggers the onMove() method because you bind it with the move event. The onMove() method will access #slidefloat1, #slidefloat2, and #slidefloat3 from the floating <div> layer. The home.ts file is where you have to animate these individual floating slides.

There are several variables that you need to declare in the home.ts file. You need to be able to access the <ion-slides> object in order to call the native Swiper methods:

  @ViewChild('myslides') myslides;

According to the Ionic documentation, the <ion-slides> object is written based on the Swiper library at http://ionicframework.com/docs/v2/api/components/slides/Slides/.

You need to bind it with the swiping event natively in order to get the correct progress data.

The following three variables are necessary to access each floating slide:

  @ViewChild('slidefloat1') slidefloat1;
  @ViewChild('slidefloat2') slidefloat2;
  @ViewChild('slidefloat3') slidefloat3;

You need to leverage requestAnimationFrame, as follows, for the best animation performance:

  private rAf: any;

Otherwise, users will sense a jerky movement during a swipe because your animation is not at 60 FPS.

Lastly, you need to bind the swipe event only once; thus, it's necessary to have a Boolean toggle to detect the binding event:

  private bindOnProgress: boolean = false;

The following code shows how to create a requestAnimationFrame object to call whichever function is to be rendered later:

this.rAf = (function(){
  return  (window as any).requestAnimationFrame || (window as any).webkitRequestAnimationFrame || (window as any).mozRequestAnimationFrame ||
    function( callback ){
      window.setTimeout(callback, 1000 / 60);
    };
})();

The onMove() method is where you put all the animation logic, which must bind with the onProgress event, as follows:

this.myslides.slider.on('onProgress', {})

First, let's take a look at the code at the bottom of onMove(), as shown:

if (progress <= 0.25) { 
  this.rAf(firstQuarter);
} else if ((progress > 0.25) && (progress <= 0.5 )) {
  this.rAf(secondQuarter);
} else if ((progress > 0.5) && (progress <= 0.75 )) {
  this.rAf(thirdQuarter);
} else if ((progress > 0.75) && (progress <= 1 )) {
  this.rAf(fourthQuarter);
}

Basically, you want to have four quarters (or segments) of animation. When you swipe from slide 1 to slide 2, it will trigger the firstQuarter and secondQuarter methods. That is, you want to fade out the first floating slide and fade in the second floating slide at the end of the process. The concept is similar for the thirdQuarter and fourthQuarter methods. Note that you don't want to call the method directly but just pass the function reference inside this.rAf to have the rendering engine manage the frame rate. Otherwise, the rendered function may end up blocking other processes in the UI, which causes jerky movement.

For each of the quarters, you only have to change the style property, given a known progress value, as shown:

let firstQuarter = () => {
  let slidefloat1Opacity = -1/0.25 * progress + 1;
  console.log('slidefloat1Opacity: ' + slidefloat1Opacity);
  this.slidefloat1.nativeElement.style.opacity =  slidefloat1Opacity;
  this.slidefloat2.nativeElement.style.opacity = 0;
}

It's important to use the arrow function here so that you can access the this context. You have to call this.slidefloat2.nativeElement to get to the <div> DOM object. It's really up to you to write your own math function to calculate the position or opacity during the slide movement with the progress value. In this example, the slidefloat1Opacity variable is just a linear function based on the progress input value.

The secondQuarter follows the same approach. However, the thirdQuarter uses the transform property instead of opacity, as illustrated:

let thirdQuarter = () => {
  let slidefloat2transform = -1000 * progress + 500;
  console.log('slidefloat2transform: ' + slidefloat2transform);
  this.slidefloat2.nativeElement.style.transform = 'translateY(' + slidefloat2transform + 'px)';
  this.slidefloat3.nativeElement.style.opacity = 0;
}

There are many ways to make a DOM object change its position. However, it's best to leverage the transform property instead of using the left and top properties. You want to achieve the highest Frame Per Second. In the thirdQuarter method, your slidefloat2transform will be calculated and it will update a new Y position using translateY().

Tip

Note that you must use this.bindOnProgress to disable another event binding to onProgress because, for each swipe, it will continue to add more events.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.84.175