Skip to content

Commit 24ff182

Browse files
committed
update conf svg
1 parent ce4dee4 commit 24ff182

6 files changed

Lines changed: 488 additions & 0 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# SVG Interactif - Guide d'utilisation rapide
2+
3+
## Pour ajouter zoom/pan/fullscreen à un SVG
4+
5+
### Étape 1 : Inclure les styles (une fois en haut de la page)
6+
```markdown
7+
{% include svg-interactive-styles.html %}
8+
```
9+
10+
### Étape 2 : Wrapper le SVG avec les contrôles
11+
```markdown
12+
<div class="figure svg-wrap" style="width:100%; overflow-x:auto;">
13+
<div class="btn-group-vertical position-absolute top-0 end-0 p-2 svg-controls">
14+
<button class="btn btn-light btn-sm svg-zoom-in" title="Zoom avant">🔍+</button>
15+
<button class="btn btn-light btn-sm svg-zoom-out" title="Zoom arrière">🔍−</button>
16+
<button class="btn btn-light btn-sm svg-zoom-reset" title="Réinitialiser">↻</button>
17+
<button class="btn btn-light btn-sm svg-fullscreen" title="Plein écran">⛶</button>
18+
</div>
19+
<p style="margin: 0; padding: 0;">
20+
{% include VOTRE-SVG.svg %}
21+
</p>
22+
</div>
23+
```
24+
25+
### Étape 3 : Inclure le script (une fois en bas de la page)
26+
```markdown
27+
{% include svg-interactive-script.html %}
28+
```
29+
30+
## Exemple complet
31+
32+
Voir `input/pagecontent/professionnel.md` pour un exemple d'implémentation.
33+
34+
## Notes
35+
36+
- ✅ Supporte plusieurs SVG sur la même page
37+
- ✅ Zoom : boutons ou molette
38+
- ✅ Pan : clic + glisser
39+
- ✅ Fullscreen : bouton ⛶
40+
- ✅ Mobile : pinch zoom et touch pan
41+
- ✅ Les liens dans le SVG restent cliquables
42+
43+
## Fichiers créés
44+
45+
- `input/includes/svg-interactive-styles.html` : CSS réutilisable
46+
- `input/includes/svg-interactive-script.html` : JavaScript réutilisable
47+
- `input/includes/README-SVG-INTERACTIF.md` : Ce guide
48+
- `input/includes/UTILISATION-SVG-INTERACTIF.md` : Documentation détaillée
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Comment utiliser la fonctionnalité SVG Interactif
2+
3+
## Description
4+
Cette fonctionnalité ajoute des contrôles de zoom, pan (déplacement) et plein écran à vos SVG.
5+
6+
## Utilisation dans vos pages markdown
7+
8+
### 1. Inclure les styles (une seule fois par page)
9+
En haut de votre fichier markdown, ajoutez :
10+
```markdown
11+
{% include svg-interactive-styles.html %}
12+
```
13+
14+
### 2. Wrapper votre SVG
15+
Enveloppez votre SVG avec la structure suivante :
16+
17+
```markdown
18+
<div class="figure svg-wrap" style="width:100%; overflow-x:auto;">
19+
<div class="btn-group-vertical position-absolute top-0 end-0 p-2 svg-controls">
20+
<button class="btn btn-light btn-sm svg-zoom-in" title="Zoom avant">🔍+</button>
21+
<button class="btn btn-light btn-sm svg-zoom-out" title="Zoom arrière">🔍−</button>
22+
<button class="btn btn-light btn-sm svg-zoom-reset" title="Réinitialiser">↻</button>
23+
<button class="btn btn-light btn-sm svg-fullscreen" title="Plein écran">⛶</button>
24+
</div>
25+
<p style="margin: 0; padding: 0;">
26+
{% include votre-fichier.svg %}
27+
</p>
28+
</div>
29+
```
30+
31+
### 3. Inclure le script (une seule fois à la fin de la page)
32+
À la fin de votre fichier markdown, ajoutez :
33+
```markdown
34+
{% include svg-interactive-script.html %}
35+
```
36+
37+
## Exemple complet
38+
39+
Voir le fichier `input/pagecontent/professionnel.md` pour un exemple d'utilisation complète.
40+
41+
## Fonctionnalités disponibles
42+
43+
- **Zoom avant/arrière** : Boutons + / - ou molette de la souris
44+
- **Réinitialiser** : Bouton ↻
45+
- **Plein écran** : Bouton ⛶
46+
- **Pan (déplacement)** : Clic gauche + glisser
47+
- **Zoom tactile** : Pinch zoom sur mobile
48+
- **Les liens restent cliquables** : Les liens dans le SVG fonctionnent normalement
49+
50+
## Notes techniques
51+
52+
- Le script s'initialise automatiquement pour tous les éléments avec la classe `svg-wrap`
53+
- Vous pouvez avoir plusieurs SVG interactifs sur la même page
54+
- Le CSS et le JavaScript sont isolés et n'interfèrent pas avec le reste de la page

