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