Μελέτη περίπτωσης UX μιας σελίδας επισκόπησης

Πώς σχεδίασα τη σελίδα επισκόπησης για όλες τις μετακατηγορίες

Στόχοι UI & UX

Ο κύριος στόχος μου με μια σελίδα επισκόπησης που εμφανίζει όλες τις κατηγορίες για δημοσιευμένες αναρτήσεις ήταν να αυξήσω την ανακάλυψη των κατηγοριών που είναι πραγματικά διαθέσιμες. Επίσης, είδα αυτήν την αλλαγή ως μια ευκαιρία να βελτιώσω την οπτική ομορφιά της σελίδας καθώς και να ευθυγραμμίσω το UX με τις άλλες σελίδες αυτής της προοδευτικής διαδικτυακής εφαρμογής.

Το παρακάτω στιγμιότυπο οθόνης δείχνει πώς ήταν η σελίδα πριν από τον επανασχεδιασμό.

Image e8edca7d7ea0

Τι έπρεπε να αλλάξει

Η παλιά εφαρμογή είχε δύο μεγάλα ζητήματα. Πρώτον, η διάταξη ήταν στην πραγματικότητα άκυρη, καθώς κατηγορίες με λιγότερα από 5 στοιχεία κλιμάκωσαν τις διαθέσιμες εικόνες εξωφύλλου μετά την προεπισκόπηση ώστε να ταιριάζουν σε όλο το πλάτος. Δεύτερον, δεν υπήρχε ορατή γραμμή κύριας ενέργειας στο επάνω μέρος της σελίδας - θα πρέπει οπωσδήποτε να αποδίδεται μία, καθώς παρέχει τα κύρια στοιχεία πλοήγησης στο χρήστη.

Επιπλέον, γίνεται προφανές ότι η σελίδα επισκόπησης για κατηγορίες δεν έκανε πραγματικά μεγάλη δουλειά για να τις κάνει ορατές. Αντ 'αυτού, ο χρήστης είδε κυρίως εικόνες εξωφύλλου για αναρτήσεις που σχετίζονται με κάθε κατηγορία. Μόνο όταν ρίχνετε μια δεύτερη ματιά, αναγνωρίζεται η σειρά με ένα εικονίδιο καθώς και ο τίτλος κάθε κατηγορίας. Εκτός από το ότι δεν είναι αισθητικά ευχάριστο, αυτό το περιβάλλον χρήστη ήταν απλά κακώς σχεδιασμένο.

Διόρθωση λαθών

Καθώς αυτή είναι η προσωπική μου εφαρμογή ιστού, ήμουν επίσης αυτός που μπέρδεψε τα πράγματα αρχικά στη σελίδα επισκόπησης κατηγοριών. Χωρίς άλλες δικαιολογίες, κάθισα λοιπόν να ξαναδουλέψω ολόκληρη τη σελίδα.

Η νέα έκδοση εμφανίζει μια μόνο στήλη γραμμών, όπου κάθε στοιχείο εμφανίζει μια μεγάλη εικόνα προεπισκόπησης για μια κατηγορία. Η προσαρμοσμένη εικόνα παίρνει όλο το επιτρεπόμενο πλάτος. Στο επίκεντρο και στους δύο άξονες βρίσκεται ο τίτλος της κατηγορίας στη μέση, κάτι που τον καθιστά άμεσα ορατό και αναγνωρίσιμο. Για να βελτιώσω την αναγνωσιμότητα, πρόσθεσα μια λεπτή κλίση από μαύρο σε διαφανές κάτω από το κείμενο.

Όταν τοποθετείτε το δείκτη του ποντικιού πάνω στην εικόνα και το κείμενο, γίνεται ορατό ένα επιπλέον κείμενο πληροφοριών, το οποίο δείχνει τον συνολικό αριθμό των δημοσιεύσεων για αυτήν την κατηγορία - πληροφορίες που δεν ήταν διαθέσιμες στο παρελθόν.

Για να διορθώσω το ζήτημα της πλοήγησης, χρησιμοποίησα επίσης την υπάρχουσα γραμμή ενεργειών με όλες τις κύριες διαδρομές που είναι διαθέσιμες στον χρήστη.

Image 0616bdd11b98

Βελτίωση μέσω επανάληψης

Για να προσφέρω μια πιο ευχάριστη εμπειρία χρήστη, πρόσθεσα επίσης ένα λεπτό animation κλιμάκωσης στην εικόνα όταν αιωρούμαι πάνω από μια σειρά κατηγορίας. Καθώς η εικόνα κλιμακώνεται στα αρχικά της όρια, η προβολή δεν αυξάνεται άσκοπα.

Επιπλέον, μια νέα σειρά κάτω από την εικόνα της κατηγορίας γίνεται ορατή μέσω κινούμενων σχεδίων όταν το αιωρείτε πάνω της. Αυτή η σειρά εμφανίζει μια επιλογή από τις πιο πρόσφατες αναρτήσεις για την κατηγορία. Κάθε μετά-προεπισκόπηση είναι ένας σύνδεσμος προς την ανάρτηση. Κάθε άλλο διαδραστικό στοιχείο στη σειρά κατηγορίας ανοίγει τη σελίδα λεπτομερειών για την κατηγορία.

Για να δώσω την ψευδαίσθηση περισσότερου περιεχομένου από ό, τι υπάρχει, χρησιμοποίησα την κατηγορία "στοίβας" Daisy-UI για να δώσω σε κάθε προεπισκόπηση μια ψεύτικη στοίβα για να υποδηλώσω την εντύπωση περισσότερων στοιχείων προεπισκόπησης κάτω από τα τρέχοντα.

Image 41c052a8c928

Σε μικρές συσκευές, δεν γίνονται προεπισκοπήσεις. Ένας χρήστης μπορεί να κάνει κλικ μόνο στη μεγάλη κατηγορία-εικόνα για να μεταβεί στη σελίδα λεπτομερειών, όπου αναφέρονται όλες οι αναρτήσεις.

Επισκόπηση και προοπτική

Όπως μπορείτε να δείτε, ακόμη και μια απλή σελίδα που πρέπει μόνο να παρέχει μια επισκόπηση στοιχείων μπορεί να είναι πηγή οπτικών και εννοιολογικών σφαλμάτων καθώς και εκπαιδευτικό υλικό για το πώς να σχεδιάσετε ένα καλό και λειτουργικό περιβάλλον χρήστη καθώς και UX.

Δεν πρόσθεσα καμία τοπική αναζήτηση κειμένου για χρήστες για φιλτράρισμα κατά εισαγωγή κειμένου. Προς το παρόν, σκοπεύω να προωθήσω την εξερεύνηση στη σελίδα με κύλιση καθώς και με τον αιώρηση. Εάν αυτό γίνει πρόβλημα στο μέλλον (για παράδειγμα επειδή οι χρήστες δεν μπορούν να βρουν το περιεχόμενο που αναζητούν), θα προσθέσω μερικές επιλογές φιλτραρίσματος. Αυτό μπορεί επίσης να καταστεί απαραίτητο καθώς ο αριθμός των δημοσιεύσεων καθώς και των κατηγοριών συνεχίζει να αυξάνεται. Φυσικά, σας καλωσορίζω να δοκιμάσετε τη σελίδα μόνοι σας.

Ο πηγαίος κώδικας

Το ακόλουθο κείμενο περιέχει τον πλήρη, μη τροποποιημένο κώδικα τόσο για το κοντέινερ της σελίδας όσο και για τις ομάδες κατηγοριών. Όλα είναι γραμμένα σε Typescript με React.js και Tailwind.css. Αν νομίζετε ότι μπορείτε να χρησιμοποιήσετε ξανά κάποια μέρη, χαίρομαι που θα μπορούσα να σας βοηθήσω.

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