input/includes/figure-caption.html

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<style>
2+
body {
3+
counter-reset: figures;
4+
}
5+
6+
/* On incrémente seulement les figures qui ont une légende */
7+
figure:has(figcaption) {
8+
counter-increment: figures;
9+
margin: 1.5em 0;
10+
}
11+
12+
/* Légende */
13+
14+
figure figcaption {
15+
text-align: center;
16+
margin-top: 0.6em;
17+
font-style: italic;
18+
color: #555;
19+
font-size: 0.95em;
20+
}
21+
22+
figure figcaption:before {
23+
content: 'Figure ' counter(figures) ' : ';
24+
font-weight: bold;
25+
font-style: normal;
26+
color: #222;
27+
}
28+
</style>
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
<!-- Script pour les contrôles SVG interactifs -->
2+
<script>
3+
(function() {
4+
// Attendre que le DOM soit complètement chargé
5+
if (document.readyState === 'loading') {
6+
document.addEventListener('DOMContentLoaded', initAllSvgWrappers);
7+
} else {
8+
initAllSvgWrappers();
9+
}
10+
11+
function initAllSvgWrappers() {
12+
// Initialiser tous les wrappers SVG sur la page
13+
const wrappers = document.querySelectorAll('.svg-wrap');
14+
wrappers.forEach(wrap => initSvgWrapper(wrap));
15+
}
16+
17+
function initSvgWrapper(wrap) {
18+
if (!wrap) return;
19+
20+
// Trouver le SVG inclus
21+
const svg = wrap.querySelector('svg');
22+
if (!svg) {
23+
console.warn('SVG non trouvé dans', wrap);
24+
return;
25+
}
26+
27+
let scale = 1;
28+
let translateX = 0;
29+
let translateY = 0;
30+
const zoomStep = 0.15;
31+
const minScale = 0.3;
32+
const maxScale = 5;
33+
const panSpeed = 1;
34+
35+
let isPanning = false;
36+
let startX = 0;
37+
let startY = 0;
38+
39+
// Fonction pour vérifier si on est en fullscreen
40+
function isFullscreen() {
41+
return document.fullscreenElement === wrap;
42+
}
43+
44+
function applyTransform() {
45+
if (isFullscreen()) {
46+
svg.style.transformOrigin = "center center";
47+
svg.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
48+
} else {
49+
// En mode normal, pas de transformation
50+
svg.style.transform = '';
51+
}
52+
}
53+
54+
// Récupérer les boutons de contrôle pour ce wrapper spécifique
55+
const zoomInBtn = wrap.querySelector('.svg-zoom-in');
56+
const zoomOutBtn = wrap.querySelector('.svg-zoom-out');
57+
const zoomResetBtn = wrap.querySelector('.svg-zoom-reset');
58+
const fsBtn = wrap.querySelector('.svg-fullscreen');
59+
60+
/* ZOOM PAR BOUTONS */
61+
if (zoomInBtn) {
62+
zoomInBtn.addEventListener('click', (e) => {
63+
if (!isFullscreen()) return; // Actif uniquement en fullscreen
64+
e.preventDefault();
65+
e.stopPropagation();
66+
scale = Math.min(maxScale, scale + zoomStep);
67+
applyTransform();
68+
});
69+
}
70+
71+
if (zoomOutBtn) {
72+
zoomOutBtn.addEventListener('click', (e) => {
73+
if (!isFullscreen()) return; // Actif uniquement en fullscreen
74+
e.preventDefault();
75+
e.stopPropagation();
76+
scale = Math.max(minScale, scale - zoomStep);
77+
applyTransform();
78+
});
79+
}
80+
81+
if (zoomResetBtn) {
82+
zoomResetBtn.addEventListener('click', (e) => {
83+
if (!isFullscreen()) return; // Actif uniquement en fullscreen
84+
e.preventDefault();
85+
e.stopPropagation();
86+
scale = 1;
87+
translateX = 0;
88+
translateY = 0;
89+
applyTransform();
90+
});
91+
}
92+
93+
/* ZOOM À LA MOLETTE */
94+
wrap.addEventListener('wheel', (e) => {
95+
if (!isFullscreen()) return; // Actif uniquement en fullscreen
96+
e.preventDefault();
97+
const delta = e.deltaY < 0 ? zoomStep : -zoomStep;
98+
scale = Math.min(maxScale, Math.max(minScale, scale + delta));
99+
applyTransform();
100+
}, { passive: false });
101+
102+
/* PAN (déplacement à la souris) */
103+
wrap.addEventListener('mousedown', (e) => {
104+
if (!isFullscreen()) return; // Actif uniquement en fullscreen
105+
106+
// Ignorer si clic sur les boutons de contrôle
107+
if (e.target.closest('.svg-controls')) return;
108+
109+
// Ignorer si clic sur un lien ou élément cliquable dans le SVG
110+
if (e.target.tagName === 'A' || e.target.closest('a')) return;
111+
if (e.target.hasAttribute('href') || e.target.getAttribute('xlink:href')) return;
112+
113+
// Ignorer si ce n'est pas le bouton gauche
114+
if (e.button !== 0) return;
115+
116+
e.preventDefault();
117+
isPanning = true;
118+
startX = e.clientX - (translateX / panSpeed);
119+
startY = e.clientY - (translateY / panSpeed);
120+
wrap.classList.add('grabbing');
121+
});
122+
123+
document.addEventListener('mousemove', (e) => {
124+
if (!isPanning) return;
125+
e.preventDefault();
126+
const deltaX = (e.clientX - startX) * panSpeed;
127+
const deltaY = (e.clientY - startY) * panSpeed;
128+
translateX = deltaX;
129+
translateY = deltaY;
130+
applyTransform();
131+
});
132+
133+
document.addEventListener('mouseup', (e) => {
134+
if (isPanning) {
135+
isPanning = false;
136+
wrap.classList.remove('grabbing');
137+
}
138+
});
139+
140+
document.addEventListener('mouseleave', () => {
141+
if (isPanning) {
142+
isPanning = false;
143+
wrap.classList.remove('grabbing');
144+
}
145+
});
146+
147+
/* PINCH ZOOM (mobile) */
148+
let touchDistance = null;
149+
let lastTouchX = 0;
150+
let lastTouchY = 0;
151+
152+
wrap.addEventListener('touchstart', (e) => {
153+
if (!isFullscreen()) return; // Actif uniquement en fullscreen
154+
if (e.touches.length === 2) {
155+
const dx = e.touches[0].clientX - e.touches[1].clientX;
156+
const dy = e.touches[0].clientY - e.touches[1].clientY;
157+
touchDistance = Math.sqrt(dx*dx + dy*dy);
158+
159+
lastTouchX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
160+
lastTouchY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
161+
} else if (e.touches.length === 1) {
162+
lastTouchX = e.touches[0].clientX;
163+
lastTouchY = e.touches[0].clientY;
164+
}
165+
});
166+
167+
wrap.addEventListener('touchmove', (e) => {
168+
if (!isFullscreen()) return; // Actif uniquement en fullscreen
169+
if (e.touches.length === 2) {
170+
e.preventDefault();
171+
172+
const dx = e.touches[0].clientX - e.touches[1].clientX;
173+
const dy = e.touches[0].clientY - e.touches[1].clientY;
174+
const newDist = Math.sqrt(dx*dx + dy*dy);
175+
176+
if (touchDistance) {
177+
const diff = newDist - touchDistance;
178+
scale += diff * 0.005;
179+
scale = Math.min(maxScale, Math.max(minScale, scale));
180+
applyTransform();
181+
}
182+
183+
touchDistance = newDist;
184+
} else if (e.touches.length === 1 && scale > 1) {
185+
e.preventDefault();
186+
const touchX = e.touches[0].clientX;
187+
const touchY = e.touches[0].clientY;
188+
189+
translateX += touchX - lastTouchX;
190+
translateY += touchY - lastTouchY;
191+
applyTransform();
192+
193+
lastTouchX = touchX;
194+
lastTouchY = touchY;
195+
}
196+
}, { passive: false });
197+
198+
wrap.addEventListener('touchend', () => {
199+
touchDistance = null;
200+
});
201+
202+
/* FULLSCREEN */
203+
if (fsBtn) {
204+
fsBtn.addEventListener('click', async (e) => {
205+
e.preventDefault();
206+
e.stopPropagation();
207+
e.stopImmediatePropagation();
208+
try {
209+
if (!document.fullscreenElement) {
210+
// Réinitialiser zoom/pan avant fullscreen
211+
scale = 1;
212+
translateX = 0;
213+
translateY = 0;
214+
applyTransform();
215+
await wrap.requestFullscreen();
216+
} else {
217+
await document.exitFullscreen();
218+
}
219+
} catch (err) {
220+
console.error('Erreur fullscreen:', err);
221+
}
222+
});
223+
}
224+
225+
// Gérer fullscreenchange pour réinitialiser quand on sort du fullscreen
226+
document.addEventListener('fullscreenchange', () => {
227+
if (!isFullscreen()) {
228+
// Sortie du fullscreen : réinitialiser le zoom/pan
229+
scale = 1;
230+
translateX = 0;
231+
translateY = 0;
232+
applyTransform();
233+
}
234+
});
235+
}
236+
})();
237+
</script>

0 commit comments

Comments
 (0)