Initial commit
This commit is contained in:
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
8
.idea/custom-libs.iml
generated
Normal file
8
.idea/custom-libs.iml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
14
.idea/discord.xml
generated
Normal file
14
.idea/discord.xml
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="ASK" />
|
||||
<option name="description" value="" />
|
||||
<option name="applicationTheme" value="default" />
|
||||
<option name="iconsTheme" value="default" />
|
||||
<option name="button1Title" value="" />
|
||||
<option name="button1Url" value="" />
|
||||
<option name="button2Title" value="" />
|
||||
<option name="button2Url" value="" />
|
||||
<option name="customApplicationId" value="" />
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/custom-libs.iml" filepath="$PROJECT_DIR$/.idea/custom-libs.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
19
.idea/php.xml
generated
Normal file
19
.idea/php.xml
generated
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MessDetectorOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCSFixerOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCodeSnifferOptionsConfiguration">
|
||||
<option name="highlightLevel" value="WARNING" />
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PhpStanOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PsalmOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
28
JS/JustifedGallery/README.md
Normal file
28
JS/JustifedGallery/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# 🖼️ Justified Gallery
|
||||
|
||||
**Justified Gallery** est une librairie JavaScript légère qui permet de générer une **galerie d’images justifiée**, responsive, fluide et entièrement autonome.
|
||||
Elle gère aussi une **modal intégrée** pour afficher les images en grand, avec navigation clavier, zoom et déplacement à la souris.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Fonctionnalités
|
||||
|
||||
- 🧩 Mise en page automatique des images (justifiée selon le conteneur).
|
||||
- 📱 Responsive grâce aux **breakpoints configurables**.
|
||||
- 🖱️ Navigation **clavier (flèches, entrée)** et **clic**.
|
||||
- 🪟 **Modal intégrée** avec :
|
||||
- navigation entre images,
|
||||
- zoom, drag, et fermeture via *Escape*.
|
||||
- 🔗 Option pour encapsuler automatiquement les images dans des liens.
|
||||
- 🧠 Aucun framework requis (pur JavaScript ES6+).
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Installation
|
||||
|
||||
Inclure simplement le fichier dans ton projet :
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import JustifiedGallery from './justified-gallery.js';
|
||||
</script>
|
||||
81
JS/JustifedGallery/assets/css/lib/gallery.css
Normal file
81
JS/JustifedGallery/assets/css/lib/gallery.css
Normal file
@@ -0,0 +1,81 @@
|
||||
/* Conteneur de la galerie */
|
||||
.justified-gallery {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
/* Images */
|
||||
.justified-gallery img {
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
vertical-align: top;
|
||||
transition: all 0.2s;
|
||||
filter: blur(2px) brightness(0.8);
|
||||
}
|
||||
.justified-gallery img:hover {
|
||||
transform: scale(1.03);
|
||||
filter: blur(0px) brightness(1);
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
.jg-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: rgba(0,0,0,0.8);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.jg-modal img {
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
transition: transform 0.2s ease;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.jg-close {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 35px;
|
||||
color: #fff;
|
||||
font-size: 40px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.jg-controls span {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
color: #fff;
|
||||
font-size: 50px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
padding: 10px;
|
||||
background: rgba(0,0,0,0.3);
|
||||
border-radius: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.jg-prev { left: 20px; }
|
||||
.jg-next { right: 20px; }
|
||||
|
||||
.jg-prev, .jg-next {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: rgba(255,255,255,0.7);
|
||||
border: none;
|
||||
font-size: 2rem;
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
29
JS/JustifedGallery/assets/css/style.css
Normal file
29
JS/JustifedGallery/assets/css/style.css
Normal file
@@ -0,0 +1,29 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 30px;
|
||||
background-color: #fcfcfc;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
.gallery {
|
||||
margin-top: 100px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.gallery a {
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
width: fit-content;
|
||||
}
|
||||
.gallery img {
|
||||
width: 100%;
|
||||
}
|
||||
BIN
JS/JustifedGallery/assets/img/PANA4649_1.jpg
Normal file
BIN
JS/JustifedGallery/assets/img/PANA4649_1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
BIN
JS/JustifedGallery/assets/img/PANA6308.jpg
Normal file
BIN
JS/JustifedGallery/assets/img/PANA6308.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.4 MiB |
BIN
JS/JustifedGallery/assets/img/PANA6677.jpg
Normal file
BIN
JS/JustifedGallery/assets/img/PANA6677.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 MiB |
BIN
JS/JustifedGallery/assets/img/PANA7701.jpg
Normal file
BIN
JS/JustifedGallery/assets/img/PANA7701.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 MiB |
BIN
JS/JustifedGallery/assets/img/PANA7846.jpg
Normal file
BIN
JS/JustifedGallery/assets/img/PANA7846.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 MiB |
10
JS/JustifedGallery/assets/js/index.js
Normal file
10
JS/JustifedGallery/assets/js/index.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import JustifiedGallery from "./lib/gallery.js";
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
let element = document.getElementById("gallery")
|
||||
|
||||
new JustifiedGallery(element, {
|
||||
wrapLinks: true,
|
||||
});
|
||||
})
|
||||
|
||||
387
JS/JustifedGallery/assets/js/lib/gallery.js
Normal file
387
JS/JustifedGallery/assets/js/lib/gallery.js
Normal file
@@ -0,0 +1,387 @@
|
||||
export default class JustifiedGallery {
|
||||
|
||||
constructor(container, options = {}) {
|
||||
this.container = container;
|
||||
this.options = {
|
||||
margin: options.margin || 5,
|
||||
columns: options.columns || 3,
|
||||
breakpoints: options.breakpoints || { 768: 2, 480: 1 },
|
||||
wrapLinks: options.wrapLinks || false, // active l'encapsulation
|
||||
linkAttribute: options.linkAttribute || 'src', // attribut à utiliser pour href
|
||||
justifyLastRow: options.justifyLastRow || false,
|
||||
};
|
||||
this.currentIndex = 0;
|
||||
|
||||
this.isOpenModal = false;
|
||||
|
||||
this.init();
|
||||
window.addEventListener('resize', () => this.layoutGallery());
|
||||
}
|
||||
|
||||
init() {
|
||||
this.images = this.getImages();
|
||||
|
||||
// Encapsule les images si nécessaire
|
||||
if (this.options.wrapLinks) {
|
||||
this.images.forEach(img => this.wrapImage(img));
|
||||
this.images = this.getImages(); // rafraîchit la liste
|
||||
}
|
||||
|
||||
// S'assure que toutes les images sont chargées
|
||||
const promises = this.images.map(img => this.waitForImageLoad(img));
|
||||
Promise.all(promises).then(() => {
|
||||
this.layoutGallery();
|
||||
this.setupModal();
|
||||
});
|
||||
|
||||
this.container.addEventListener('keydown', e => {
|
||||
e.preventDefault();
|
||||
if (e.key === 'ArrowRight') {
|
||||
e.target.nextSibling.nextSibling.focus()
|
||||
}
|
||||
if (e.key === "ArrowLeft") {
|
||||
e.target.previousSibling.previousSibling.focus()
|
||||
}
|
||||
if (e.key === "ArrowDown") {
|
||||
|
||||
const images = this.getImages();
|
||||
let target = e.target;
|
||||
|
||||
for (let i = 0; i < this.options.columns; i++) {
|
||||
if (Math.ceil(images.length / this.options.columns) <= i) {
|
||||
// On récupère le dernier élément si on dépasse la dernière ligne
|
||||
target = images[images.length - 1].parentNode;
|
||||
break;
|
||||
}
|
||||
// Utiliser nextElementSibling pour éviter les text nodes
|
||||
if (target.nextElementSibling) {
|
||||
target = target.nextElementSibling;
|
||||
}
|
||||
}
|
||||
|
||||
if (target) {
|
||||
target.focus();
|
||||
// Scroll pour voir la ligne où se trouve l'image focus
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
}
|
||||
if (e.key === "ArrowUp") {
|
||||
let target = e.target
|
||||
for (let i = 0; this.options.columns > i; i++) {
|
||||
if (this.getImages().length < i) {
|
||||
target = this.getImages()[0];
|
||||
break;
|
||||
}
|
||||
target = target.previousSibling.previousSibling
|
||||
}
|
||||
if (target) {
|
||||
target.focus();
|
||||
// Scroll pour voir la ligne où se trouve l'image focus
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
}
|
||||
if (e.key === "Enter") {
|
||||
e.target.click()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
wrapImage(img) {
|
||||
const href = img.getAttribute(this.options.linkAttribute) || img.src;
|
||||
const a = document.createElement('a');
|
||||
a.href = href;
|
||||
a.style.display = 'inline-block';
|
||||
img.parentNode.insertBefore(a, img);
|
||||
a.appendChild(img);
|
||||
}
|
||||
|
||||
getImages() {
|
||||
return Array.from(this.container.querySelectorAll('img'));
|
||||
}
|
||||
|
||||
waitForImageLoad(img) {
|
||||
return new Promise(resolve => {
|
||||
if (img.complete) resolve();
|
||||
else {
|
||||
img.onload = () => resolve();
|
||||
img.onerror = () => resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getColumns() {
|
||||
const width = this.container.clientWidth;
|
||||
let cols = this.options.columns;
|
||||
for (let bp in this.options.breakpoints) {
|
||||
if (width <= bp) cols = this.options.breakpoints[bp];
|
||||
}
|
||||
return cols;
|
||||
}
|
||||
|
||||
layoutGallery() {
|
||||
const containerWidth = this.container.clientWidth;
|
||||
const margin = this.options.margin;
|
||||
const cols = this.getColumns();
|
||||
|
||||
let rowImages = [];
|
||||
let totalRatio = 0;
|
||||
|
||||
this.images.forEach((img, index) => {
|
||||
const ratio = img.naturalWidth / img.naturalHeight;
|
||||
|
||||
if (this.options.wrapLinks) {
|
||||
rowImages.push({ img: img.parentNode, ratio });
|
||||
} else {
|
||||
rowImages.push({ img, ratio });
|
||||
}
|
||||
|
||||
totalRatio += ratio;
|
||||
|
||||
const isLastRow = index === this.images.length - 1;
|
||||
if (rowImages.length === cols || isLastRow) {
|
||||
let rowHeight;
|
||||
if (isLastRow && rowImages.length < cols && !this.options.justifyLastRow) {
|
||||
// Ne pas étirer la dernière ligne
|
||||
const avgRatio = totalRatio / rowImages.length;
|
||||
rowHeight = Math.floor((containerWidth / cols) / avgRatio);
|
||||
} else {
|
||||
// Étire normalement pour remplir la ligne
|
||||
rowHeight = Math.floor((containerWidth - margin * (rowImages.length - 1)) / totalRatio);
|
||||
}
|
||||
this.scaleRow(rowImages, rowHeight, margin);
|
||||
rowImages = [];
|
||||
totalRatio = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
scaleRow(row, rowHeight, margin) {
|
||||
row.forEach((item, index) => {
|
||||
const width = rowHeight * item.ratio;
|
||||
|
||||
|
||||
if (this.options.wrapLinks) {
|
||||
item.img.style.overflow = 'hidden';
|
||||
item.img.firstChild.style.width = '100%';
|
||||
}
|
||||
|
||||
item.img.style.width = `${width}px`;
|
||||
item.img.style.height = `${rowHeight}px`;
|
||||
item.img.style.marginRight = index < row.length - 1 ? `${margin}px` : '0';
|
||||
item.img.style.marginBottom = `${margin}px`;
|
||||
item.img.style.cursor = 'pointer';
|
||||
});
|
||||
}
|
||||
|
||||
setupModal() {
|
||||
// Création du modal
|
||||
this.modal = document.createElement('div');
|
||||
this.modal.classList.add('jg-modal');
|
||||
this.modal.innerHTML = `
|
||||
<span class="jg-close">×</span>
|
||||
<img class="jg-modal-img" src="">
|
||||
<div class="jg-controls">
|
||||
<span class="jg-prev">❮</span>
|
||||
<span class="jg-next">❯</span>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(this.modal);
|
||||
|
||||
// Références
|
||||
this.modalImg = this.modal.querySelector('.jg-modal-img');
|
||||
this.closeBtn = this.modal.querySelector('.jg-close');
|
||||
this.prevBtn = this.modal.querySelector('.jg-prev');
|
||||
this.nextBtn = this.modal.querySelector('.jg-next');
|
||||
|
||||
// Événements
|
||||
this.images.forEach((img, idx) => {
|
||||
img.addEventListener('click', e => {
|
||||
e.preventDefault(); // empêche le clic sur le lien
|
||||
this.openModal(idx);
|
||||
});
|
||||
img.parentNode.addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this.openModal(idx);
|
||||
}
|
||||
})
|
||||
});
|
||||
this.closeBtn.addEventListener('click', () => this.closeModal());
|
||||
this.prevBtn.addEventListener('click', () => this.prevImage());
|
||||
this.nextBtn.addEventListener('click', () => this.nextImage());
|
||||
this.modal.addEventListener('click', e => {
|
||||
if (e.target === this.modal) this.closeModal();
|
||||
});
|
||||
document.addEventListener('keydown', e => {
|
||||
if (this.modal.style.display === 'flex') {
|
||||
if (e.key === 'ArrowLeft') this.prevImage();
|
||||
if (e.key === 'ArrowRight') this.nextImage();
|
||||
if (e.key === 'Escape') this.closeModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Variables pour drag + zoom
|
||||
this.modalImg.currentScale = 1;
|
||||
let isDragging = false;
|
||||
let startX, startY;
|
||||
let currentTranslateX = 0, currentTranslateY = 0;
|
||||
|
||||
// Drag start
|
||||
this.modalImg.addEventListener('mousedown', e => {
|
||||
if (e.button !== 0) return;
|
||||
isDragging = true;
|
||||
startX = e.clientX;
|
||||
startY = e.clientY;
|
||||
this.modalImg.style.cursor = 'grabbing';
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
// Drag move
|
||||
this.modal.addEventListener('mousemove', e => {
|
||||
if (!isDragging) return;
|
||||
const dx = e.clientX - startX;
|
||||
const dy = e.clientY - startY;
|
||||
startX = e.clientX;
|
||||
startY = e.clientY;
|
||||
|
||||
currentTranslateX += dx;
|
||||
currentTranslateY += dy;
|
||||
|
||||
this.modalImg.style.transform = `translate(${currentTranslateX}px, ${currentTranslateY}px) scale(${this.modalImg.currentScale})`;
|
||||
});
|
||||
|
||||
// Drag end
|
||||
this.modal.addEventListener('mouseup', () => {
|
||||
isDragging = false;
|
||||
this.modalImg.style.cursor = 'grab';
|
||||
});
|
||||
this.modal.addEventListener('mouseleave', () => {
|
||||
isDragging = false;
|
||||
this.modalImg.style.cursor = 'grab';
|
||||
});
|
||||
|
||||
// Scroll pour zoom
|
||||
this.modalImg.addEventListener('wheel', e => {
|
||||
e.preventDefault();
|
||||
const scaleAmount = e.deltaY > 0 ? -0.1 : 0.1;
|
||||
this.modalImg.currentScale = Math.min(Math.max(this.modalImg.currentScale + scaleAmount, 0.5), 3); // limites min/max
|
||||
this.modalImg.style.transform = `translate(${currentTranslateX}px, ${currentTranslateY}px) scale(${this.modalImg.currentScale})`;
|
||||
});
|
||||
}
|
||||
|
||||
openModal(index) {
|
||||
this.isOpenModal = true;
|
||||
this.currentIndex = index;
|
||||
this.modal.style.display = 'flex';
|
||||
this.modalImg.src = this.images[this.currentIndex].src;
|
||||
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
closeModal() {
|
||||
this.isOpenModal = false;
|
||||
this.modal.style.display = 'none';
|
||||
document.body.style.overflow = 'scroll';
|
||||
|
||||
// Reset zoom et position
|
||||
this.modalImg.currentScale = 1;
|
||||
this.modalImg.style.transform = `translate(0px, 0px) scale(1)`;
|
||||
currentTranslateX = 0;
|
||||
currentTranslateY = 0;
|
||||
}
|
||||
|
||||
prevImage() {
|
||||
this.currentIndex = (this.currentIndex - 1 + this.images.length) % this.images.length;
|
||||
this.modalImg.src = this.images[this.currentIndex].src;
|
||||
}
|
||||
|
||||
nextImage() {
|
||||
this.currentIndex = (this.currentIndex + 1) % this.images.length;
|
||||
this.modalImg.src = this.images[this.currentIndex].src;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class Gallery {
|
||||
constructor(container, options = {}) {
|
||||
this.container = container;
|
||||
this.options = {
|
||||
margin: options.margin || 5,
|
||||
columns: options.columns || 4, // nombre de colonnes par défaut
|
||||
breakpoints: options.breakpoints || { 768: 2, 480: 1 }, // largeur => colonnes
|
||||
};
|
||||
|
||||
this.init();
|
||||
window.addEventListener('resize', () => this.layoutGallery());
|
||||
}
|
||||
|
||||
init() {
|
||||
this.images = this.getImages();
|
||||
|
||||
// On attend que toutes les images soient chargées pour avoir leurs dimensions naturelles
|
||||
const promises = this.images.map(img => this.waitForImageLoad(img));
|
||||
Promise.all(promises).then(() => this.layoutGallery());
|
||||
}
|
||||
|
||||
getImages() {
|
||||
return Array.from(this.container.querySelectorAll('img'));
|
||||
}
|
||||
|
||||
waitForImageLoad(img) {
|
||||
return new Promise(resolve => {
|
||||
if (img.complete) resolve();
|
||||
else {
|
||||
img.onload = () => resolve();
|
||||
img.onerror = () => resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
calculateRatio(img) {
|
||||
return img.naturalWidth / img.naturalHeight;
|
||||
}
|
||||
|
||||
getColumns() {
|
||||
const width = this.container.clientWidth;
|
||||
let cols = this.options.columns;
|
||||
|
||||
// Vérifie les breakpoints pour adapter le nombre de colonnes
|
||||
for (let bp in this.options.breakpoints) {
|
||||
if (width <= bp) {
|
||||
cols = this.options.breakpoints[bp];
|
||||
}
|
||||
}
|
||||
|
||||
return cols;
|
||||
}
|
||||
|
||||
layoutGallery() {
|
||||
const containerWidth = this.container.clientWidth;
|
||||
const margin = this.options.margin;
|
||||
const cols = this.getColumns();
|
||||
const rowHeight = (containerWidth - margin * (cols - 1)) / cols; // largeur d'une "colonne" comme base
|
||||
let rowImages = [];
|
||||
|
||||
this.images.forEach((img, index) => {
|
||||
const ratio = this.calculateRatio(img);
|
||||
rowImages.push({ img, ratio });
|
||||
|
||||
if (rowImages.length === cols || index === this.images.length - 1) {
|
||||
this.scaleRow(rowImages, rowHeight, margin);
|
||||
rowImages = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
scaleRow(row, rowHeight, margin) {
|
||||
row.forEach((item, index) => {
|
||||
const width = rowHeight * item.ratio;
|
||||
item.img.style.width = `${width}px`;
|
||||
item.img.style.height = `${rowHeight}px`;
|
||||
item.img.style.marginRight = index < row.length - 1 ? `${margin}px` : '0';
|
||||
item.img.style.marginBottom = `${margin}px`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
27
JS/JustifedGallery/index.html
Normal file
27
JS/JustifedGallery/index.html
Normal file
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<title>Test de création d'une gallerie justifié</title>
|
||||
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
<link rel="stylesheet" href="assets/css/lib/gallery.css">
|
||||
|
||||
<script src="assets/js/index.js" type="module" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Laboratoire - Gallery library</h1>
|
||||
<div id="gallery" class="justified-gallery">
|
||||
<img src="assets/img/PANA6677.jpg" alt="">
|
||||
<img src="assets/img/PANA6308.jpg" alt="">
|
||||
<img src="assets/img/PANA6677.jpg" alt="">
|
||||
<img src="assets/img/PANA7701.jpg" alt="">
|
||||
<img src="assets/img/PANA6308.jpg" alt="">
|
||||
<img src="assets/img/PANA4649_1.jpg" alt="">
|
||||
<img src="assets/img/PANA7846.jpg" alt="">
|
||||
</di
|
||||
</body>
|
||||
</html>
|
||||
60
README.md
Normal file
60
README.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# 🧩 Aranéite Utils — Librairies PHP & JS réutilisables
|
||||
|
||||
Ce dépôt regroupe l’ensemble des **librairies JavaScript et PHP** développées pour mes projets.
|
||||
Chaque module est conçu pour être **modulaire, léger et réutilisable** dans différents environnements :
|
||||
sites statiques, frameworks comme Laravel, WordPress ou Symfony, ou encore des applications front-end modernes.
|
||||
|
||||
---
|
||||
|
||||
## 📁 Structure du dépôt
|
||||
|
||||
```
|
||||
├── js/
|
||||
│ └── justified-gallery/
|
||||
│ └── justified-gallery.js
|
||||
│
|
||||
└── php/
|
||||
└── ...
|
||||
```
|
||||
|
||||
- **`js/`** : contient les librairies JavaScript (front-end, UI, utilitaires).
|
||||
- **`php/`** : contient les librairies PHP (helpers, classes, outils backend).
|
||||
- Chaque dossier contient son propre `README.md` pour la documentation et les exemples.
|
||||
|
||||
---
|
||||
|
||||
## 📦 Librairies disponibles
|
||||
|
||||
### ### 🖼️ `js/justified-gallery`
|
||||
|
||||
Librairie JavaScript permettant de créer une **galerie d’images justifiée et responsive**
|
||||
avec prise en charge :
|
||||
- du **redimensionnement automatique** des images,
|
||||
- d’une **navigation clavier**,
|
||||
- d’une **ouverture en modal** avec navigation entre images,
|
||||
- du **zoom et drag** dans la vue agrandie.
|
||||
|
||||
➡️ [Documentation complète](js/justified-gallery/README.md)
|
||||
|
||||
---
|
||||
|
||||
## 🚧 À venir
|
||||
|
||||
- ⚙️ `php/http-client` — wrapper léger autour de cURL.
|
||||
- 🔐 `php/env-loader` — gestion simple des variables d’environnement.
|
||||
- 🧰 `php/str-utils` — helpers de manipulation de chaînes.
|
||||
- 🎨 `js/color-tools` — outils de conversion et génération de palettes.
|
||||
|
||||
---
|
||||
|
||||
## 🪪 Licence
|
||||
|
||||
Ce dépôt est distribué sous la licence **MIT**, pour permettre une réutilisation libre dans mes projets ou ceux de la communauté.
|
||||
|
||||
---
|
||||
|
||||
## 👤 Auteur
|
||||
|
||||
**Aranéite**
|
||||
Développement web & outils open-source
|
||||
🌐 [https://araneite.fr](https://araneite.fr)
|
||||
Reference in New Issue
Block a user