js

remplir avec des formes aléatoires (mise à jour)

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

Une sorte de Tetris

Comment remplir une zone de formes basiques au hasard en optimisant le placement ?

En partant d’une grille de 10 sur 10, si la case est vide j’écris un 0 sinon un 1.

0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000

On peut utiliser un tableau de tableaux, mais je vais utiliser une astuce de 0 et 1.

Première expérimentation simple avec des pièces d’une ligne

Je veux remplir celle‐ci avec des barres horizontales de longueurs variables entre 3 et 5 cases en interdisant les superpositions, voici mes barres :

111
1111
11111

Des 0 et des 1

Un peu de culture 🙂
Les ordinateurs utilisent des 0 et des 1 pour faire des opérations, on parle de langage binaire. Si vous avez déjà manipulé un cadenas à chiffres de 0 à 9, en base 10 on compte comme ceci :

000 -> 0
001 -> 1
002 -> 2
…
999 -> 999

C’est simple pour nous, il s’agit de notre système décimal. Avec un cadenas à trois molettes, on a 1000 combinaisons (10 puissance 3).
En binaire, on compte ainsi :

000 -> 0
001 -> 1
010 -> 2
011 -> 3
100 -> 4
101 -> 5
110 -> 6
111 -> 7

Ce qui donne 8 combinaisons (2 puissance 3).

Les zéros « devant » sont facultatifs.

Avec un peu de JavaScript, on peut écrire un binaire en décimal et un décimal en binaire, une gymnastique entre des chiffres et des lettres chaînes de caractères 0 et 1.

(7).toString(2) // 111
parseInt('111',2) // 7

OR | et XOR ^

Supposons que les cases d’une ligne soient occupées ainsi.

0111000111

Je veux placer une pièce de trois cases pour obtenir

0111111111
0111000111 // ma ligne
// en prenant une pièce de trois cases 
00000000111 // ma pièce superposée, x = 0
00000001110 // ma pièce superposée, x = 1 
00000011100 // ma pièce superposée, x = 2 
00000111000 // ma pièce OK
00001110000 // ma pièce superposée
00011100000 // ma pièce superposée
00111000000 // ma pièce superposée
01110000000 // ma pièce superposée
11100000000 // ma pièce superposée

Décalage

Pour décaler d’un zéro vers la gauche, on utilise cet opérateur :

(1 << 3).toString(2) // 1 -> 1000

En boucle

Si je fais un OR et un XOR entre la ligne et la pièce et que le résultat est identique, la place est libre !

var ligneInt = parseInt("0111000111",2);
var piece = "111";
var pieceInt = parseInt(piece,2);
 
for(var  i = 0;i<(10-piece.length);i++){
  var pieceInti = pieceInt << i;
 
  console.log(i,"la pièce",(pieceInti).toString(2));
  console.log(i,"le |",(pieceInti | ligneInt).toString(2));
  console.log(i,"le ^",(pieceInti ^ ligneInt).toString(2));
 
  if((pieceInti | ligneInt) === (pieceInti ^ ligneInt)){
    console.log('place disponible en',i);
  }
}

Ouvrir le codepen et appuyer sur le bouton console en bas à gauche :

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

Avec du HTML et CSS

J’ai commenté le code sur codepen.
J’ai aussi inversé le x par rapport à l’exemple précédent.

See the Pen or & xor + html / css / grid by Benoît Wimart (@benoitwimart) on CodePen.0

Avec des pièces plus complexes que des lignes

Ça se complique dans la logique et l’algorithme, mais que le code change peu.
Les pièces sont désormais des tableaux de « lignes ».

['10',
 '10',
 '11'] // dessine un L 

On fera une boucle sur chaque ligne de la pièce pour tester si la place est disponible.
Si les places sont disponibles, on mettra à jour le tableau « ligne » et on dessinera nos pièces avec un peu de CSS (nb : la méthode de dessin ne permet pas de faire des motifs avec des trous comme « 1001 »).

See the Pen or & xor + html / css / grid (e02) by Benoît Wimart (@benoitwimart) on CodePen.0

La méthode de dessin via CSS étant rapide, mais avec ses limites, on pourrait poursuivre l’exercice avec des formes SVG plus complexes.

Variables partout

Variables & CSS

Depuis quelques années, CSS propose d’utiliser des variables.

:root {
  --font-base: 64px;
}
body{
  font-size:var(--font-base);
}

Cette écriture lourde et étrange, n’apporte rien face à des outils/langages comme le SCSS et personnellement je ne l’utilise pas. Néanmoins, on peut facilement créer une interaction avec celle‐ci et du JavaScript, ce qui rend la chose plus attrayante !
Avant de faire plus de code, un petit prérequis avec « calc », pour supprimer les unités, c’est juste une histoire de goût pour le JS final.

