Étude de cas UX d'une page de présentation

Comment j'ai conçu la page de vue d'ensemble pour toutes les post-catégories

Objectifs UI et UX

Mon objectif principal avec une page de vue d'ensemble qui montre toutes les catégories pour les articles publiés était d'augmenter la découvrabilité des catégories qui sont réellement disponibles. J'ai également vu ce changement comme une opportunité d'améliorer la beauté visuelle de la page ainsi que d'aligner l'UX avec les autres pages de cette application Web progressive.

La capture d'écran suivante montre à quoi ressemblait la page avant la refonte.

Image e8edca7d7ea0

Qu'est-ce qui a dû changer

L'ancienne implémentation présentait deux problèmes majeurs. Premièrement, la mise en page était en fait invalide, car les catégories avec moins de 5 éléments redimensionnaient les images de couverture post-aperçu disponibles pour s'adapter à toute la largeur. Deuxièmement, il n'y avait pas de barre d'action principale visible en haut de la page - il devrait certainement y en avoir une, car elle fournit les principaux éléments de navigation à l'utilisateur.

De plus, il devient évident que la page de présentation des catégories n'a pas fait un excellent travail pour les rendre visibles. Au lieu de cela, l'utilisateur a principalement vu des images de couverture pour les publications liées à chaque catégorie. Ce n'est qu'en y repensant que la ligne avec une icône ainsi que le titre de chaque catégorie est reconnue. En plus d'être esthétiquement désagréable, cette interface utilisateur était tout simplement mal conçue.

Corriger les erreurs

Comme il s'agit de mon application Web personnelle, c'est aussi moi qui ai tout gâché au départ sur la page d'aperçu des catégories. Sans plus d'excuses, je me suis donc assis pour retravailler toute la page.

La nouvelle version affiche une seule colonne de lignes, où chaque élément affiche une grande image d'aperçu pour une catégorie. L'image personnalisée prend toute la largeur autorisée. Centré sur les deux axes se trouve le titre de la catégorie au milieu, ce qui le rend immédiatement visible et reconnaissable. Pour améliorer la lisibilité, j'ai ajouté un dégradé subtil du noir au transparent sous le texte.

En survolant l'image et le texte, un texte d'information supplémentaire devient visible, indiquant le nombre total de publications pour cette catégorie - une information qui n'était pas disponible auparavant.

Pour résoudre le problème de navigation, j'ai également réutilisé la barre d'action existante avec toutes les routes principales disponibles pour l'utilisateur.

Image 0616bdd11b98

Amélioration par itération

Pour offrir une expérience utilisateur plus agréable, j'ai également ajouté une subtile animation de mise à l'échelle à l'image lors du survol d'une ligne de catégorie. Au fur et à mesure que l'image évolue dans ses limites d'origine, la vue ne s'agrandit pas inutilement.

De plus, une nouvelle ligne sous l'image de la catégorie devient visible grâce à l'animation en survolant celle-ci. Cette ligne affiche une sélection des derniers messages pour la catégorie. Chaque post-preview est un lien vers le post. Tous les autres éléments interactifs de la ligne de catégorie ouvrent la page de détail de la catégorie.

Pour donner l'illusion de plus de contenu qu'il n'y en a en réalité, j'ai utilisé la classe "pile" de Daisy-UIs pour donner à chaque post-prévisualisation une pile truquée pour donner une impression de plus d'éléments de prévisualisation sous les éléments actuels.

Image 41c052a8c928

Sur les petits appareils, aucun aperçu n'est rendu. Un utilisateur ne peut cliquer que sur la grande image de catégorie pour accéder à la page de détail, où tous les messages sont répertoriés.

Bilan et perspectives

Comme vous pouvez le voir, même une simple page qui ne doit fournir qu'un aperçu des éléments peut être une source d'erreurs visuelles et conceptuelles ainsi que du matériel d'apprentissage sur la façon de concevoir une bonne interface utilisateur ainsi qu'une UX.

Je n'ai ajouté aucune recherche de texte locale pour que les utilisateurs filtrent par saisie de texte. Pour l'instant, j'ai l'intention de favoriser l'exploration sur la page en faisant défiler ainsi qu'en survolant. Si cela devient un problème à l'avenir (par exemple parce que les utilisateurs ne trouvent pas le contenu qu'ils recherchent), j'ajouterai des options de filtrage. Cela pourrait également devenir nécessaire car le nombre de messages ainsi que les catégories ne cessent de croître. Bien sûr, je vous invite à essayer la page par vous-même.

Le code source

Le texte suivant contient le code complet et non modifié pour le conteneur de pages ainsi que les groupes de catégories. Tout est écrit en Typescript avec React.js et Tailwind.css. Si vous pensez pouvoir réutiliser certaines pièces, je suis heureux de pouvoir vous aider.

