Maison > interface Web > js tutoriel > L'art du Smooth UX : Anti-rebond et Throttling pour une UI plus performante

L'art du Smooth UX : Anti-rebond et Throttling pour une UI plus performante

DDD
Libérer: 2025-01-01 02:05:09
original
897 Les gens l'ont consulté

Référentiel de codes Github

Dans un monde où tout va très vite, la plupart des travaux que nous effectuons se font sur le Web, et rapidement. Créer une expérience utilisateur transparente et fluide devient d’autant plus important. Les consommateurs adorent une interface utilisateur qui fonctionne rapidement, sans décalage ni retard. Réaliser une expérience presque parfaite est possible, bien que délicat. Avez-vous entendu parler des boucles d'événements ?

En JavaScript, la boucle d'événements est un concept fondamental qui gère l'ordre d'exécution du code, collecte les processus, place les instructions dans des sous-tâches en file d'attente et exécute efficacement les opérations asynchrones. Voici un aperçu rapide du fonctionnement d'une boucle d'événements :

  • Pile d'appel : toutes les fonctions, lorsqu'elles sont invoquées, sont ajoutées à cette pile, et le flux de contrôle revient de la fonction, il est sorti de la pile
  • Tas : toutes les variables et objets se voient allouer de la mémoire à partir de ce tas
  • File d'attente : une liste de messages/instructions - qui sont exécutés les uns après les autres

Cette boucle d'événements vérifie en permanence la pile d'appels. L'exécution d'un code JavaScript se poursuit jusqu'à ce que la pile d'appels soit vide.

La gestion des événements est une partie très cruciale de la création d'applications JavaScript. Dans une telle application, nous devrons peut-être associer plusieurs événements à un composant d'interface utilisateur.

The art of Smooth UX : Debouncing and Throttling for a more performant UI

Imaginez...