:root {
  --font-base: 64; // sans px
}
body{
  font-size:calc(var(--font-base) * 1px); // 64px
}

Variables CSS & JS

La communication entre les deux est plutôt simple :

var dS = document.documentElement.style; //le style de la balise <html>
dS.setProperty('--font-base', 128); // je modifie la valeur de --font-base à 128

Avec du JavaScript et des input d’HTML5 (type = ‘range’) :

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

Et avec des fontes variables ? ✌️

Les fontes variables sont de plus en plus nombreuses, il s’agit d’avoir un seul fichier et de jouer avec des opérations mathématiques pour produire différentes graisses, italiques ou formes.
Pour la suite, je suis passé chez Velvetyne télécharger la Tiny. Un fichier m’intéresse plus que les autres, c’est la fonte variable disponible sur GitHub.
Je place le fichier dans le même dossier que ma page pour la démonstration.

    @font-face {
      font-family:'tiny';
      font-style:normal;
      font-weight:400;
      src:url("TINY5x3GX.ttf") format("truetype"); // ou src:url("/Range-mieux/tonDossier/fontes/TINY5x3GX.ttf") format("truetype");
    }

Pour faire varier le « point » en CSS:

font-variation-settings: "wght" 200;

Selon les fontes, les paramètres sont plus ou moins nombreux et s’ajoutent à la suite.
Sur la fonte Graduate on peut ajuster 12 variables !

font-variation-settings: "XOPQ" 76.92, "XTRA" 400, "OPSZ" 16, "GRAD" 5.37, "YTRA" 783.75, "CNTR" 35.64, "YOPQ" 318.68, "SERF" 5.42, "YTAS" 17.82, "YTLC" 691.92, "YTDE" 20.64, "SELE" -12.12;'

Version finale

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Tiny Test</title>
  <style>
    :root {
      --font-base: 64;
      --r:255;
      --v:255;
      --b:0;
      --wght:60;
    }
 
    @font-face {
      font-family:'tiny';
      font-style:normal;
      font-weight:400;
      src:url("TINY5x3GX.ttf") format("truetype");
    }
 
    .interface{
      padding:24px;
      display:flex;
      background: #fff;
      font-family: sans-serif;
    }
 
    body{
      background:rgb(calc(255 - var(--r)),calc(255 - var(--v)),calc(255 - var(--b)));
      margin:0;
    }
 
    article{
      padding:24px;
      font-size:  calc(var(--font-base) * 1px);
      color:rgb(var(--r),var(--v),var(--b));
      font-family: "tiny";
      font-variation-settings: "wght" var(--wght);
    }
 
    p{
      margin:0;
    }
 
    input,label{
      font-size: 16px;
      display:inline;
      margin-right:12px;
    }
 
    input{
      margin-right:32px;
    }
 
  </style>
</head>
<body>
  <div class="interface">
    <label for="fader-size">Font-Size</label>
  <input type="range" min="16" max="256" value="64" id="fader-size" step="1">
 
  <label for="fader-r">R</label>
  <input type="range" min="0" max="255" value="255" id="fader-r" step="1">
 
  <label for="fader-v">V</label>
  <input type="range" min="0" max="255" value="255" id="fader-v" step="1">
 
  <label for="fader-v">B</label>
  <input type="range" min="0" max="255" value="0" id="fader-b" step="1">
 
    <label for="fader-wght">WGHT</label>
  <input type="range" min="20" max="300" value="60" id="fader-wght" step="1">
  </div>
  <article>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus, sit dolore nostrum aliquam a consequuntur ducimus alias dignissimos maiores corrupti quidem non dolores est assumenda quis ea quae ipsum placeat!</p>
 
  </article>
<script>
  var faderSize = document.querySelector('#fader-size');
  var faderR = document.querySelector('#fader-r');
  var faderV = document.querySelector('#fader-v');
  var faderB = document.querySelector('#fader-b');
  var faderWGHT = document.querySelector('#fader-wght');
 
  var dS = document.documentElement.style;
 
  faderSize.addEventListener('input',function(e){
    console.log(faderSize.value);
    dS.setProperty('--font-base', this.value);
  });
 
  faderR.addEventListener('input',function(e){
    dS.setProperty('--r', this.value);
  });
 
  faderV.addEventListener('input',function(e){
    dS.setProperty('--v', this.value);
  });
 
  faderB.addEventListener('input',function(e){
    dS.setProperty('--b', this.value);
  });
 
  faderWGHT.addEventListener('input',function(e){
    dS.setProperty('--wght', this.value);
  });
 
 
</script>
 
</body>
</html>

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