>
Tutoriel 2026-04-05

Système de notifications pour FiveM

TDYSKY

TDYSKY

Fondateur et développeur principal chez Agency Scripts

Pourquoi créer un système de notification personnalisé ?

Les notifications FiveM par défaut sont fonctionnelles mais manquent de finition visuelle et de flexibilité. La plupart des serveurs de jeu de rôle s'appuient sur des fenêtres contextuelles de texte génériques qui semblent toutes identiques, ce qui rend difficile pour les joueurs de faire la distinction entre une erreur, une confirmation de réussite ou une alerte d'information. Un système de notification personnalisé construit avec NUI tu donne un contrôle total sur le style, les animations, le positionnement, les effets sonores et le comportement de la file d'attente. Il transforme un élément de base de l'interface utilisateur en quelque chose qui correspond à l'identité de marque de ton serveur et améliore considérablement l'expérience du joueur. Dans ce didacticiel, nous allons créer un système de notification toast complet à partir de zéro en utilisant Lua côté serveur et client, avec HTML, CSS et JavaScript alimentant l'interface NUI.

Configuration de la couche NUI

La base de tout système de notification personnalisé dans FiveM est la couche NUI (nouvelle interface utilisateur). NUI tu permet d'afficher du contenu HTML en superposition sur le jeu, ce qui signifie que tu disposes de toute la puissance des technologies Web modernes. Commencez par créer ton structure de ressources avec un fxmanifest.lua, un script Lua côté client et un html dossier contenant tes fichiers d'interface utilisateur. Le manifeste doit déclarer ton page NUI et enregistrer les gestionnaires de messages afin que tes scripts Lua puissent communiquer avec le frontend.

-- fxmanifest.lua
fx_version 'cerulean'
game 'gta5'

ui_page 'html/index.html'

files {
    'html/index.html',
    'html/style.css',
    'html/script.js',
    'html/sounds/*.ogg'
}

client_script 'client.lua'
server_script 'server.lua'

Ton html/index.html le fichier doit être minime. Il n'a besoin que d'un conteneur div où les notifications seront injectées dynamiquement par JavaScript. Gardez le HTML simple car chaque élément de notification est créé et détruit par programme. Liez ton feuille de style et ton script et assurez-tu que le corps a un arrière-plan transparent afin que le monde du jeu reste visible derrière tes notifications.

<!-- html/index.html -->
<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div id="notification-container"></div>
    <script src="script.js"></script>
</body>
</html>

Conception du CSS de notification Toast

Les notifications Toast doivent être visuellement distinctes par type. Définir des jeux de couleurs distincts pour succès, erreur, infos, et avertissement notifications. Positionnez le conteneur dans le coin supérieur droit de l'écran en utilisant un positionnement fixe et empilez les notifications verticalement avec un petit espace entre elles. Chaque toast doit avoir un subtil effet de morphisme de verre avec un flou de fond, une bordure gauche colorée pour indiquer le type et des animations d'entrée et de sortie fluides. Utilisez des images clés CSS pour le glissement depuis la droite et le fondu sortant lorsque la notification expire.

/* html/style.css */
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: transparent; font-family: 'Segoe UI', sans-serif; overflow: hidden; }

#notification-container {
    position: fixed;
    top: 20px;
    right: 20px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    z-index: 9999;
    max-width: 380px;
    width: 100%;
}

.toast {
    background: rgba(15, 15, 25, 0.85);
    backdrop-filter: blur(12px);
    border-radius: 10px;
    padding: 14px 18px;
    border-left: 4px solid #3b82f6;
    color: #e2e8f0;
    animation: slideIn 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
    display: flex;
    align-items: flex-start;
    gap: 12px;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}

