All files / src/effects paper-ripple.ts

17.11% Statements 13/76
0% Branches 0/50
5.88% Functions 1/17
16.67% Lines 12/72
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217              1x                                                                                       1x                                       1x                                       1x                               1x       1x                                                     1x                           1x                     1x                     1x                               1x                                                 1x  
import { PaperWave } from './paper-wave';
import { PLATFORM } from 'aurelia-pal';
 
/**
 * Provides all the logic to produce ripple visual effect.
 * Other elements can use it to simulate rippling effect emanating from the point of contact.
 */
export class PaperRipple {
  /**
   * Gets or sets the initial opacity of the each wave.
   */
  public initialOpacity: number;
 
  /**
   * Gets or sets how fast (opacity per second) the wave fades out.
   */
  public opacityDecayVelocity: number;
 
  /**
   * The object wrapper containing the `$background` DOM element belongs to the current instance.
   */
  public $background: HTMLElement;
 
  /**
   * The object wrapper containing the waves container belongs to the current instance.
   */
  public $waves: HTMLElement;
 
  /**
   * The object wrapper containing all the DOM elements belongs to the current instance.
   */
  public $: HTMLElement;
 
  private waves: PaperWave[] = [];
 
  /**
   * Initializes a new instance of the `PaperRipple` class with the specified `config` object.
   */
  constructor(cfg: any = {}) {
    this.initialOpacity = cfg.initialOpacity || 0.25;
    this.opacityDecayVelocity = cfg.opacityDecayVelocity || 0.8;
    this.initTarget(cfg && cfg.nodeType ? cfg : cfg.target && cfg.target.nodeType ? cfg.target : null);
    this.recenters = cfg.recenters || this.recenters;
    this.center = cfg.center || this.center;
    this.round = cfg.round || this.round;
  }
 
  /**
   * Determines whether all the waves should be re-centered towards the center of the container.
   * @returns If `true`, waves will exhibit a gravitational pull towards the center of the container as they fade away.
   */
  get recenters(): boolean {
    return this.$.hasAttribute('recenters');
  }
 
  /**
   * Sets the value that indicates whether all the waves should be re-centered towards the center of the container.
   * @returns Nothing.
   */
  set recenters(newValue: boolean) {
    if (newValue) {
      this.$.setAttribute('recenters', '');
    } else {
      this.$.removeAttribute('recenters');
    }
  }
 
  /**
   * Determines whether all the waves should start a movement from the center of the container.
   * @returns If `true`, waves will center inside its container
   */
  get center(): boolean {
    return this.$.hasAttribute('center');
  }
 
  /**
   * Sets the value that indicates whether all the waves should start a movement from the center of the container.
   * @returns Nothing.
   */
  set center(newValue: boolean) {
    if (newValue) {
      this.$.setAttribute('center', '');
    } else {
      this.$.removeAttribute('center');
    }
  }
 
  /**
   * Determines whether ripple effect should apply within a circle.
   * @returns If `true`, ripple effect will apply within a circle.
   */
  get round(): boolean {
    return this.$.classList.contains('paper-ripple--round');
  }
 
  /**
   * Sets the value that indicates whether ripple effect should apply within a circle.
   * @returns Nothing.
   */
  set round(newValue: boolean) {
    this.$.classList.toggle('paper-ripple--round', newValue);
  }
 
  /**
   * Determines whether the ripple should keep animating or not.
   * @returns `true`, if so, otherwise `false`.
   */
  get shouldKeepAnimating() {
    return this.waves.some(wave => !wave.isAnimationComplete);
  }
 
  private initTarget(target: HTMLElement | null = null) {
    const doc: Document = PLATFORM.global.document;
 
    this.$ = target || doc.createElement('div');
    this.$.classList.add('paper-ripple');
 
    if (!this.$background) {
      this.$background = target &&
        target.querySelector('.paper-ripple__background') as HTMLElement || doc.createElement('div');
      this.$background.classList.add('paper-ripple__background');
      this.$.appendChild(this.$background);
    }
 
    if (!this.$waves) {
      this.$waves = target &&
        target.querySelector('.paper-ripple__waves') as HTMLElement || doc.createElement('div');
      this.$waves.classList.add('paper-ripple__waves');
      this.$.appendChild(this.$waves);
    }
 
    return this;
  }
 
  /**
   * Adds new wave to the list of visual ripples.
   * @returns Current instance for method chaining.
   */
  public addWave() {
    const wave = new PaperWave(this);
    this.$waves.appendChild(wave.$);
    this.$background.style.backgroundColor = wave.color!;
    this.waves.push(wave);
    return wave;
  }
 
  /**
   * Produces a ripple-down effect.
   *
   * @param  ev Object containing coordinates of the point of contact.
   * @returns Current instance for method chaining.
   */
  public downAction(ev: MouseEvent) {
    const wave = this.addWave();
    wave.downAction(ev);
    this.animate();
    return this;
  }
 
  /**
   * Produces a ripple-up effect.
   * @returns {PaperRipple} Current instance for method chaining.
   */
  public upAction() {
    this.waves.forEach(wave => { wave.upAction(); });
    this.animate();
    return this;
  }
 
  /**
   * Removes given wave from the list of visual ripples.
   * @param wave - The wave to remove.
   * @returns Current instance for method chaining.
   */
  public removeWave(wave: PaperWave) {
    const waveIndex = this.waves.indexOf(wave);
 
    if (waveIndex < 0) {
      return this;
    }
 
    this.waves.splice(waveIndex, 1);
    wave.remove();
    return this;
  }
 
  /**
   * Animates all the waves in the list of visual ripples.
   * @returns Current instance for method chaining.
   */
  public animate() {
    // tslint:disable:prefer-const
    for (let i = 0, l = this.waves.length; i < l; i++) {
      const wave = this.waves[i];
 
      if (wave) {
        wave.draw();
 
        this.$background.style.opacity = wave.outerOpacity.toString();
 
        if (wave.isWaveFullyOpaque && !wave.isMaxRadiusReached) {
          this.removeWave(wave);
        }
      }
    }
    // tslint:enable:prefer-const
 
    if (!this.shouldKeepAnimating && this.waves.length === 0) {
      this.$background.style.backgroundColor = null!;
    } else {
      PLATFORM.requestAnimationFrame(this.animate.bind(this));
    }
 
    return this;
  }
}