Caso di studio UX di una pagina panoramica

Come ho progettato la pagina panoramica per tutte le post-categorie

Obiettivi dell'interfaccia utente e dell'esperienza utente

Il mio obiettivo principale con una pagina panoramica che mostra tutte le categorie per i post pubblicati era aumentare la rilevabilità delle categorie effettivamente disponibili. Ho anche visto questo cambiamento come un'opportunità per migliorare la bellezza visiva della pagina e per allineare l'UX con le altre pagine di questa web app progressiva.

Lo screenshot seguente mostra l'aspetto della pagina prima della riprogettazione.

Image e8edca7d7ea0

Cosa doveva cambiare

La vecchia implementazione aveva due problemi principali. Innanzitutto, il layout in realtà non era valido, poiché le categorie con meno di 5 elementi ridimensionavano le immagini di copertina post-anteprima disponibili per adattarle all'intera larghezza. In secondo luogo, non c'era alcuna barra delle azioni principale visibile nella parte superiore della pagina: dovrebbe essercene una renderizzata, in quanto fornisce all'utente gli elementi di navigazione principali.

Inoltre, diventa evidente che la pagina di panoramica per le categorie in realtà non ha fatto un ottimo lavoro nel renderle visibili. Invece, l'utente ha visto principalmente le immagini di copertina per i post relativi a ciascuna categoria. Solo a una seconda occhiata viene riconosciuta la riga con l'icona e il titolo di ogni categoria. Oltre ad essere esteticamente non gradevole, questa interfaccia utente è stata semplicemente mal progettata.

Correggere gli errori

Poiché questa è la mia app Web personale, sono stato anche quello che inizialmente ha incasinato le cose nella pagina di panoramica delle categorie. Senza più scuse, mi sono quindi seduto a rielaborare l'intera pagina.

La nuova versione mostra una singola colonna di righe, in cui ogni elemento mostra una grande immagine di anteprima per una categoria. L'immagine personalizzata occupa l'intera larghezza consentita. Al centro su entrambi gli assi c'è il titolo della categoria al centro, che lo rende immediatamente visibile e riconoscibile. Per migliorare la leggibilità, ho aggiunto una sfumatura sottile dal nero al trasparente sotto il testo.

Passando il mouse sopra l'immagine e il testo, diventa visibile un testo informativo aggiuntivo, che mostra il numero totale di post per questa categoria, un'informazione che in precedenza non era disponibile.

Per risolvere il problema di navigazione, ho anche riutilizzato la barra delle azioni esistente con tutti i percorsi principali disponibili per l'utente.

Image 0616bdd11b98

Migliorare attraverso l'iterazione

Per fornire un'esperienza utente più piacevole, ho anche aggiunto una sottile animazione di ridimensionamento all'immagine quando si passa con il mouse su una riga di categoria. Poiché l'immagine si ridimensiona all'interno dei suoi confini originali, la vista non cresce inutilmente.

Inoltre, una nuova riga sotto l'immagine della categoria diventa visibile attraverso l'animazione al passaggio del mouse su di essa. Questa riga mostra una selezione degli ultimi post per la categoria. Ogni post-anteprima è un link al post. Ogni altro elemento interattivo nella riga della categoria apre la pagina dei dettagli per la categoria.

Per dare l'illusione di più contenuti di quelli effettivamente presenti, ho usato la classe "stack" di Daisy-UI per dare a ogni post-anteprima uno stack falso per connotare un'impressione di più elementi di anteprima sotto quelli attuali.

Image 41c052a8c928

Sui dispositivi di piccole dimensioni, non viene eseguito il rendering delle anteprime. Un utente può solo fare clic sull'immagine della categoria grande per andare alla pagina dei dettagli, dove sono elencati tutti i post.

Recensione e prospettive

Come puoi vedere, anche una semplice pagina che deve fornire solo una panoramica degli elementi può essere fonte di errori sia visivi che concettuali, nonché materiale di apprendimento su come progettare un'interfaccia utente buona e funzionante, nonché UX.

Non ho aggiunto alcuna ricerca di testo locale per consentire agli utenti di filtrare in base all'input di testo. Per ora, intendo promuovere l'esplorazione della pagina tramite lo scorrimento e il passaggio del mouse. Se questo diventa un problema in futuro (ad esempio perché gli utenti non riescono a trovare il contenuto che stanno cercando), aggiungerò alcune opzioni di filtro. Ciò potrebbe anche essere necessario poiché il numero di post e le categorie continuano a crescere. Naturalmente, ti do il benvenuto per provare tu stesso la pagina.

Il codice sorgente

Il testo seguente contiene il codice completo e non modificato sia per il contenitore della pagina che per i gruppi di categorie. Tutto è scritto in Typescript con React.js e Tailwind.css. Se pensi di poter riutilizzare alcune parti, sono felice di poterti aiutare.

//
// 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>
  );
};