Le mois dernier, sur un portfolio d’artiste (peintures/illustrations), je suis retombé dans le piège classique du site “100% statique” qui devient vite ingérable : à chaque nouvelle toile, il fallait ouvrir index.html, copier/coller un bloc, vérifier les chemins d’images, l’alt, les tags… puis prier pour ne rien casser.
Le problème n’était pas la “tech”, c’était le workflow : l’artiste devait pouvoir ajouter une œuvre sans toucher au HTML.
Résultat : j’ai séparé la donnée (liste des œuvres) du rendu (la page) via un simple images.csv chargé en JavaScript.
Repo de référence (code complet) :
https://github.com/vladimir-monari/monartistique/blob/main/
1) L’idée : des données dans images.csv, un rendu automatique dans le DOM
Structure CSV (la “source de vérité”)
J’ai standardisé un fichier images.csv avec des en-têtes stables (importants, car le code les référence tels quels) :
Nom de l'image,Chemin de l'image,Description de l'image,Tags
Portrait Bleu,/img/bleu.jpg,Huile sur toile,abstrait-portrait
Coucher de soleil,/img/soleil.jpg,Aquarelle,paysage-nature- Nom de l’image : sert de titre affiché
- Chemin de l’image : URL relative (ex:
/images/...ou/img/...) - Description de l’image : sert de texte descriptif (et
alt) - Tags : une liste séparée par
-(ex:abstrait-portrait)
Ce point (séparateur -) est volontaire : c’est simple à saisir dans Google Sheets, et trivial à parser côté JS.
2) index.html : une page “coquille” (et c’est exactement ce qu’on veut)
Ce que j’ai gardé dans index.html, c’est :
- tout ce qui est structurel (header, section “about”, footer)
- les conteneurs vides destinés à être remplis (
#image-container,#tag-cloud) - les dépendances (CSS + PapaParse) et le script applicatif
scripts.js - la modal d’affichage grand format
Points importants dans ton index.html :
a) Les conteneurs clés
<div id="tag-cloud"></div>
<div id="image-container"></div>Ils définissent les “ports d’entrée” où le JS injecte la galerie.
b) La modal déjà prête
<div id="modal" class="modal">
<span class="close">×</span>
<img class="modal-content" id="modal-image">
<div id="caption"></div>
</div>C’est un bon pattern : la structure est déclarative dans le HTML, et le JS ne fait que manipuler display, src et le texte.
c) Dépendances : PapaParse OK, D3 probablement inutile
Tu charges :
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.0/papaparse.min.js"></script>
<script src="https://d3js.org/d3.v5.min.js"></script>PapaParse est effectivement utilisé. En revanche, D3 n’est pas utilisé dans scripts.js tel qu’il est actuellement (le nuage de tags est construit au DOM “vanilla”). Donc tu peux supprimer D3 pour alléger la page, sauf si tu prévois une vraie visualisation.
d) Google Tag Manager / Analytics
Tu as GTM + gtag. Ça fonctionne, mais garde en tête :
- c’est du poids JS supplémentaire
- idéalement, documente dans un README ce qui est mesuré (évite la “boîte noire” côté maintenance)
3) scripts.js : le pipeline complet (CSV → objets → DOM → tags → filtres)
a) Chargement du CSV
function loadCSV(callback) {
fetch('images.csv')
.then(response => response.text())
.then(data => callback(data))
.catch(error => console.error('Erreur lors du chargement du CSV:', error));
}Ce que ça implique :
images.csvdoit être dans le même dossier queindex.html(ou il faut ajuster le chemin)fetch()ne marchera pas correctement en ouvrant le fichier enfile://(cf. pièges plus bas)
b) Parsing + génération des cartes image
Le cœur est ici :
Papa.parse(data, {
header: true,
complete: function (results) {
results.data.forEach(function (d) {
if (!d["Nom de l'image"] || !d["Chemin de l'image"]) return;var container = document.createElement(‘div’);container.classList.add(‘image-wrapper’);
container.dataset.tags = d[‘Tags’];
var img = new Image();
img.src = d[« Chemin de l’image »];
img.alt = d[« Description de l’image »];
img.classList.add(« image-responsive »);
…
Bon choix : tu fais un early return si les champs essentiels manquent (ça neutralise déjà pas mal de lignes vides / erreurs CSV).
Ensuite :
dataset.tagssert à filtreraltest alimenté par la description (bon réflexe accessibilité/SEO)- l’ouverture de modal se fait au clic
c) Nuage de tags + filtrage
Tu comptes les tags :
d['Tags'].split('-').forEach(function (tag) {
tags[tag] = (tags[tag] || 0) + 1;
});et tu construis le nuage avec une taille proportionnelle au nombre d’occurrences.
4) Le flux d’automatisation (celui qui change vraiment la vie)
Le flux que j’ai mis en place (et qui tient bien dans le temps) :
- L’artiste met à jour une feuille (Google Sheets / Excel)
- Export en CSV
- Dépôt du CSV + des images dans le repo
git push- Déploiement (GitHub Pages ou autre) → la galerie se met à jour automatiquement
Le gros gain : plus jamais d’édition manuelle du HTML pour une nouvelle œuvre.
5) Les pièges rencontrés (et les corrections concrètes)
1) Lignes vides / objets incomplets (PapaParse)
Tu l’as déjà traité en partie avec :
if (!d["Nom de l'image"] || !d["Chemin de l'image"]) return;Je garde exactement cette logique, et j’ajoute souvent une défense sur Tags :
const rawTags = (d['Tags'] || '').trim();
container.dataset.tags = rawTags;Sinon, split('-') sur undefined peut casser.
2) CORS en local (fetch + file://)
Classique : ouvrir index.html en double-cliquant ne suffit pas.
Solution : serveur local (Live Server, python -m http.server, etc.).
Tu l’as bien noté : Live Server sous VS Code règle ça immédiatement.
3) XSS / injection via CSV (le vrai point sensible)
Dans ton code actuel, il y a un endroit franchement risqué :
document.getElementById("caption").innerHTML = this.alt;Même si “c’est juste un CSV”, c’est une entrée de données. Si un jour une valeur contient des balises, tu les interprètes.
Correction simple et robuste : ne jamais utiliser innerHTML pour du texte :
document.getElementById("caption").textContent = this.alt;Ça te rend la fonction sanitize() beaucoup moins critique (et tu peux la réserver aux cas où tu dois vraiment injecter du HTML).
4) Bug discret : double conteneur #tag-cloud
Dans index.html, tu as déjà :
<div id="tag-cloud"></div>Mais dans displayTagCloud, tu recrées un nouveau div avec le même id et tu l’insères après le <h3> :
var tagCloud = document.createElement('div');
tagCloud.id = 'tag-cloud';
document.querySelector('#container h3').insertAdjacentElement('afterend', tagCloud);Conséquence : tu peux te retrouver avec deux éléments portant le même id (invalid HTML, comportements bizarres).
Correction : réutiliser le conteneur existant :
function displayTagCloud(tags) {
var tagCloud = document.getElementById('tag-cloud');
tagCloud.innerHTML = ''; // reset
...
}6) styles.css : ce qui marche bien (et ce que j’ajusterais)
Points positifs :
- layout simple, lisible, cohérent
.image-wrappereninline-block: pratique pour une galerie “mosaïque”- modal plein écran avec fond noir : efficace pour de l’art
Deux remarques terrain :
a) max-width: 100vh sur .modal-content
Tu as :
.modal-content {
max-width: 100vh;
max-height: 90vh;
}max-width en vh (hauteur viewport) est inhabituel : sur écran large, ça peut limiter la largeur de façon contre-intuitive. En général, je préfère :
.modal-content {
max-width: 95vw;
max-height: 90vh;
}b) Performance visuelle
Si la galerie grandit, pense à :
loading="lazy"côté image- éventuellement des miniatures dédiées (évite de charger un 4000px pour une vignette)
7) Améliorations futures (SEO, UX, perf) — plan d’évolution réaliste
UX / Accessibilité
- fermer la modal au clic hors image + touche
Escape - focus management (quand la modal s’ouvre, focus sur le bouton close)
- ajouter
aria-labelau bouton close
Performance
img.loading = "lazy";- pagination / chargement par lots (12 par 12)
- miniatures + images HD séparées (ex:
thumb_pathetfull_pathdans le CSV)
SEO
altdéjà OK, mais tu peux enrichir :- titres d’œuvres en
<h4>(ou au moins structure sémantique) - JSON-LD (type
ImageObjectouCreativeWork) si tu veux pousser le référencement
- titres d’œuvres en
Sécurité
- bannir
innerHTMLpour tout texte issu du CSV (caption, descriptions, titres) - valider les chemins d’images (optionnel, mais utile si le CSV est édité par plusieurs personnes)
Conclusion
Le vrai bénéfice de cette approche CSV + rendu JS, c’est qu’elle transforme un site statique fragile en mini “CMS maison”, sans serveur, sans base de données, sans back-office. Pour un portfolio d’artiste, c’est exactement le bon compromis : simple à maintenir, rapide à charger, et évolutif.
Code complet (structure, index.html, images.csv, scripts.js, styles.css) :
https://github.com/vladimir-monari/monartistique/blob/main/