.toast.success { border-left-color: #22c55e; }
.toast.error   { border-left-color: #ef4444; }
.toast.warning { border-left-color: #f59e0b; }
.toast.info    { border-left-color: #3b82f6; }

.toast-icon { font-size: 20px; flex-shrink: 0; margin-top: 2px; }
.toast-body  { flex: 1; }
.toast-title { font-weight: 700; font-size: 14px; margin-bottom: 4px; }
.toast-msg   { font-size: 13px; color: #94a3b8; line-height: 1.5; }

.toast-progress {
    position: absolute;
    bottom: 0; left: 0;
    height: 3px;
    background: currentColor;
    border-radius: 0 0 0 10px;
    animation: progress linear forwards;
}

@keyframes slideIn {
    from { opacity: 0; transform: translateX(100px); }
    to   { opacity: 1; transform: translateX(0); }
}

@keyframes slideOut {
    from { opacity: 1; transform: translateX(0); }
    to   { opacity: 0; transform: translateX(100px); }
}

@keyframes progress {
    from { width: 100%; }
    to   { width: 0%; }
}

Création de la file d'attente de notifications JavaScript

La couche JavaScript gère les messages entrants de Lua, crée des éléments DOM, gère la file d'attente et gère le rejet automatique. Une file d'attente de notifications est essentielle car tu ne souhaitez pas que dix notifications s'empilent simultanément à l'écran. Limitez le nombre de notifications visibles à un maximum de cinq et mettez en file d'attente toutes les notifications de débordement afin qu'elles apparaissent lorsque les précédentes expirent. Chaque notification doit avoir une durée configurable et cliquer sur une notification devrait la rejeter immédiatement. La barre de progression au bas de chaque toast donne aux joueurs un indicateur visuel de la durée pendant laquelle la notification restera à l'écran.

// html/script.js
const container = document.getElementById('notification-container');
const MAX_VISIBLE = 5;
const queue = [];
let activeCount = 0;

const icons = {
    success: '✔',
    error:   '✖',
    warning: '⚠',
    info:    'ℹ'
};

const sounds = {
    success: new Audio('sounds/success.ogg'),
    error:   new Audio('sounds/error.ogg'),
    warning: new Audio('sounds/warning.ogg'),
    info:    new Audio('sounds/info.ogg')
};

window.addEventListener('message', (event) => {
    if (event.data.type === 'showNotification') {
        addNotification(event.data);
    }
});

function addNotification(data) {
    if (activeCount >= MAX_VISIBLE) {
        queue.push(data);
        return;
    }
    createToast(data);
}

function createToast(data) {
    activeCount++;
    const duration = data.duration || 5000;
    const toast = document.createElement('div');
    toast.className = `toast ${data.style || 'info'}`;
    toast.style.position = 'relative';
    toast.innerHTML = `
        <div class="toast-icon">${icons[data.style] || icons.info}</div>
        <div class="toast-body">
            <div class="toast-title">${data.title || ''}</div>
            <div class="toast-msg">${data.message}</div>
        </div>
        <div class="toast-progress" style="animation-duration:${duration}ms"></div>
    `;

    toast.addEventListener('click', () => dismissToast(toast));
    container.appendChild(toast);

    if (data.sound !== false && sounds[data.style]) {
        sounds[data.style].currentTime = 0;
        sounds[data.style].play().catch(() => {});
    }

    setTimeout(() => dismissToast(toast), duration);
}

function dismissToast(toast) {
    if (toast.dataset.dismissed) return;
    toast.dataset.dismissed = 'true';
    toast.style.animation = 'slideOut 0.3s ease forwards';
    setTimeout(() => {
        toast.remove();
        activeCount--;
        if (queue.length > 0) {
            createToast(queue.shift());
        }
    }, 300);
}

Intégration Lua côté client

Côté client, tu as besoin d'une fonction qui envoie des messages NUI à ton couche HTML et d'une exportation afin que d'autres ressources puissent déclencher des notifications sans dépendre directement des éléments internes de ton script. Le SendNUIMessage native pousse les données vers le contexte du navigateur où ton JavaScript les récupère. Enveloppez-le dans une fonction API propre qui accepte un titre, un message, un type et une durée facultative. Enregistrez à la fois un événement client et une exportation afin que les notifications puissent être déclenchées à partir de scripts côté serveur et d'autres ressources client. Cette double approche garantit une compatibilité maximale avec n’importe quel framework.

-- client.lua
local function ShowNotification(title, message, style, duration, sound)
    SendNUIMessage({
        type   = 'showNotification',
        title  = title or '',
        message = message or '',
        style  = style or 'info',
        duration = duration or 5000,
        sound  = sound ~= false
    })
end

-- Export for other resources
exports('ShowNotification', ShowNotification)

-- Event-based trigger from server
RegisterNetEvent('notifications:show', function(title, message, style, duration)
    ShowNotification(title, message, style, duration)
end)

-- Convenience commands for testing
RegisterCommand('testnotify', function()
    ShowNotification('Success', 'Your item has been saved.', 'success', 4000)
    Wait(500)
    ShowNotification('Error', 'Insufficient funds for this purchase.', 'error', 5000)
    Wait(500)
    ShowNotification('Warning', 'Your vehicle is low on fuel.', 'warning', 4000)
    Wait(500)
    ShowNotification('Info', 'Press E to interact with the NPC.', 'info', 3000)
end, false)

Répartition des événements côté serveur

Le script côté serveur fournit des fonctions d'assistance pour envoyer des notifications à des joueurs spécifiques, à tous les joueurs ou à des groupes de joueurs. C'est ici que tu géres des cas d'utilisation tels que la diffusion d'annonces, l'envoi de confirmations de transaction ou l'alerte des administrateurs en cas d'activité suspecte. En centralisant la logique de répartition sur le serveur, tu conserves un point de contrôle unique sur la livraison des notifications. Tu peux également ajouter une limitation de débit ici pour empêcher le spam de notification provenant de scripts clients malveillants ou bogués. Le serveur doit valider les paramètres de notification avant le transfert pour empêcher l'injection de NUI via des messages contrefaits.

-- server.lua
local function NotifyPlayer(source, title, message, style, duration)
    if not source or source <= 0 then return end
    title   = tostring(title or '')
    message = tostring(message or '')
    style   = style or 'info'
    TriggerClientEvent('notifications:show', source, title, message, style, duration)
end

local function NotifyAll(title, message, style, duration)
    TriggerClientEvent('notifications:show', -1, title, message, style, duration)
end

exports('NotifyPlayer', NotifyPlayer)
exports('NotifyAll', NotifyAll)

-- Example: welcome notification
AddEventHandler('playerJoining', function()
    local src = source
    Wait(3000)
    NotifyPlayer(src, 'Welcome', 'Welcome to the server! Have fun.', 'success', 6000)
end)

-- Admin broadcast command
RegisterCommand('broadcast', function(source, args)
    if source > 0 and not IsPlayerAceAllowed(source, 'command.broadcast') then return end
    local msg = table.concat(args, ' ')
    NotifyAll('Announcement', msg, 'info', 8000)
end, true)

Ajout d'effets sonores et de polissage

Les effets sonores élèvent les notifications d'un élément uniquement visuel à un mécanisme de rétroaction multisensoriel. Les joueurs ont souvent le jeu en arrière-plan ou sont concentrés sur la conduite et peuvent manquer un toast silencieux. Un carillon court et subtil pour les notifications de réussite, un faible bourdonnement pour les erreurs et un léger ping pour les alertes d'informations garantissent que les joueurs enregistrent les informations importantes même lorsqu'ils ne regardent pas la zone de notification. Gardez tes fichiers audio courts, inférieurs à 500 millisecondes, et utilisez le format OGG pour la compatibilité du navigateur. Réglez le volume à environ 30 % pour qu'il se fonde dans le son du jeu plutôt que de le surcharger. Stockez les fichiers son dans ton html/sounds répertoire et préchargez-les dans JavaScript pour éviter les retards de lecture dès la première notification.

Fonctionnalités avancées et personnalisation

Une fois que le système principal fonctionne, envisagez d’ajouter des fonctionnalités avancées pour différencier ton serveur. Les notifications persistantes qui restent à l'écran jusqu'à ce que le joueur clique explicitement dessus sont utiles pour les alertes importantes telles que les appels téléphoniques en attente ou les minuteries de prison. Les boutons d'action dans les notifications permettent aux joueurs de répondre directement, par exemple en acceptant une demande d'échange sans ouvrir un menu séparé. Tu peux également implémenter un regroupement de notifications dans lequel des notifications identiques répétées se résument en un seul toast avec un badge de compteur indiquant le nombre d'occurrences. Pour l'intégration du framework, créez des fichiers pont qui remplacent les fonctions de notification par défaut dans QBCore ou ESX afin que chaque ressource de ton serveur utilise automatiquement ton système personnalisé sans aucune modification de code. Ce modèle de remplacement des valeurs par défaut du framework par ton propre implémentation est un moyen simple de mettre à niveau l'interface utilisateur entière d'un serveur en une seule étape.

Partager cet article

Prêt à améliorer votre serveur ?

Découvrez nos scripts FiveM premium dans la boutique Agency Scripts ou rejoignez notre communauté Discord pour le support et les mises à jour.