//
// Page container.
//

import React from "react";
import { CMSCategoryResolution } from "../../cms/entities/cms.entity.catgory";
import Reveal from "../Reveal/Reveal";
import { BogPostCategoryPostsGroup } from "./BlogPostCategoryPostsGroup";
import BlogPostCategoriesOverviewHero from "./BlogPostCategoriesOverviewHero";
import PrimaryActionBar from "../PrimaryActionsBar/PrimaryActionsBar";

/*
 *
 * Interfaces.
 *
 */

interface Props {
  categories: CMSCategoryResolution[];
}

/*
 *
 * Components.
 *
 */

export default function BlogPostCategoriesOverview(props: Props) {
  const { categories } = props;

  return (
    <div className="max-w-6xl mx-auto">
      <BlogPostCategoriesOverviewHero />
      <div className="mt-10 mb-20">
        <PrimaryActionBar isCentered color="white" />
      </div>
      <div className="px-6 mx-auto space-y-6 lg:px-0 md:space-y-16 lg:space-y-20">
        {categories.map((c, i) => (
          <Reveal key={i}>
            <BogPostCategoryPostsGroup category={c} maxPostsEachThreshold={20} />
          </Reveal>
        ))}
      </div>
    </div>
  );
}

//
// BogPostCategoryPostsGroup.
//

import React from "react";
import { CMSCategoryResolution } from "../../cms/entities/cms.entity.catgory";
import { FLPath } from "../../models/route/model.route";
import { CMSCategoryIconFactory } from "../CMS/CMSCategoryIcon";
import Link from "next/link";
import RiMore from "remixicon-react/MoreLineIcon";

/*
 *
 * Interfaces.
 *
 */

interface Props {
  category: CMSCategoryResolution;
  maxPostsEachThreshold: number;
}

/*
 *
 * Components.
 *
 */

export const BogPostCategoryPostsGroup = (props: Props) => {
  const { category, maxPostsEachThreshold } = props;
  const { posts } = category;

  if (posts.length === 0) {
    return null;
  }

  const url = `${FLPath.Categories}/${category.slug.current}`;
  const Icon = CMSCategoryIconFactory({ variant: category.slug.current });

  return (
    <div className="flex flex-col items-center group">
      <div className="relative max-w-4xl overflow-visible rounded-md">
        <div className="absolute items-end justify-start hidden w-full h-full px-6 space-x-4 overflow-x-scroll duration-500 ease-in-out translate-y-12 opacity-0 md:flex pb-14 group-hover:opacity-100 group-hover:translate-y-20">
          {posts.slice(0, 4).map((p, i) => (
            <span className="stack">
              <Link
                href={`${FLPath.Posts}/${p.slug.current}`}
                aria-label={p.title}
                prefetch={false}>
                <a className="duration-200 ease-out hover:scale-105">
                  <img
                    src={p.imageUrl.jpg}
                    alt={p.title}
                    loading="lazy"
                    className="object-contain h-20 rounded "
                  />
                </a>
              </Link>

              {Array.from({ length: 2 }, (_, i) => (
                <span key={i} style={{ width: 152, height: 80 }} className="bg-gray-800 rounded" />
              ))}
            </span>
          ))}
          {posts.length >= 5 && (
            <Link passHref href={url} aria-label={`Link to ${category.title}`} prefetch={false}>
              <a className="flex flex-col items-center justify-center h-20 w-14">
                <RiMore />
              </a>
            </Link>
          )}
        </div>

        <div className="relative flex flex-col items-center overflow-hidden duration-500 ease-in-out rounded-md shadow-xl md:group-hover:-translate-y-20">
          <img
            className="object-cover w-full duration-500 ease-in-out group-hover:scale-105"
            width={960}
            height={200}
            src={category.imageUrl.webp}
            alt={category.title}
            loading="lazy"
          />
          <Link passHref href={url} aria-label={`Link to ${category.title}`} prefetch={false}>
            <a className="absolute flex flex-col items-center justify-center w-full h-full bg-gradient-to-t from-primary">
              <span className="flex items-center space-x-2 duration-300 ease-in-out translate-y-2 group-hover:-translate-y-3">
                <Icon size="2em" />
                <h2 className="text-sm md:text-xl">{category.title}</h2>
              </span>
              <span className="text-sm font-light text-gray-200 transition duration-500 ease-in-out translate-y-2 opacity-0 group-hover:opacity-100 group-hover:-translate-y-2">
                {category.posts.length}
                {category.posts.length === maxPostsEachThreshold ? "+" : ""}{" "}
                {category.posts.length === 1 ? "post" : "posts"}
              </span>
            </a>
          </Link>
        </div>
      </div>
    </div>
  );
};