Vous avez un bouton dans une interface utilisateur qui permet de remplir un tableau avec les dernières nouvelles du sport. Maintenant, cela vous oblige à :

  • Cliquez sur un bouton (associez le gestionnaire d'événements "clic" à un bouton.
  • Récupérer les résultats d'une API
  • Analyser la sortie (Json) et afficher

Ces 3 processus s'enchaînent de manière synchrone. Désormais, appuyer à plusieurs reprises sur le bouton signifierait plusieurs appels d'API - ce qui entraînerait le blocage de l'interface utilisateur pendant quelques secondes - une expérience utilisateur apparemment lente.

Il s'agit d'un bon cas d'utilisation pour des approches telles que l'anti-rebond et la limitation. Pour des événements comme celui-ci, qui déclenchent une chaîne d'événements complexes - nous pouvons utiliser de telles manœuvres pour limiter le nombre de fois que nous appelons l'API, ou dans un sens général - limiter la vitesse à laquelle nous traitons un événement.

Qu'est-ce que l'anti-rebond par rapport à la limitation ?

Anti-rebond : Différer l'exécution d'une fonction jusqu'à ce qu'une période de refroidissement spécifiée se soit écoulée depuis le dernier événement.

Par exemple :

Si nous rebondissons handleOnPressKey() pendant 2 secondes, il ne s'exécutera que si l'utilisateur arrête d'appuyer sur les touches pendant 2 secondes.

Scénario:

  • Appui initial sur la touche : Démarrez un minuteur de 2 000 ms pour appeler handleOnPressKey().
  • Appui ultérieur sur une touche dans les 1000 ms : La minuterie est réinitialisée ; nous attendons encore 2000 ms après cette dernière pression sur une touche.
  • Aucune pression sur une touche pendant 2000 ms : Le minuteur se termine et handleOnPressKey() est appelé.

Extrait de code :

let debounceTimer; // Timer reference

const handleOnPressKey = () => {
    console.log("Key pressed and debounce period elapsed!");
};

const debouncedKeyPress = () => {
    // Clear any existing timer
    clearTimeout(debounceTimer);

    // Start a new debounce timer
    debounceTimer = setTimeout(() => {
        handleOnPressKey(); // Execute the function after cooldown
    }, 2000); // Cooldown period of 2000ms
};

// Attach debouncedKeyPress to keypress events
document.getElementById("input").addEventListener("keypress", debouncedKeyPress);
Copier après la connexion
Copier après la connexion

Limitation : Garantir qu'une fonction est appelée au plus une fois au cours d'une période de temps spécifiée, quelle que soit la fréquence à laquelle l'événement se produit.

Par exemple :

Si nous limitons handleOnScroll() avec un intervalle de 2 secondes, la fonction s'exécutera au plus une fois toutes les 2 secondes, même si l'événement de défilement se déclenche plusieurs fois au cours de cette période.

Scénario:

  • Événement de défilement initial : handleOnScroll() est appelé et un temps de recharge de 2 000 ms démarre.
  • Événements de défilement ultérieurs dans les 2 000 ms : Ceux-ci sont ignorés car la période de recharge est active.
  • Événement de défilement après 2000 ms : handleOnScroll() est à nouveau appelé.

Exemple de code :

let throttleTimer; // Timer reference

const handleOnScroll = () => {
    console.log("Scroll event processed!");
};

const throttledScroll = () => {
    if (!throttleTimer) {
        handleOnScroll(); // Execute the function immediately
        throttleTimer = setTimeout(() => {
            throttleTimer = null; // Reset timer after cooldown
        }, 2000); // Cooldown period of 2000ms
    }
};

// Attach throttledScroll to scroll events
document.addEventListener("scroll", throttledScroll);
Copier après la connexion
Copier après la connexion

Maintenant, construisons quelque chose

Ce projet est une application moderne de To-Do List conçue pour explorer les concepts de anti-rebond et de limitation dans la gestion des événements. Il propose l'ajout de tâches en temps réel, une fonctionnalité de recherche optimisée par Fuse.js et une liste déroulante pour les suggestions dynamiques.

The art of Smooth UX : Debouncing and Throttling for a more performant UI

Jetons rapidement un coup d'œil au code HTML avant de passer au script.js, plus critique

Nous avons utilisé TailwindCSS pour un style rapide. Vous pouvez consulter leur documentation ici Tailwind Documentation - c'est extrêmement utile pour créer des prototypes rapides

  • En-tête : L'en-tête contient le titre de la page.
  • Champ de saisie : un champ de saisie pour ajouter des notes, stylisé avec Tailwind CSS.
  • Liste déroulante des suggestions  : une liste déroulante masquée qui affichera les suggestions au fur et à mesure que l'utilisateur les tape.
  • Liste des tâches statiques : Une liste pour afficher les tâches ajoutées.
  • Scripts : inclut la bibliothèque Fuse.js pour la recherche floue et le fichier script.js pour la logique JavaScript personnalisée.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Event Loop Practice</title>
    <!-- Tailwind CSS CDN -->
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
    <style>
        /* Tailwind Extensions (Optional for Customizations) */
        body {
            font-family: 'Inter', sans-serif;
        }
    </style>
</head>
<body>



<h3>
  
  
  Why Use Fuse.js?
</h3>

<p>Fuse.js is a lightweight, customizable library for fuzzy searching. It handles typos and partial matches, offers high performance for large datasets, and has an intuitive API. This will help enhance your search functionality with flexible, user-friendly search experiences. Additionally, this provides you with a CDN link, so it can work right of the bat, no imports or local storage required.</p>

<h2>Now let's Code in the Real Deal - The JS</h2>

<h4>
  
  
  1. Task Array and Variables
</h4>



<pre class="brush:php;toolbar:false">const tasks = new Array (
    "Complete Blog on Throttling + Debouncing",
    "Make a list of 2025 Resolutions",
);
let fuse = undefined;
let debounceTimer;
let throttleTimer;
Copier après la connexion
Copier après la connexion

Cette section initialise un tableau de tâches et déclare des variables pour Fuse.js, le minuteur anti-rebond et le minuteur d'accélérateur. Nous avons déjà codé en dur certaines tâches - pour le bien de ce projet

Construisons maintenant la fonction onSubmit. Cette fonction sera déclenchée une fois que l'utilisateur cliquera sur la flèche de soumission. Il empêche la soumission du formulaire par défaut, récupère la valeur d'entrée, efface le champ de saisie, ajoute la nouvelle tâche au tableau des tâches et met à jour la liste des tâches.

let debounceTimer; // Timer reference

const handleOnPressKey = () => {
    console.log("Key pressed and debounce period elapsed!");
};

const debouncedKeyPress = () => {
    // Clear any existing timer
    clearTimeout(debounceTimer);

    // Start a new debounce timer
    debounceTimer = setTimeout(() => {
        handleOnPressKey(); // Execute the function after cooldown
    }, 2000); // Cooldown period of 2000ms
};

// Attach debouncedKeyPress to keypress events
document.getElementById("input").addEventListener("keypress", debouncedKeyPress);
Copier après la connexion
Copier après la connexion

Nous devons maintenant nous assurer qu'une fois qu'une tâche a été soumise, elle est mise à jour dans la liste des tâches

let throttleTimer; // Timer reference

const handleOnScroll = () => {
    console.log("Scroll event processed!");
};

const throttledScroll = () => {
    if (!throttleTimer) {
        handleOnScroll(); // Execute the function immediately
        throttleTimer = setTimeout(() => {
            throttleTimer = null; // Reset timer after cooldown
        }, 2000); // Cooldown period of 2000ms
    }
};

// Attach throttledScroll to scroll events
document.addEventListener("scroll", throttledScroll);
Copier après la connexion
Copier après la connexion

La fonction updateList() restitue la liste des tâches en parcourant le tableau des tâches et en créant des éléments de liste pour chaque tâche. Chaque élément de la liste comprend une puce et le texte de la tâche.

Nous devons maintenant nous assurer que la liste est mise à jour après le chargement de la page, la première fois. Nous voudrions également initialiser Fuse.js lors du chargement de la page - et y associer le tableau de tâches. N'oubliez pas que nous souhaitons afficher les suggestions de ce tableau de tâches dans la liste déroulante.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Event Loop Practice</title>
    <!-- Tailwind CSS CDN -->
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
    <style>
        /* Tailwind Extensions (Optional for Customizations) */
        body {
            font-family: 'Inter', sans-serif;
        }
    </style>
</head>
<body>



<h3>
  
  
  Why Use Fuse.js?
</h3>

<p>Fuse.js is a lightweight, customizable library for fuzzy searching. It handles typos and partial matches, offers high performance for large datasets, and has an intuitive API. This will help enhance your search functionality with flexible, user-friendly search experiences. Additionally, this provides you with a CDN link, so it can work right of the bat, no imports or local storage required.</p>

<h2>Now let's Code in the Real Deal - The JS</h2>

<h4>
  
  
  1. Task Array and Variables
</h4>



<pre class="brush:php;toolbar:false">const tasks = new Array (
    "Complete Blog on Throttling + Debouncing",
    "Make a list of 2025 Resolutions",
);
let fuse = undefined;
let debounceTimer;
let throttleTimer;
Copier après la connexion
Copier après la connexion
const onSubmit = (event) => {
    //Prevent default
    event.preventDefault();

    const text = document.getElementById("input").value.trim();
    document.getElementById("input").value = "";
    tasks.push(text);
    updateList();
}
Copier après la connexion

Maintenant, nous devons nous assurer que sur chaque « entrée », nous recherchons dans la liste pour afficher les suggestions dans la liste déroulante. Celui-ci comporte 3 parties :

  • Écrivez la logique de recherche : searchTasks()
  • Remplissez la liste déroulante sur chaque entrée : updateDropdown()
  • Associez le updateDropdown() à appeler à chaque entrée (enfin du moins pour l'instant :-) -> Jusqu'à ce que nous implémentions la logique anti-rebond/limitation)
const updateList = () => {
    const lists = document.getElementById("taskList");
    lists.innerHTML = "";

    //Loop through all elements in tasks
    tasks.forEach(task => {
        const taskElement = document.createElement("li");
        taskElement.classList.add("flex", "items-center", "space-x-2");

        //Add Bullet Point Element
        const bullet = document.createElement("span");
        bullet.classList.add("h-2", "w-2", "bg-blue-500", "rounded-full");

        //Add Span Tag
        const taskText = document.createElement("span");
        taskText.textContent = task;

        taskElement.appendChild(bullet);
        taskElement.appendChild(taskText);
        lists.appendChild(taskElement);
    })
}
Copier après la connexion
const init = () => {
    console.log("Initializing...");
    //Update and render the list
    updateList();

    //Initialize Fuse with the updated array
    try{
        fuse = new Fuse(tasks, {
            includeScore: true,
            threshold: 0.3 //For sensitivity
        })
    } catch(e) {
        console.log("Error initializing Fuse:"+ fuse);
    }
}
Copier après la connexion
document.addEventListener("DOMContentLoaded", init);
Copier après la connexion

Jusqu'à présent : la liste déroulante sera mise à jour à chaque fois que vous tapez quelque chose - dans une interface utilisateur plus volumineuse, nous ne voudrions pas de cette expérience

La mise à jour de la liste déroulante à chaque frappe dans une interface utilisateur volumineuse peut entraîner des problèmes de performances, entraînant un décalage et une mauvaise expérience utilisateur. Des mises à jour fréquentes peuvent submerger la boucle d'événements, entraînant des retards dans le traitement d'autres tâches.

Nous allons maintenant voir comment utiliser l'anti-rebond OU la limitation pour aider à gérer la fréquence des mises à jour, garantissant des performances plus fluides et une interface plus réactive.

Voici comment nous pouvons mettre en œuvre l'une ou l'autre des techniques dans notre projet de prise de notes.

Anti-rebond :

Debouncing garantit qu'une fonction n'est appelée qu'après un laps de temps spécifié depuis le dernier appel. Ceci est utile pour des scénarios tels que les champs de saisie de recherche, dans lesquels nous souhaitons attendre que l'utilisateur ait fini de taper avant de passer un appel API.

Extrait de code :

//Utility function to search within already entered values
const searchTasks = (query) => {
    const result = fuse.search(query);
    const filteredTasks = result.map(result => result.item)
    updateDropdown(filteredTasks);
}
Copier après la connexion

Explication :

  • L'écouteur d'événement d'entrée est attaché au champ de saisie.
  • La fonction clearTimeout efface tout minuteur anti-rebond existant.
  • La fonction setTimeout définit une nouvelle minuterie anti-rebond pendant 1 seconde. Si aucune entrée n'est détectée dans ce délai, la fonction searchTasks est appelée avec la valeur d'entrée.

Limitation (dans le même cas d'utilisation) - Utilisez l'une des deux approches

const updateDropdown = (tasks) => {
    const dropdown = document.getElementById("dropdown");
    dropdown.innerHTML = "";

    if(tasks.length === 0) {
        dropdown.style.display = "none";
        return;
    }

    tasks.forEach(task => {
        const listItem = document.createElement("li");
        listItem.textContent = task;
        listItem.addEventListener("click", () => {
            document.getElementById("input").value = task;
            dropdown.style.display = "none";
        })
        dropdown.appendChild(listItem);
    });

    dropdown.style.display = "block";
}
Copier après la connexion

Explication :

  • let lastCall = 0; : initialise une variable pour suivre la dernière fois que searchTasks a été appelé.
  • document.getElementById("input").addEventListener("input", (event) => { ... }); : attache un écouteur d'événement d'entrée au champ de saisie.
  • const now = Date.now(); : obtient l'heure actuelle en millisecondes.
  • const delay = 1000 ; : Règle le délai d'accélération à 1 seconde.
  • if (now - lastCall >= delay) { ... } : Vérifie si suffisamment de temps s'est écoulé depuis le dernier appel.
    • const query = event.target.value.trim(); : récupère la valeur d'entrée découpée.
    • searchTasks(query); : appelle la fonction searchTasks avec la valeur d'entrée.
    • lastCall = now ; : met à jour l’heure de lastCall à l’heure actuelle.

Cependant, veuillez noter : la limitation n'est pas la meilleure solution pour ce scénario car elle limite la fréquence d'exécution des fonctions à un intervalle fixe, ce qui pourrait ne pas offrir la meilleure expérience utilisateur pour les suggestions de recherche en temps réel. Les utilisateurs s'attendent à un retour immédiat au fur et à mesure qu'ils tapent, et la limitation peut introduire des retards notables.

Meilleurs cas d'utilisation pour la limitation

La limitation est plus adaptée aux scénarios dans lesquels vous souhaitez contrôler le taux de gestion des événements pour éviter les problèmes de performances. Voici quelques exemples :

  • Redimensionnement de la fenêtre : lorsqu'un utilisateur redimensionne la fenêtre du navigateur, vous souhaiterez peut-être mettre à jour la mise en page ou effectuer des calculs. La limitation garantit que ces mises à jour se produisent à un rythme contrôlé, évitant ainsi les appels de fonctions excessifs.
  • Défilement : lors de la gestion des événements de défilement, tels que le chargement de plus de contenu ou la mise à jour de l'interface utilisateur en fonction de la position de défilement, la limitation permet de gérer la fréquence des mises à jour, garantissant ainsi des performances fluides.
  • Limitation du débit API : lorsque vous effectuez des appels API, la limitation peut vous aider à rester dans les limites de débit en contrôlant la fréquence des requêtes.

En utilisant la limitation dans ces scénarios, vous pouvez améliorer les performances et garantir une expérience utilisateur plus fluide.

Trouvez le code complet ici

Bon codage !


Veuillez laisser un commentaire !

J'espère que vous avez trouvé ce blog utile ! Vos commentaires sont inestimables pour moi, alors n'hésitez pas à laisser vos réflexions et suggestions dans les commentaires ci-dessous.

N'hésitez pas à me contacter sur LinkedIn pour plus d'informations et de mises à jour. Restons connectés et continuons à apprendre et à grandir ensemble !

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal