UX-Fallstudie einer Übersichtsseite

Wie ich die Übersichtsseite für alle Post-Kategorien gestaltet habe

UI- und UX-Ziele

Mein Hauptziel mit einer Übersichtsseite, die alle Kategorien für veröffentlichte Beiträge anzeigt, war es, die Auffindbarkeit der tatsächlich verfügbaren Kategorien zu erhöhen. Ich sah diese Änderung auch als Gelegenheit, die visuelle Schönheit der Seite zu verbessern und die UX an die anderen Seiten dieser progressiven Web-App anzupassen.

Der folgende Screenshot zeigt, wie die Seite vor dem Redesign aussah.

Image e8edca7d7ea0

Was musste sich ändern

Die alte Implementierung hatte zwei Hauptprobleme. Erstens war das Layout tatsächlich ungültig, da Kategorien mit weniger als 5 Elementen die verfügbaren Titelbilder nach der Vorschau auf die gesamte Breite skaliert haben. Zweitens war oben auf der Seite keine primäre Aktionsleiste sichtbar - es sollte definitiv eine gerendert werden, da sie dem Benutzer die wichtigsten Navigationselemente bietet.

Außerdem wird deutlich, dass die Übersichtsseite für Kategorien nicht wirklich gut gelungen ist, diese sichtbar zu machen. Stattdessen sah der Benutzer hauptsächlich Titelbilder für Beiträge, die sich auf jede Kategorie beziehen. Erst auf den zweiten Blick erkennt man die Zeile mit dem Icon sowie den Titel jeder Kategorie. Abgesehen davon, dass sie ästhetisch nicht ansprechend ist, wurde diese Benutzeroberfläche einfach schlecht gestaltet.

Fehler korrigieren

Da dies meine persönliche Web-App ist, war ich auch derjenige, der anfangs auf der Kategorieübersichtsseite etwas durcheinander gebracht hat. Ohne weitere Ausreden habe ich mich deshalb hingesetzt, um die ganze Seite zu überarbeiten.

Die neue Version zeigt eine einzelne Spalte mit Zeilen, in denen jedes Element ein großes Vorschaubild für eine Kategorie zeigt. Das benutzerdefinierte Bild nimmt die gesamte zulässige Breite ein. Auf beiden Achsen zentriert befindet sich der Kategorietitel in der Mitte, wodurch er sofort sichtbar und erkennbar wird. Um die Lesbarkeit zu verbessern, habe ich unter dem Text einen subtilen Farbverlauf von Schwarz zu Transparent hinzugefügt.

Wenn Sie mit der Maus über Bild und Text fahren, wird ein zusätzlicher Infotext sichtbar, der die Gesamtzahl der Beiträge für diese Kategorie anzeigt - eine Information, die zuvor nicht verfügbar war.

Um das Navigationsproblem zu beheben, habe ich auch die vorhandene Aktionsleiste mit allen für den Benutzer verfügbaren Hauptrouten wiederverwendet.

Image 0616bdd11b98

Verbesserung durch Iteration

Um eine angenehmere Benutzererfahrung zu bieten, habe ich dem Bild auch eine subtile Skalierungsanimation hinzugefügt, wenn ich mit der Maus über eine Kategoriezeile fahre. Da das Bild innerhalb seiner ursprünglichen Grenzen skaliert wird, wächst die Ansicht nicht unnötig.

Außerdem wird eine neue Zeile unter dem Kategoriebild durch Animation sichtbar, wenn man mit der Maus darüber fährt. Diese Zeile zeigt eine Auswahl der neuesten Beiträge für die Kategorie. Jede Beitragsvorschau ist ein Link zum Beitrag. Jedes andere interaktive Element in der Kategorie-Zeile öffnet die Detailseite für die Kategorie.

Um die Illusion von mehr Inhalt zu erwecken, als tatsächlich vorhanden ist, habe ich die „Stack“-Klasse von Daisy-UI verwendet, um jeder Post-Preview einen gefälschten Stack zu geben, um einen Eindruck von mehr Preview-Elementen unter den aktuellen zu vermitteln.

Image 41c052a8c928

Auf kleinen Geräten werden keine Vorschauen gerendert. Ein Benutzer kann nur auf das große Kategorie-Bild klicken, um zur Detailseite zu gelangen, auf der alle Beiträge aufgelistet sind.

Rückblick und Ausblick

Wie Sie sehen, kann selbst eine einfache Seite, die nur einen Überblick über die Elemente bieten muss, sowohl eine Quelle für visuelle als auch konzeptionelle Fehler sowie Lernmaterial zum Entwerfen einer guten und funktionierenden Benutzeroberfläche sowie UX sein.

Ich habe keine lokale Textsuche hinzugefügt, damit Benutzer nach Texteingaben filtern können. Im Moment beabsichtige ich, die Erkundung der Seite durch Scrollen und Schweben zu fördern. Sollte dies in Zukunft zu einem Problem werden (z. B. weil Benutzer die gesuchten Inhalte nicht finden können), werde ich einige Filteroptionen hinzufügen. Dies kann auch notwendig werden, da die Anzahl der Beiträge sowie der Kategorien ständig wächst. Natürlich lade ich Sie ein, die Seite selbst auszuprobieren.

Der Quellcode

Der folgende Text enthält den vollständigen, unveränderten Code sowohl für den Seitencontainer als auch für die Kategoriegruppen. Alles ist in Typescript mit React.js und Tailwind.css geschrieben. Wenn Sie der Meinung sind, dass Sie einige Teile wiederverwenden können, freue ich mich, Ihnen helfen zu können.

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