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
| <!DOCTYPE html>
<html lang=fr>
<head>
<meta charset=utf-8>
<title>Collision de divs</title>
<style>
div {
width: 16px;
height: 16px;
background: #eee;
border: solid 2px #999;
border-radius: 16px;
position: absolute;
/* une transition juste pour faire classe */
-webkit-transition: left 100ms ease-out, top 100ms ease-out;
-moz-transition: left 100ms ease-out, top 100ms ease-out;
-ms-transition: left 100ms ease-out, top 100ms ease-out;
-o-transition: left 100ms ease-out, top 100ms ease-out;
transition: left 100ms ease-out, top 100ms ease-out;
}
body {
position: relative;
margin: 0;
}
</style>
<script>
"use strict";
// constantes représentant les dimensions des divs
// mettre les mêmes valeurs que dans le CSS (ne pas oublier les
// marges, padding et bordures)
const DIV_WIDTH = 20;
const DIV_HEIGHT = 20;
// l'espace minimum qu'on veut avoir entre les divs
const SPACE = 2;
// facteur pour atténuer ou augmenter l'effet des forces
// 1 = normal, 0.5 = petit, 2 = grand
const FACTEUR = 1;
function repousse( divs ){
// 1. on applique les forces précédemment calculées
// une force = un déplacement en x + un déplacement en y
divs.forEach(div => {
// la 1re fois, force n'est pas définie, on prend donc (0,0)
// comme force par défaut
var force = div.force || { x: 0, y: 0 };
// j'ai choisi de stocker les coordonnées directement sur la
// div pour ne pas avoir à refaire parseInt sur style.left et
// style.top à chaque fois
var style = div.style;
var x = div.x || parseInt(style.left, 10);
var y = div.y || parseInt(style.top, 10);
x += force.x;
y += force.y;
style.left = x + "px";
style.top = y + "px";
div.x = x;
div.y = y;
// on prépare la liste des collisions sur chaque div
div.collisions = [];
});
// 2. on détermine les nouvelles collisions
// c'est un parcours triangulaire en O(n*n/2), il y a sans doute
// moyen de faire mieux
var encore = false;
divs.forEach((divA, i) => {
// on fait slice(i + 1) car les divs avant ont déjà été vues
divs.slice(i + 1).forEach(divB => {
var distX = divA.x - divB.x;
var absX = Math.abs(distX);
if (absX > DIV_WIDTH + SPACE) return;
var distY = divA.y - divB.y;
var absY = Math.abs(distY);
if (absY > DIV_HEIGHT + SPACE) return;
encore = true;
// calcul des puissances de répulsion : plus les divs sont
// proches, plus la force est puissante
var signeX = distX > 0 ? 1 : -1;
var puissX = FACTEUR * signeX * (DIV_WIDTH + SPACE - absX);
var signeY = distY > 0 ? 1 : -1;
var puissY = FACTEUR * signeY * (DIV_HEIGHT + SPACE - absY);
divA.collisions.push({ x: puissX, y: puissY });
divB.collisions.push({ x: -puissX, y: -puissY });
});
});
// 3a. si pas de collisions à ce point, on a fini
if (!encore) return;
// 3b. si collisions, on calcule les nouvelles forces
divs.forEach(div => {
var force = div.collisions.reduce(( result, collision ) => ({
x: result.x + collision.x,
y: result.y + collision.y
}), { x: 0, y: 0 });
div.force = { x: force.x, y: force.y };
});
// 4. on refait un appel récursif
setTimeout(() => repousse(divs), 250);
}
document.addEventListener("DOMContentLoaded", e => {
// calcule le centre de la page
var O = {
x: window.innerWidth / 2,
y: window.innerHeight / 2
};
// on récupère la liste des divs sous forme de tableau
var divs = Array.map(document.querySelectorAll("div"), $ => $);
// au départ, on décale légèrement chaque div
divs.forEach(div => {
var dx = 25 - Math.round(50 * Math.random());
var dy = 25 - Math.round(50 * Math.random());
div.style.left = O.x + dx + "px";
div.style.top = O.y + dy + "px";
});
// appelle la fonction récursive
repousse(divs);
});
</script>
</head>
<body>
<div></div> <div></div> <div></div> <div></div>
<div></div> <div></div> <div></div> <div></div>
<div></div> <div></div> <div></div> <div></div>
<div></div> <div></div> <div></div> <div></div>
</body>
</html> |
Partager