gsap

typotap

Introduction

En 26 jours de confinement, je viens de finir un alphabet animé de 26 lettres, visible sur https://typotap.jenseign.com, le projet se situe entre patatap, le cédérom Alphabet et d’autres inspirations.
Décortication.

Trois ingrédients

Pour faire votre propre typotap, il vous faut :

  • Environ 26 SVG ;
  • 26 MP3 ;
  • 200 gr de JavaScript.

Les SVG

Pour les SVG, il est nécessaire de comprendre, le dessin en SVG via un logiciel de votre choix (Illustrator, Figma, InkScape, Affinity Designer), la balise <g> et les options de la balise <svg> pour certains cas (preserveAspectRatio).

Les MP3

Par défaut, les animations vont être créées avec une base temporelle en seconde, pour certains sons rythmiques, j’ai réglé les BPM à 120 ou 60 (astuce offerte).

Le JavaScript

On décompose les connaissances :

  • Détecter les événements du clavier — les touches pressées (niveau débutant) ;
  • Charger un SVG en JavaScript (pas débutant, mais facile à écrire avec un exemple) ;
  • Animer le SVG, on utilisera la librairie GSAP (niveau moyen) ;
  • Lire le MP3, on utilisera ToneJS (niveau débutant avec un exemple) ;
  • Retirer le SVG (une ligne :))

Récipient

L’ensemble des ingrédients sera mélangé dans votre navigateur, de préférence à jour et on monte le tout dans une page HTML, avec un peu de CSS.

En pratique

On commence par le clavier et le JavaScript.

See the Pen clavier by Benoît Wimart (@benoitwimart) on CodePen.dark

Puis pour le SVG, on va chercher le fichier (fetch) et si on le trouve (internet ok, le site ok, le fichier existe…), dès qu’il est totalement chargé, on l’insère dans l’élément « #play ».

