Animations custom (video/GIF) + selection aleatoire
- Upload de GIF/MP4/WebM comme animations de compte a rebours - Checkboxes pour activer/desactiver chaque animation (CSS + custom) - Si plusieurs actives, tirage aleatoire a chaque photo - Preview dans le backoffice avec tirage aleatoire - Suppression des animations custom importees - Les animations custom se jouent en plein ecran avant la capture Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ DOSSIER_PHOTOS = RACINE / "data" / "photos"
|
|||||||
DOSSIER_EXPORTS = RACINE / "data" / "exports"
|
DOSSIER_EXPORTS = RACINE / "data" / "exports"
|
||||||
DOSSIER_OVERLAYS = RACINE / "frontend" / "assets" / "overlays"
|
DOSSIER_OVERLAYS = RACINE / "frontend" / "assets" / "overlays"
|
||||||
DOSSIER_FONDS = RACINE / "frontend" / "assets" / "backgrounds"
|
DOSSIER_FONDS = RACINE / "frontend" / "assets" / "backgrounds"
|
||||||
|
DOSSIER_ANIMATIONS = RACINE / "frontend" / "assets" / "animations"
|
||||||
|
|
||||||
|
|
||||||
def charger_config() -> dict:
|
def charger_config() -> dict:
|
||||||
@@ -48,5 +49,5 @@ def _merge_profond(base: dict, modifications: dict):
|
|||||||
|
|
||||||
|
|
||||||
# Initialisation des dossiers au chargement du module
|
# Initialisation des dossiers au chargement du module
|
||||||
for dossier in [DOSSIER_PHOTOS, DOSSIER_EXPORTS, DOSSIER_OVERLAYS, DOSSIER_FONDS]:
|
for dossier in [DOSSIER_PHOTOS, DOSSIER_EXPORTS, DOSSIER_OVERLAYS, DOSSIER_FONDS, DOSSIER_ANIMATIONS]:
|
||||||
dossier.mkdir(parents=True, exist_ok=True)
|
dossier.mkdir(parents=True, exist_ok=True)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from fastapi.responses import FileResponse, JSONResponse
|
|||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
from backend.config import (
|
from backend.config import (
|
||||||
RACINE, DOSSIER_PHOTOS, DOSSIER_EXPORTS, DOSSIER_OVERLAYS, DOSSIER_FONDS,
|
RACINE, DOSSIER_PHOTOS, DOSSIER_EXPORTS, DOSSIER_OVERLAYS, DOSSIER_FONDS, DOSSIER_ANIMATIONS,
|
||||||
charger_config, sauvegarder_config, mettre_a_jour_config,
|
charger_config, sauvegarder_config, mettre_a_jour_config,
|
||||||
)
|
)
|
||||||
from backend.camera import camera
|
from backend.camera import camera
|
||||||
@@ -348,6 +348,44 @@ async def api_animations():
|
|||||||
return ANIMATIONS_CAR
|
return ANIMATIONS_CAR
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/animations/custom")
|
||||||
|
async def api_animations_custom():
|
||||||
|
"""Liste les animations personnalisees (GIF/video)."""
|
||||||
|
extensions = {".gif", ".mp4", ".webm"}
|
||||||
|
fichiers = []
|
||||||
|
if DOSSIER_ANIMATIONS.exists():
|
||||||
|
for f in sorted(DOSSIER_ANIMATIONS.iterdir()):
|
||||||
|
if f.suffix.lower() in extensions:
|
||||||
|
fichiers.append(f.name)
|
||||||
|
config = charger_config()
|
||||||
|
actives = config.get("animations_custom", {}).get("actives", [])
|
||||||
|
return {"tous": fichiers, "actives": actives}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/animations/custom")
|
||||||
|
async def api_animations_custom_update(donnees: dict):
|
||||||
|
actives = donnees.get("actives", [])
|
||||||
|
mettre_a_jour_config({"animations_custom": {"actives": actives}})
|
||||||
|
return {"actives": actives}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/upload/animation")
|
||||||
|
async def api_upload_animation(fichier: UploadFile = File(...)):
|
||||||
|
chemin = DOSSIER_ANIMATIONS / fichier.filename
|
||||||
|
with open(chemin, "wb") as f:
|
||||||
|
f.write(await fichier.read())
|
||||||
|
return {"nom": fichier.filename}
|
||||||
|
|
||||||
|
|
||||||
|
@app.delete("/api/animations/custom/{nom}")
|
||||||
|
async def api_supprimer_animation(nom: str):
|
||||||
|
chemin = DOSSIER_ANIMATIONS / nom
|
||||||
|
if chemin.exists() and chemin.parent == DOSSIER_ANIMATIONS:
|
||||||
|
chemin.unlink()
|
||||||
|
return {"succes": True}
|
||||||
|
return JSONResponse({"erreur": "Fichier introuvable"}, status_code=404)
|
||||||
|
|
||||||
|
|
||||||
# --- API Systeme ---
|
# --- API Systeme ---
|
||||||
|
|
||||||
@app.post("/api/systeme/redemarrer")
|
@app.post("/api/systeme/redemarrer")
|
||||||
|
|||||||
@@ -21,7 +21,11 @@
|
|||||||
"iso": "auto",
|
"iso": "auto",
|
||||||
"balance_blancs": "auto",
|
"balance_blancs": "auto",
|
||||||
"compte_a_rebours": 3,
|
"compte_a_rebours": 3,
|
||||||
"animation_compte_a_rebours": "classique"
|
"animation_compte_a_rebours": "classique",
|
||||||
|
"animations_css_actives": ["classique"]
|
||||||
|
},
|
||||||
|
"animations_custom": {
|
||||||
|
"actives": []
|
||||||
},
|
},
|
||||||
"compteur": {
|
"compteur": {
|
||||||
"actif": true,
|
"actif": true,
|
||||||
|
|||||||
@@ -888,6 +888,66 @@ h3 {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-anim-video {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Overlay video plein ecran pendant capture */
|
||||||
|
.video-countdown-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0;
|
||||||
|
width: 100%; height: 100%;
|
||||||
|
background: #000;
|
||||||
|
z-index: 50;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-countdown-overlay video,
|
||||||
|
.video-countdown-overlay img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation item avec checkbox */
|
||||||
|
.anim-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.8rem;
|
||||||
|
padding: 0.6rem 1rem;
|
||||||
|
background: var(--fond);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.anim-item input[type="checkbox"] {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
accent-color: var(--primaire);
|
||||||
|
}
|
||||||
|
|
||||||
|
.anim-item-preview {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 6px;
|
||||||
|
object-fit: cover;
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.anim-item .btn-supprimer {
|
||||||
|
margin-left: auto;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--danger);
|
||||||
|
font-size: 1.2rem;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.anim-chiffre {
|
.anim-chiffre {
|
||||||
|
|||||||
@@ -257,16 +257,27 @@
|
|||||||
|
|
||||||
<!-- Panneau Animations -->
|
<!-- Panneau Animations -->
|
||||||
<div class="admin-panneau" id="panneau-animations-admin">
|
<div class="admin-panneau" id="panneau-animations-admin">
|
||||||
<h3>Animation du compte a rebours</h3>
|
<h3>Animations CSS</h3>
|
||||||
<p class="aide">Choisissez le style d'animation pour le 3... 2... 1...</p>
|
<p class="aide">Cochez les animations a utiliser. Si plusieurs sont cochees, une sera choisie au hasard a chaque photo.</p>
|
||||||
<div id="liste-animations" class="liste-animations">
|
<div id="liste-animations" class="liste-animations">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h3>Animations personnalisees (video/GIF)</h3>
|
||||||
|
<p class="aide">Importez vos propres animations (GIF, MP4, WebM). Elles seront jouees en plein ecran avant la capture.</p>
|
||||||
|
<div id="liste-animations-custom" class="liste-animations">
|
||||||
|
</div>
|
||||||
|
<div class="champ">
|
||||||
|
<input type="file" id="input-animation" accept=".gif,.mp4,.webm" class="input-fichier">
|
||||||
|
<button class="btn-action" onclick="uploaderAnimation()">Importer</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="animation-preview">
|
<div class="animation-preview">
|
||||||
<h4>Apercu</h4>
|
<h4>Apercu</h4>
|
||||||
<div id="preview-animation" class="preview-animation-box">
|
<div id="preview-animation" class="preview-animation-box">
|
||||||
<span id="preview-anim-chiffre" class="anim-chiffre">3</span>
|
<span id="preview-anim-chiffre" class="anim-chiffre">3</span>
|
||||||
|
<video id="preview-anim-video" class="preview-anim-video cache" muted playsinline></video>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn-secondaire" onclick="testerAnimation()">Tester</button>
|
<button class="btn-secondaire" onclick="testerAnimationPreview()">Tester</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -264,60 +264,181 @@ async function uploaderCadre() {
|
|||||||
|
|
||||||
// === ANIMATIONS ===
|
// === ANIMATIONS ===
|
||||||
|
|
||||||
let animationChoisie = 'classique';
|
const EMOJI_MAP = { 3: '🤪', 2: '😱', 1: '🔥' };
|
||||||
|
|
||||||
async function chargerAnimations() {
|
async function chargerAnimations() {
|
||||||
|
// Animations CSS internes
|
||||||
const anims = await apiGet('/api/animations');
|
const anims = await apiGet('/api/animations');
|
||||||
animationChoisie = config.camera?.animation_compte_a_rebours || 'classique';
|
const cssActives = config.camera?.animations_css_actives || ['classique'];
|
||||||
|
|
||||||
const liste = document.getElementById('liste-animations');
|
const liste = document.getElementById('liste-animations');
|
||||||
liste.innerHTML = '';
|
liste.innerHTML = '';
|
||||||
|
|
||||||
for (const [id, nom] of Object.entries(anims)) {
|
for (const [id, nom] of Object.entries(anims)) {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.className = 'anim-option' + (id === animationChoisie ? ' actif' : '');
|
div.className = 'anim-item';
|
||||||
div.textContent = nom;
|
|
||||||
div.onclick = () => choisirAnimation(id, div);
|
const cb = document.createElement('input');
|
||||||
|
cb.type = 'checkbox';
|
||||||
|
cb.checked = cssActives.includes(id);
|
||||||
|
cb.dataset.animId = id;
|
||||||
|
cb.onchange = () => majAnimationsCss();
|
||||||
|
|
||||||
|
const label = document.createElement('span');
|
||||||
|
label.textContent = nom;
|
||||||
|
|
||||||
|
div.appendChild(cb);
|
||||||
|
div.appendChild(label);
|
||||||
|
liste.appendChild(div);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animations custom (video/GIF)
|
||||||
|
await chargerAnimationsCustom();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function chargerAnimationsCustom() {
|
||||||
|
const data = await apiGet('/api/animations/custom');
|
||||||
|
const liste = document.getElementById('liste-animations-custom');
|
||||||
|
liste.innerHTML = '';
|
||||||
|
|
||||||
|
if (data.tous.length === 0) {
|
||||||
|
liste.innerHTML = '<p class="texte-secondaire">Aucune animation importee</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const nom of data.tous) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'anim-item';
|
||||||
|
|
||||||
|
const cb = document.createElement('input');
|
||||||
|
cb.type = 'checkbox';
|
||||||
|
cb.checked = data.actives.includes(nom);
|
||||||
|
cb.dataset.animNom = nom;
|
||||||
|
cb.onchange = () => majAnimationsCustom();
|
||||||
|
|
||||||
|
// Preview miniature
|
||||||
|
const ext = nom.split('.').pop().toLowerCase();
|
||||||
|
let preview;
|
||||||
|
if (ext === 'gif') {
|
||||||
|
preview = document.createElement('img');
|
||||||
|
preview.src = `/assets/animations/${nom}`;
|
||||||
|
preview.className = 'anim-item-preview';
|
||||||
|
} else {
|
||||||
|
preview = document.createElement('video');
|
||||||
|
preview.src = `/assets/animations/${nom}`;
|
||||||
|
preview.className = 'anim-item-preview';
|
||||||
|
preview.muted = true;
|
||||||
|
preview.preload = 'metadata';
|
||||||
|
}
|
||||||
|
|
||||||
|
const label = document.createElement('span');
|
||||||
|
label.textContent = nom;
|
||||||
|
|
||||||
|
const btnSuppr = document.createElement('button');
|
||||||
|
btnSuppr.className = 'btn-supprimer';
|
||||||
|
btnSuppr.innerHTML = '✕';
|
||||||
|
btnSuppr.onclick = () => supprimerAnimation(nom);
|
||||||
|
|
||||||
|
div.appendChild(cb);
|
||||||
|
div.appendChild(preview);
|
||||||
|
div.appendChild(label);
|
||||||
|
div.appendChild(btnSuppr);
|
||||||
liste.appendChild(div);
|
liste.appendChild(div);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function choisirAnimation(id, el) {
|
async function majAnimationsCss() {
|
||||||
animationChoisie = id;
|
const checkboxes = document.querySelectorAll('#liste-animations .anim-item input[type="checkbox"]');
|
||||||
document.querySelectorAll('.anim-option').forEach(e => e.classList.remove('actif'));
|
const actives = [];
|
||||||
el.classList.add('actif');
|
checkboxes.forEach(cb => { if (cb.checked) actives.push(cb.dataset.animId); });
|
||||||
|
// Au moins une doit rester active
|
||||||
await apiPost('/api/config', {
|
if (actives.length === 0) actives.push('classique');
|
||||||
camera: { animation_compte_a_rebours: id },
|
await apiPost('/api/config', { camera: { animations_css_actives: actives } });
|
||||||
});
|
|
||||||
|
|
||||||
testerAnimation();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const EMOJI_MAP = { 3: '🤪', 2: '😱', 1: '🔥' };
|
async function majAnimationsCustom() {
|
||||||
|
const checkboxes = document.querySelectorAll('#liste-animations-custom .anim-item input[type="checkbox"]');
|
||||||
|
const actives = [];
|
||||||
|
checkboxes.forEach(cb => { if (cb.checked) actives.push(cb.dataset.animNom); });
|
||||||
|
await apiPost('/api/animations/custom', { actives });
|
||||||
|
}
|
||||||
|
|
||||||
function testerAnimation() {
|
async function uploaderAnimation() {
|
||||||
|
const input = document.getElementById('input-animation');
|
||||||
|
if (!input.files.length) return;
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('fichier', input.files[0]);
|
||||||
|
|
||||||
|
await fetch('/api/upload/animation', { method: 'POST', body: formData });
|
||||||
|
input.value = '';
|
||||||
|
chargerAnimationsCustom();
|
||||||
|
afficherStatut('Animation importee', 'succes');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function supprimerAnimation(nom) {
|
||||||
|
if (!confirm(`Supprimer l'animation "${nom}" ?`)) return;
|
||||||
|
await fetch(`/api/animations/custom/${encodeURIComponent(nom)}`, { method: 'DELETE' });
|
||||||
|
chargerAnimationsCustom();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testerAnimationPreview() {
|
||||||
const chiffre = document.getElementById('preview-anim-chiffre');
|
const chiffre = document.getElementById('preview-anim-chiffre');
|
||||||
let count = 3;
|
const video = document.getElementById('preview-anim-video');
|
||||||
|
|
||||||
function afficher() {
|
// Verifier s'il y a des animations custom actives
|
||||||
if (animationChoisie === 'emoji') {
|
const customActives = [];
|
||||||
chiffre.textContent = EMOJI_MAP[count] || count;
|
document.querySelectorAll('#liste-animations-custom .anim-item input[type="checkbox"]:checked').forEach(cb => {
|
||||||
} else {
|
customActives.push(cb.dataset.animNom);
|
||||||
chiffre.textContent = count;
|
});
|
||||||
|
|
||||||
|
// Verifier les CSS actives
|
||||||
|
const cssActives = [];
|
||||||
|
document.querySelectorAll('#liste-animations .anim-item input[type="checkbox"]:checked').forEach(cb => {
|
||||||
|
cssActives.push(cb.dataset.animId);
|
||||||
|
});
|
||||||
|
|
||||||
|
const toutes = [...cssActives.map(a => ({type: 'css', id: a})), ...customActives.map(a => ({type: 'custom', nom: a}))];
|
||||||
|
if (toutes.length === 0) return;
|
||||||
|
|
||||||
|
const choix = toutes[Math.floor(Math.random() * toutes.length)];
|
||||||
|
|
||||||
|
if (choix.type === 'custom') {
|
||||||
|
// Jouer la video/gif
|
||||||
|
chiffre.classList.add('cache');
|
||||||
|
video.classList.remove('cache');
|
||||||
|
video.src = `/assets/animations/${choix.nom}`;
|
||||||
|
video.currentTime = 0;
|
||||||
|
video.play();
|
||||||
|
video.onended = () => {
|
||||||
|
video.classList.add('cache');
|
||||||
|
chiffre.classList.remove('cache');
|
||||||
|
};
|
||||||
|
// Pour les GIF, masquer apres 3s
|
||||||
|
if (choix.nom.endsWith('.gif')) {
|
||||||
|
setTimeout(() => {
|
||||||
|
video.classList.add('cache');
|
||||||
|
chiffre.classList.remove('cache');
|
||||||
|
}, 3000);
|
||||||
}
|
}
|
||||||
// Reset : retirer toute classe d'animation
|
} else {
|
||||||
chiffre.className = 'anim-chiffre';
|
// Animation CSS
|
||||||
// Force reflow pour que le navigateur enregistre le retrait
|
video.classList.add('cache');
|
||||||
void chiffre.offsetWidth;
|
chiffre.classList.remove('cache');
|
||||||
// Appliquer la nouvelle animation
|
let count = 3;
|
||||||
chiffre.classList.add('anim-' + animationChoisie);
|
function afficher() {
|
||||||
|
if (choix.id === 'emoji') {
|
||||||
count--;
|
chiffre.textContent = EMOJI_MAP[count] || count;
|
||||||
if (count > 0) setTimeout(afficher, 1000);
|
} else {
|
||||||
|
chiffre.textContent = count;
|
||||||
|
}
|
||||||
|
chiffre.className = 'anim-chiffre';
|
||||||
|
void chiffre.offsetWidth;
|
||||||
|
chiffre.classList.add('anim-' + choix.id);
|
||||||
|
count--;
|
||||||
|
if (count > 0) setTimeout(afficher, 1000);
|
||||||
|
}
|
||||||
|
afficher();
|
||||||
}
|
}
|
||||||
|
|
||||||
afficher();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// === FONCTIONNALITES ===
|
// === FONCTIONNALITES ===
|
||||||
|
|||||||
@@ -76,32 +76,83 @@ function getNombrePhotos() {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function choisirAnimationAleatoire() {
|
||||||
|
// Construire la liste de toutes les animations actives (CSS + custom)
|
||||||
|
const cssActives = config.camera?.animations_css_actives || ['classique'];
|
||||||
|
const customActives = config.animations_custom?.actives || [];
|
||||||
|
|
||||||
|
const pool = [
|
||||||
|
...cssActives.map(id => ({ type: 'css', id })),
|
||||||
|
...customActives.map(nom => ({ type: 'custom', nom })),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (pool.length === 0) return { type: 'css', id: 'classique' };
|
||||||
|
return pool[Math.floor(Math.random() * pool.length)];
|
||||||
|
}
|
||||||
|
|
||||||
async function compteARebours() {
|
async function compteARebours() {
|
||||||
const conteneur = document.getElementById('compte-a-rebours');
|
const conteneur = document.getElementById('compte-a-rebours');
|
||||||
const chiffre = document.getElementById('chiffre-car');
|
const chiffre = document.getElementById('chiffre-car');
|
||||||
const duree = config.camera?.compte_a_rebours || 3;
|
const duree = config.camera?.compte_a_rebours || 3;
|
||||||
const anim = config.camera?.animation_compte_a_rebours || 'classique';
|
|
||||||
|
|
||||||
conteneur.classList.remove('cache');
|
// Choisir une animation au hasard parmi les actives
|
||||||
|
const anim = choisirAnimationAleatoire();
|
||||||
|
|
||||||
for (let i = duree; i > 0; i--) {
|
if (anim.type === 'custom') {
|
||||||
// Contenu selon le type d'animation
|
// Jouer une video/gif en plein ecran
|
||||||
if (anim === 'emoji') {
|
await jouerAnimationCustom(anim.nom, duree);
|
||||||
chiffre.textContent = EMOJI_CAR[i] || i;
|
} else {
|
||||||
} else {
|
// Animation CSS classique
|
||||||
chiffre.textContent = i;
|
conteneur.classList.remove('cache');
|
||||||
|
|
||||||
|
for (let i = duree; i > 0; i--) {
|
||||||
|
if (anim.id === 'emoji') {
|
||||||
|
chiffre.textContent = EMOJI_CAR[i] || i;
|
||||||
|
} else {
|
||||||
|
chiffre.textContent = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
chiffre.className = 'anim-chiffre';
|
||||||
|
void chiffre.offsetWidth;
|
||||||
|
chiffre.classList.add('anim-' + anim.id);
|
||||||
|
|
||||||
|
await pause(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Appliquer l'animation
|
conteneur.classList.add('cache');
|
||||||
chiffre.className = 'anim-chiffre anim-' + anim;
|
}
|
||||||
chiffre.style.animation = 'none';
|
}
|
||||||
chiffre.offsetHeight;
|
|
||||||
chiffre.className = 'anim-chiffre anim-' + anim;
|
|
||||||
|
|
||||||
await pause(1000);
|
async function jouerAnimationCustom(nom, duree) {
|
||||||
|
// Creer un overlay plein ecran pour la video/gif
|
||||||
|
const overlay = document.createElement('div');
|
||||||
|
overlay.className = 'video-countdown-overlay';
|
||||||
|
|
||||||
|
const ext = nom.split('.').pop().toLowerCase();
|
||||||
|
|
||||||
|
if (ext === 'gif') {
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.src = `/assets/animations/${nom}`;
|
||||||
|
overlay.appendChild(img);
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
await pause(duree * 1000);
|
||||||
|
} else {
|
||||||
|
const video = document.createElement('video');
|
||||||
|
video.src = `/assets/animations/${nom}`;
|
||||||
|
video.muted = true;
|
||||||
|
video.playsInline = true;
|
||||||
|
video.autoplay = true;
|
||||||
|
overlay.appendChild(video);
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
|
||||||
|
// Attendre la fin de la video ou le timeout
|
||||||
|
await Promise.race([
|
||||||
|
new Promise(resolve => { video.onended = resolve; }),
|
||||||
|
pause(duree * 1000 + 2000),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
conteneur.classList.add('cache');
|
overlay.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
function afficherFlash() {
|
function afficherFlash() {
|
||||||
|
|||||||
Reference in New Issue
Block a user