Gerard Sans
@gerardsans
Gerard Sans
@gerardsans
SANS
GERARD
Spoken at 111 events in 27 countries
900
1.6K
DURATION
opacity: 0;
opacity: 1;
PROPERTY
transition: opacity 1s ease-in 1s;
DURATION
TIMING
DELAY
<div class="container">
  <div class="circle base elastic"></div>
</div>
.circle {
  border-radius: 50%;
  position: absolute; opacity:0;
  will-change: opacity, transform;
}
.base {
  transition: opacity 1s ease, 
   transform 1s ease;
}
.elastic { 
  transition-timing-function: 
   cubic-bezier(.8,-0.6,0.2,1.5);
}
.container:hover .circle {
  transform: translateX(80px);
  opacity:1;
}all background* border* bottom box-shadow clip clip-path color filter font* height left margin* mask* offset* opacity outline* padding* perspective* right text-decoration text-shadow top transform vertical-align visibility width z-index
DURATION
0%
100%
KEYFRAMES
NAME
animation: fade 1s 1s infinite linear;
DURATION
DELAY
ITERATIONS
TIMING
<div class="circle base elastic"></div>
.base {
  animation: fade 2s infinite;
}
@keyframes fade {
  0% { 
    transform: translateX(0px);
    opacity: 0;
  }
  40%, 60% { 
    transform: translateX(80px);
    opacity: 1;
  }
  100% { 
    transform: translateX(0px);
    opacity: 0;
  }
}60 FPS
Time
16.66ms
16.66ms
16.66ms
1000/60ms
paint
frame
paint
frame
paint
frame
😃
paint
frame
paint
frame
60 FPS
Time
16.66ms
16.66ms
16.66ms
😱
Move
transform: translateX(10px);
Resize
transform: scale(0.5);
Rotate
transform: rotate(1turn);
Fade
opacity: 0;
<div class="circle base elastic"></div>
.base {
  animation: move 2s infinite;
}
.elastic { 
  animation-timing-function: 
    cubic-bezier(.8,-0.6,0.2,1.5);
}
@keyframes move {
  0% { 
    transform: translateX(-250px);
  }
  40%, 60% { 
    transform: translateX(50px);
  }
  100% { 
    transform: translateX(250px);
  }
}<div class="square base linear"></div>
.base {
  animation: spin 2s infinite;
}
.linear {
  animation-timing-function: linear;
}
@keyframes spin {
  to { 
    transform: rotate(1turn);
  }
}<div class="circle base"></div>
.base {
  animation: size 2s infinite;
}
@keyframes size {
  0%, 40%, 100% {
    transform: scale(1);
  }
  25%, 60% {
    transform: scale(1.1);
  }
}fadeIn
fadeOut
TRANSITIONS
STATE
STATE
fadeIn => fadeOut
fadeOut => fadeIn
opacity: 1
opacity: 0
animate('400ms linear')
animate('400ms linear')
fadeOut <=> fadeIn
animate('400ms linear')
import {fade} from './animations';
@Component({
  selector: 'my-app',
  template: `<button [@fade]='fade' (click)="toggleFade()">Fade</button>`,
  animations: [ fade ]
})
export class App {
  fade = 'fadeIn';
  toggleFade(){
    this.fade = this.fade === 'fadeIn' ? 'fadeOut' : 'fadeIn';
  }
}import { 
  trigger, transition, state, style, animate 
} from '@angular/animations';
export const fade = trigger('fade', [
  state('fadeIn', style({ opacity: 1 })),
  state('fadeOut', style({ opacity: 0 })),
  transition('fadeIn <=> fadeOut', animate('400ms linear'))
]);void
*
void => *
STATE
STATE
:enter
scale(1)
opacity 1
scale(0.5)
opacity 0
*
void
* => void
STATE
STATE
:leave
scale(0.5)
opacity 0
scale(1)
opacity 1
sequence
group
time
class="container"
class="item"
class="item"
query(selector)
query('.item')
class="container"
class="item"
class="item"
query('.item:enter')
class="container"
class="item"
class="item"
class="item"
added
animateChild
time
time
Builder
`(${x}, ${y})`
(100, 50)
Player
Template
1
2
(100, 50)
3
build()
play()
import {AnimationBuilder} from "@angular/animations";
export class MyComponent {
  constructor(
    private elem: ElementRef, 
    private builder: AnimationBuilder
  ) {
    const player = this.playerMoveTo({ x: 100, y: 100 });
    player.play();
  } 
  playerMoveTo(to) {
    const moveAnimation = this.builder.build([
      animate('1s', style({ transform: `translate(${x}px, ${y}px)` }))
    ]);
    return moveAnimation.create(this.elem.nativeElement
      , {params: { x: to.x, y: to.y }});
  }
}
constructor(
  private elem: ElementRef
) {
  // find out center
  this.mx = elem.nativeElement.offsetWidth/2;
  this.my = elem.nativeElement.offsetHeight/2;
} 
ngOnInit() {
  const mouseMove$ = fromEvent(document, 'mousemove')
    .pipe(map(e => ({ x: e.pageX, y: e.pageY })));
  mouseMove$.subscribe({
    next: e => {
      this.lastPosition = { x: e.x-this.mx, y: e.y-this.my };
      const player = this.playerMoveTo(this.lastPosition);
      player.play();
    }
  })
}