Fetch permet de charger un fichier venant d’un autre site, mais pour des raisons de sécurité, les Blocage de requêtes multiorigines (Cross-Origin Request) sont courants (ici jenseign.com est bloqué sur codepen, mais pas « s3-us-west‑2.amazonaws.com/s.cdpn.io ».

See the Pen fetch svg by Benoît Wimart (@benoitwimart) on CodePen.dark

Les deux ensemble (en supposant que le svg est dans le même dossier) :

const play = document.querySlector('#play');
 
function load(letter){
  fetch(letter+'.svg') // concatenation, je charge a.svg ou b.svg … z.svg 
    .then(function(response){
      if(response.ok){
        response.text().then(function(svg){
          play.insertAdjacentHTML("beforeend", svg);
        });
      }
  });
}
 
document.addEventListener('keyup',function(e){
  load(e.key); // une fonction qui charge 
});
Attention, pas mal fonctionnalités JavaScript nécessitent un serveur pour marcher. Trois solutions, mettre en ligne, installer un serveur local ou utiliser l’extension live server de VS Code. Si votre adresse dans le navigateur commence par « file:/// », vous devez résoudre d’abord ce problème.

J’anime

Voici l’avant dernière étape, l’animation, il faut connaitre les fonctions, GSAP et l’overlap (la superposition) en CSS avec Grid.
L’ensemble est ici sur Repl.it.

C’est ici qu’il faut jongler entre la créatvité, le SVG, GSAP et le logiciel de musique (Audacity pour voir le timing).
const play = document.querySelector('#play');
 
function load(letter){
  fetch(letter+'.svg') // concatenation, je charge a.svg ou b.svg … z.svg 
    .then(function(response){
      if(response.ok){
        response.text().then(function(svg){
          // insertion professionnelle
          play.insertAdjacentHTML("beforeend", svg);
          // on ajoute un id avec la lettre et la date en ms
          // depuis 1970 pour le rendre unique
          // ex : r1586771234272 
          // https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Date/now
          play.lastElementChild.id = letter + Date.now(); 
          // la fonction qui animera
          // je lui donne la lettre, l'élément et l'id
          anim(letter,play.lastElementChild,play.lastElementChild.id);
 
        });
      }
  });
}
 
document.addEventListener('keyup',function(e){
  load(e.key); // une fonction qui charge 
});
 
function anim(letter,el,id){
    // je récupère la letter, l'élément et l'id pour gsap et le switch 
    // voir la doc mdn sur le switch/case 
    switch(letter) {
      case 'r':
          // j'attache une timeline à l'élément
          el.tl_= gsap.timeline();
          // gsap to
          el.tl_.to('body',{backgroundColor:'#F90'});
          // '#'+id+' .r' sera '#r1586771234272 .r'
          el.tl_.to('#'+id+' .r',{duration:.2,rotation:-15,transformOrigin:'left bottom',repeat:1,yoyo:true,ease:'power1.out'},0);
          el.tl_.to('#'+id,{scale:1.1,transformOrigin:'.5 .5',opacity:0,duration:2,ease:'power1'},0);
          // on enchaine et efface l'élément quand l'animation est finie :) 
          el.tl_.call(function(){el.remove()});
      break;
 
      // avec les autres lettres
      /*
      case 's':
          el.tl_= gsap.timeline();
          el.tl_.to('body',{backgroundColor:'#000'});
          el.tl_.to('#'+id+' .s',{duration:.2,rotation:-15,transformOrigin:'left bottom',repeat:1,yoyo:true,ease:'power1.out'},0);
          el.tl_.to('#'+id,{scale:1.1,transformOrigin:'.5 .5',opacity:0,duration:2,ease:'power1'},0);
          el.tl_.call(function(){el.remove()});
      break;
      */
    }
}

Let’s the musique play

J’ajoute la librairie dans la page d’HTML.
Je connnais peu ToneJS, mais le sampler permet de charger nos sons et avoir une polyphonie.
Pour TypoTap, après chargement, j’ajoute l’écouteur d’événement du clavier.

Voici la demo sur repl.it

Le code JavaScript :

const play = document.querySelector('#play');
 
/* TONE JS */
const alphabet = new Tone.Sampler({
  "F3":"r.mp3", // , <- virgule
  "G3":"s.mp3" // on liste les sons ici
  }, // <- virgule
  function(){ 
    // quand les mp3 sont chargés, on ajoute l'écouteur de clavier
    // ce bout de code était plus bas
    document.addEventListener('keyup',function(e){
      load(e.key); // une fonction qui charge 
    });
  }
).toMaster(); // <- un truc de toneJS pour envoyer le son
 
function load(letter){
  fetch(letter+'.svg') // concatenation, je charge a.svg ou b.svg … z.svg 
    .then(function(response){
      if(response.ok){
        response.text().then(function(svg){
          // insertion professionnelle
          play.insertAdjacentHTML("beforeend", svg);
          // on ajoute un id avec la lettre et la date en ms
          // depuis 1970 pour le rendre unique
          // ex : r1586771234272 
          // https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Date/now
          play.lastElementChild.id = letter + Date.now(); 
          // la fonction qui animera
          // je lui donne la lettre, l'élément et l'id
          anim(letter,play.lastElementChild,play.lastElementChild.id);
 
        });
      }
  });
}
 
 
 
function anim(letter,el,id){
    // je récupère la letter, l'élément et l'id pour gsap et le switch 
    // voir la doc mdn sur le switch/case 
    switch(letter) {
      case 'r':
          /* Tone JS en action */
          alphabet.triggerAttack("F3");
 
          // j'attache une timeline à l'élément
          el.tl_= gsap.timeline();
          // gsap to
          el.tl_.to('body',{backgroundColor:'#F90'});
          // '#'+id+' .r' sera '#r1586771234272 .r'
          el.tl_.to('#'+id+' .r',{duration:.2,rotation:-15,transformOrigin:'left bottom',repeat:1,yoyo:true,ease:'power1.out'},0);
          el.tl_.to('#'+id,{scale:1.1,transformOrigin:'.5 .5',opacity:0,duration:2,ease:'power1'},0);
          // on enchaine et efface l'élément quand l'animation est finie :) 
          el.tl_.call(function(){el.remove()});
      break;
 
      // avec les autres lettres
      /*
      case 's':
          el.tl_= gsap.timeline();
          el.tl_.to('body',{backgroundColor:'#000'});
          el.tl_.to('#'+id+' .s',{duration:.2,rotation:-15,transformOrigin:'left bottom',repeat:1,yoyo:true,ease:'power1.out'},0);
          el.tl_.to('#'+id,{scale:1.1,transformOrigin:'.5 .5',opacity:0,duration:2,ease:'power1'},0);
          el.tl_.call(function(){el.remove()});
      break;
      */
    }
}

Remplir avec du texte

Petite exercice pour remplir « sans dépasser » de lettres au hasard un bloc html.

L’idée est de remplir chaque ligne de lettre en chasse fixe (monospace) et d’ajouter un retour à la ligne pour avoir un bloc plein.
Attention : ce code est pour le fun et n’est pas parfait (certains caractères ne passent pas et la preview du codepen ne marche pas).

Le code est aussi commenté dans CodePen.

/* Ajouter GSAP*/
 
var fill  = document.querySelector('.fill'); // je cible la première div.fill
var wl; // largeur d'une lettre 
var hl; // hauteur
var w; // largeur de la fenêtre
var h; // hauteur
 
/* fonction de lettre random via gsap */
// https://greensock.com/docs/v3/GSAP/UtilityMethods/random
var randomLetter = gsap.utils.random('-abcdeABCDEF+*,#_!:\.?/='.split(''),true);
 
/*
étape 1 : prendre les dimensions
*/
function ini(){
  w = window.innerWidth; // largeur de la fenetre 
  h = window.innerHeight; // hauteur
  fill.style.display = 'inline-block'; // par défaut display block prend toute la largeur; 
  // inline-block prend juste la largeur du texte 
  fill.innerHTML = '0123456789'; // j'ajoute 10 glyphs 
  wl = fill.clientWidth/10; // je divise par 10 pour avoir la taille d'un glyph
  fill.innerHTML = '0<br>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9'; // idem en hauteur
  hl = fill.clientHeight/10; // ok
  fill.style.display = 'block'; // je remets 
  console.log(w,h,wl,hl)
  fill.innerHTML = ''; // je vide
}
 
ini(); // je lance la fonction ini
 
/*
étape 2 : on remplit
un peu de math
Si j'ai 13 px et des lettres de 4 px, je peux en mettre 3.
13/4 = 3,25, on supprime le reste après la virgule -> 3.
Math.floor(3.25) = 3 
*/
 
function fillit(){
  let maxX = Math.floor(w/wl); // nombre max en x
  let maxY = Math.floor(h/hl); // nombre max en y
  let innerhtml = ''; // variable temporaire qui contient le texte
 
  /* boucle dans une boucle */
  for(let y=0;y<maxY;y++){ 
    for(let x=0;x<maxX;x++){
      innerhtml+=randomLetter(); // truc de gsap
    }
    innerhtml+='<br>'; // on passe à ligne
  }
  fill.innerHTML = innerhtml; // on remplit l'élement html avec innerhtml
}
 
fillit(); // je lance …
 
/* bonus */
window.addEventListener('resize',function(){
  ini();
  fillit();
})

See the Pen remplir avec du texte by Benoît Wimart (@benoitwimart) on CodePen.dark

Carte, scroll, data, css et JavaScript

dans la ou les catégorie(s) : code et avec pour étiquette(s) : ,

Aujourd’hui, une petite démo avec un plugin GSAP combiné avec plusieurs techniques pour faire une navigation en 2D.

Prérequis :

  • Comprendre les unités vw et vh ;
  • Comprendre le position absolute ;
  • Comprendre les transformations CSS avec GSAP ;
  • Comprendre l’overflow en CSS.

Créer le HTML, le CSS et penser au JavaScript

Mon body contient quelques div avec la class item, une class spécifique item+chiffre et des data-* où * sera le nom de ses datas.

<div class="item item1" data-link="item3" data-xy="100vw,100vh">01</div>
<div class="item item2" data-link="item1" data-xy="200vw,200vh">02</div>
<div class="item item3" data-link="item4" data-xy="200vw,50vh">03</div>
<div class="item item4" data-link="item2" data-xy="0,150vh">04</div>

Avec un peu de CSS, mes éléments sont superposés (position : absolute) et occupent toutes la largeur et la hauteur (vw et vh).

document.querySelector('.item1').dataset.link; 
// contient la chaîne de <div … data-link="truc" >…</div>, donc "truc"

Placer avec du code

TweenMax.set permet de changer les valeurs des propriétés CSS d’un élément sans animation. Avec TweenMax, on peut mettre en valeur une fonction avec deux paramètres (i,e), i compte de 0 à n et e contient l’élément.
On renvoit une reponse avec un return et ce qu’on veut, ici je récupère le data-xy, je le transforme en tableau en coupant à la virgule et je prends la première entrée pour les x, la seconde pour les y.

TweenMax.set('.item',{
  x:function(i,e){
    return e.dataset.xy.split(',')[0];
  },
  y:function(i,e){
    return e.dataset.xy.split(',')[1];
  }
});
ScrollTo n’accepte pas de coordonnées négatives et n’aime pas les vw/vh (viewport units).

le Scroll

J’ajoute un écouteur de clic sur le document et si l’élément cliqué (e.target) a un data-link, je lance ma fonction.
Je n’ai pas eu 100% satisfaction avec les ancres (#…), j’ai donc repris les transformations CSS appliquées via le TweenMax ; elles sont stockées dans un objet attaché à l’élément element._gsTransform.x pour le x…
Avec un quelques commentaires à lire dans le JS.

See the Pen cartographique by Benoît Wimart (@benoitwimart) on CodePen.0

Taquin, astuce et algorithme

dans la ou les catégorie(s) : code et avec pour étiquette(s) : ,

Aujourd’hui, je vous propose un petit jeu avec du code JavaScript et une approche visuelle.
Il s’agit du jeu du pousse-pousse ou taquin.

Première étape, mélanger les éléments, le HTML et le CSS.

Selon votre approche, le JS sera plus ou moins complexe… j’ai commencé un prototype avec un « grid-layout » pour finalement basculer sur un placement par x et y (en transformation CSS via GSAP).

Voici le HTML :

<div class="grid">
  <div class="item">1</div>
  <div class="item">2</div>
  <div class="item">3</div>
  <div class="item">4</div>
  <div class="item">5</div>
  <div class="item">6</div>
  <div class="item">7</div>
  <div class="item">8</div>
  <div class="item">9</div>
  <div class="item">10</div>
  <div class="item">11</div>
  <div class="item">12</div>
  <div class="item">13</div>
  <div class="item">14</div>
  <div class="item">15</div>
  <div class="special"></div>
</div>
<div class="win">
  YOU WIN
</div>

et le CSS simplifié :

.grid {
  position: relative;
  width: 400px;
  height: 400px;
}
.win {
  opacity: 0;
  z-index: 0;
}
.item {
  z-index: 999;
  position: absolute;
  width: 100px;
  height: 100px;
}

Pour le placement en tableau, j’utilise GSAP et TweenMax :

TweenMax.set('.grid>div',{
  x:function(i){return i%4 * 100}, //0,100,200,300, 0,100,200 …
  y:function(i){return Math.floor(i/4) * 100} // 0,0,0,0, 100,100,100,100, 200,200…
})
Avec TweenMax, le « i » dans « function(i) » va prendre les valeurs de 0 pour le 1er item, 1 pour le second, 2, jusqu’à 15.

Deuxième étape, mélanger les éléments !

Si vous parlez un peu anglais, ça aide, taper « shuffle element javascript » dans un moteur de recherche et trouver un bout de code sur StackOverflow.
J’ai trouvé plus ou moins ceci :

var grid = document.querySelector(".grid");
for (var i = grid.children.length; i >= 0; i--) {
    grid.appendChild(grid.children[Math.random() * i | 0]);
}

Le code est un peu bizarre, mais ce n’est pas important. La boucle for est en décompte, cela va un peu plus vite qu’en passant par i++.
« Math.random() * i | 0 » donne un entier au hasard.

L’astuce

1999

Au siècle dernier, j’avais lu un article pour faire un taquin avec peu de lignes, j’ai retrouvé celui-ci : http://yazo.net/racine/v2/director/pouss.html ! C’est un autre langage (Director Lingo), mais le principe marche toujours.
Quand on clique sur une case, on teste si elle « touche » la case vide et si oui, on inverse les positions, celle qui est touchée se place sur la vide et la vide sur celle qui est touchée.

2019

Pour faire un test simple, je vais mesurer la distance (juste utiliser Pythagore) entre la case cible et la case vide, si c’est ≤ 100 (dans mon cas) alors elles sont voisines (en diagonal on aura ~ 141px) sinon rien.

Le test de la victoire

Après chaque déplacement, je compte le nombre d’items bien placés.

  var score = 0
  for(var i = 0;i<items.length;i++){
    if(items[i]._gsTransform.x === i%4 * 100 && items[i]._gsTransform.y === Math.floor(i/4) * 100){ 
      score++; 
    }
  }

Version simple

See the Pen taquin parfois impossible by Benoît Wimart (@benoitwimart) on CodePen.0

Joie du code

Après avoir codé la version basique, je n’arrivais pas à finir le puzzle.

Un mélange hasardeux va donner des puzzles impossibles, cette vidéo vous explique pourquoi.

J’ai trouvé une explication pour valider les mélanges sur les « Puzzle of Fifteen ». On compte le nombre de permutations et on regarde le numéro du rang de la case vide en comptant du bas vers le haut. Si le nombre de permutation est pair, le rang doit être impair, s’il est impair, le rang doit être pair.

See the Pen taquin avec test by Benoît Wimart (@benoitwimart) on CodePen.0

GSAP et les function

dans la ou les catégorie(s) : code et avec pour étiquette(s) : ,

GSAP est parfait pour modifier, placer ou déplacer des éléments d’une page web.

Petit exemple, j’ai une variable de type tableau avec des couleurs :

var colors = ['red', 'green', 'blue', 'orange', '#f60','yellow'];

En javascript pour avoir un élément du tableau au hasard, on fait :

// une couleur au hasard dans le tableau colors
var colorAuPif = colors[Math.floor(Math.random() * colors.length)];

Avec GSAP pour assigner une couleur à tous les éléments de la class item

TweenLite.set('.item',{backgroundColor : 'red'});
Si vous ne passez pas par une function (comme ci-dessous), vous aurez une seule couleur au hasard pour tous les items !

Mixage des deux codes :

TweenLite.set('.item',{
    backgroundColor : function() {
			return colors[Math.floor(Math.random() * colors.length)]; // le return renvoie une couleur
		}
})

Démonstration et plus sur CodePen

See the Pen gsap + random by Benoît Wimart (@benoitwimart) on CodePen.0

Souvenir scolaire, trajectoire, vitesse, angle et gravité

Attention, je monte le niveau d’un cran ou deux et je parle de maths et physique !

Il existe des équations écrites par rapport au temps (je ne parle pas de météo), elles ont des avantages en maths, code et en graphisme.
Un peu de processing sauce P5JS, parce que les graphistes aiment bien P5 :

function setup() {
  createCanvas(400, 400);
}
 
function draw() {
  point(frameCount,20); // un point placé à x = la frame actuelle et y = 20
}

Ici frameCount représente le temps (on pourrait prendre le temps en javascript, mais je simplifie).

Variation avec des cosinus et des sinus :

function draw() {
  point(frameCount,50+20*cos(radians(10*frameCount)));
  point(frameCount,50+20*sin(radians(10*frameCount)));
}

See the Pen p5js by Benoît Wimart (@benoitwimart) on CodePen.0

Une trajectoire circulaire :

function draw() {
  background(255,255,255,10);
  point(200+100*cos(radians(frameCount)),200+100*sin(radians(frameCount)));
}

Ce dernier peut s’écrire x = R * cos(t) et y = R * sin(t) avec R le rayon et t le temps et en plaçant le centre à 0,0.

Trajectoire d’un lancer

Dans des conditions simplifiés (pas de vents, pas de frottements…) on lance un objet (une balle, un oiseau pas content, une charentaise…) avec un angle déterminé « alpha » et une vitesse de départ « v ».
Voici l’équation magique :
x = v * cos(alpha) * t
y = ‑1/2*g*t2 + v * sin(alpha) * t
avec g = gravité (9.81 à Amiens)

// version p5js
var v = 50; // la vitesse
var g = 9.81; // la gravité
var angle = 60; // l'angle en degré // alpha est réservé
 
function setup() {
  createCanvas(400, 400);
  angleMode(DEGREES);
  background(0);
  strokeWeight(2);
  stroke(255, 100, 0);
}
 
function draw() {
  var  t = frameCount/10;
  x = v * cos(angle) * t
  y = 1/2*g*t*t - v * sin(angle) * t;
  point(x,y+200);
}
Petite arnaque sur y, en maths le zéro des répères orthonormés est en bas, « plus y est grand, plus le point est placé vers le haut », en code on inverse et le zéro est en haut, il faut donc faire y = ‑y.

See the Pen Gravité + p5js by Benoît Wimart (@benoitwimart) on CodePen.0

Avec GSAP :

  • J’utilise le plugin modifiers, il permet de recalculer une valeur selon une de ses propriétés ;
  • J’ajoute des attributs data (c’est du GSAP + JS) data‑v et data-alpha ;
  • « target » va lire les propriétés de l’item ;
  • Je triche pour avoir un t, j’utilise le z du « target »
var colors = ['red', 'green', 'blue', 'orange', '#f60', 'yellow'];
 
 
TweenLite.set(".item", {
  backgroundColor: function() {
    return colors[Math.floor(Math.random() * colors.length)]; // le return renvoie une couleur
  },
  attr: {
    'data-v': function() {
      return Math.random() * 100
    },
    'data-alpha': function() {
      return 2 * Math.PI * Math.random()
    }
  }
});
 
 
TweenMax.to(".item", 10, {
  x: 0,
  y: 0,
  z: 100,
  modifiers: {
    x: function(x, target) {
      var t = target._gsTransform.z;
      var g = 9.81;
      var v = target.dataset.v;
      var alpha = target.dataset.alpha;
      var x = v * Math.cos(alpha) * t;
      return x;
    },
    y: function(y, target) {
      var t = target._gsTransform.z;
      var g = 9.81;
      var v = target.dataset.v;
      var alpha = target.dataset.alpha;
      var y = -0.5 * g * t * t + v * Math.sin(alpha) * t;
      return -y;
    }
  },
  ease: Linear.easeNone
})

See the Pen gsap + gravité by Benoît Wimart (@benoitwimart) on CodePen.0

GSAP, le timing et la musique

dans la ou les catégorie(s) : code et avec pour étiquette(s) :

À partir d’une petite boucle de batterie, voici comment travailler sur une pulsation avec GSAP.
Les timelines de GSAP utilisent comme unité de temps la seconde, la musique se base sur le BPM, battements par minute.

  • À 60 bpm, un temps par seconde, 0s 1s 2s 3s donne la pulsation ;
  • À 120 bpm (le tempo de la Marseillaise) 2 temps par seconde, 0s 0.5s 1s 1.5s donne la pulsation ;
  • À 88 bpm, c’est plus complexe, 0s 0.6818s 1.3636s 2.04545s (60/88*t)

GSAP a une fonction pour accélérer ou décélérer les timelines !

tl.timeScale(2); // double la vitesse
tl.timeScale(0.5); // divise par 2 la vitesse

L’astuce est de compter le nombre de temps dans l’extrait musical et de faire un timeScale(nombre de temps/durée de l’extrait).

Voici ma boucle de 8 temps.
http://www.orangefreesounds.com/funk-drum-loop/, elle dure 5,333333 secondes.

En écriture musicale de boite à rythmes sur https://drumbit.app/, j’obtiens ceci pour les 4 derniers temps (une petite variation d’ouverture de hit-hat):


beats.json à importer

La base SVG

Je vais utiliser 3 cercles, un rouge pour la grosse caisse (BD), un orange pour la caisse claire (SD) et un dernier jaune pour l’ouverture de la charleston.

  <svg width="800px" height="800px">
    <circle id="ho" cx="400" cy="400" r="50" fill="yellow"/>
    <circle id="sd" cx="500" cy="400" r="50" fill="#f60"/>
    <circle id="bd" cx="300" cy="400" r="50" fill="red"/>
  </svg>

Pour le son, je copie/colle pour créer une répétition.
Version 1 fois


Version 4 fois

GSAP est chargé via <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/latest/TweenMax.min.js"></script>

Le reste est en commentaire sur le code.

var audio = document.querySelector("audio");
var duration = 5.333333; // durée des 8 temps en seconde;
var beat = 8;
 
var tl = new TimelineMax({repeat:4}); 
tl.pause(); // antidémarrage 
tl.set("circle", { transformOrigin: "center center", opacity: 0 }); // réglage important pour le scale
 
/* la grosse caisse */
tl.to("#bd", 0.1, { scale: 4, opacity: 0.5 }, 0); // zoom rapide à 0 = 1er temps
tl.to("#bd", 0.9, { scale: 1, opacity: 0.0 }, 0.1); // dézoom à 0.1 temps pendant 0.9 temps
tl.to("#bd", 0.1, { scale: 4, opacity: 0.5 }, 2); // 2 = 3e temps
tl.to("#bd", 0.1, { scale: 1, opacity: 0.0 }, 2.1);
tl.to("#bd", 0.1, { scale: 4, opacity: 0.5 }, 2.5); // 2.5 = contretemps du 3e temps
tl.to("#bd", 0.1, { scale: 1, opacity: 0.0 }, 2.6);
 
/* (…) */
 
tl.to("#sd", 0.1, { scale: 4, opacity: 0.5 }, 1); // 1 = 2e temps
tl.to("#sd", 0.1, { scale: 1, opacity: 0.0 }, 1.1);
 
/* (…) */
 
tl.to("#ho", 0.4, { scale: 8, opacity: 0.5 }, 6.5); // 6.5 = contretemps du 3e temps de la 2e mesure à 4 temps
tl.to("#ho", 0.1, { scale: 0, opacity: 0 }, 6.9);
 
/* l'astuce */
tl.timeScale(beat / duration);
 
 
audio.addEventListener("play", function() {
  tl.play(0); // lecture quand on clique sur play
});
 
audio.addEventListener("pause", function() {
  tl.pause();
});

Version Codepen

See the Pen GSAP & BPM by Benoît Wimart (@benoitwimart) on CodePen.0