UX מקרה של דף סקירה כללית

כיצד עיצבתי את דף הסקירה לכל קטגוריות הפוסט

מטרות UI ו- UX

המטרה העיקרית שלי עם דף סקירה המציג את כל הקטגוריות לפוסטים שפורסמו הייתה הגדלת הגילוי של הקטגוריות הזמינות בפועל. ראיתי גם בשינוי זה הזדמנות לשפר את היופי החזותי של הדף וכן ליישר את ה- UX עם שאר הדפים באפליקציית הרשת המתקדמת הזו.

צילום המסך הבא מראה כיצד הדף נראה לפני העיצוב המחודש.

Image e8edca7d7ea0

מה היה צריך לשנות

ליישום הישן היו שתי בעיות עיקריות. ראשית, הפריסה הייתה למעשה לא חוקית, מכיוון שקטגוריות עם פחות מ -5 אלמנטים הגדילו את תמונות הכריכה הזמינות לאחר התצוגה המקדימה כך שיתאימו לכל הרוחב. שנית, לא היה סרגל פעולה ראשי גלוי בראש הדף - בהחלט צריך להיות אחד שיוצג, מכיוון שהוא מספק רכיבי ניווט עיקריים למשתמש.

יתר על כן, מתברר שדף הסקירה לקטגוריות לא ממש עשה עבודה נהדרת בהפיכתם לגלויים. במקום זאת, המשתמש ראה בעיקר תמונות כיסוי לפוסטים הקשורים לכל קטגוריה. רק בהסתכלות שנייה, השורה עם הסמל כמו גם הכותרת של כל קטגוריה מוכרת. מלבד היותו אסתטי לא נעים, ממשק המשתמש הזה פשוט תוכנן בצורה גרועה.

תיקון טעויות

מכיוון שזו אפליקציית האינטרנט האישית שלי, הייתי גם זה שבלגן את הדברים בתחילה בדף סקירת הקטגוריות. ללא תירוצים נוספים, ישבתי לכן לעבד את כל הדף מחדש.

הגרסה החדשה מציגה עמודה אחת של שורות, כאשר כל פריט מציג תמונת תצוגה מקדימה גדולה לקטגוריה. התמונה המותאמת אישית לוקחת את כל הרוחב המותר. במרכז שני הצירים נמצאת כותרת הקטגוריה באמצע, מה שהופך אותה לגלויה ומזהה באופן מיידי. כדי לשפר את הקריאות, הוספתי שיפוע עדין משחור לשקוף מתחת לטקסט.

כאשר מרחפים מעל התמונה והטקסט, טקסט מידע נוסף הופך להיות גלוי, המציג את מספר הפוסטים הכולל עבור קטגוריה זו - מידע שלא היה זמין בעבר.

כדי לתקן את בעיית הניווט, השתמשתי שוב בסרגל הפעולות הקיים עם כל המסלולים העיקריים העומדים לרשות המשתמש.

Image 0616bdd11b98

שיפור באמצעות איטרציה

כדי לספק חווית משתמש נעימה יותר, הוספתי לתמונה גם אנימציה קנה מידה עדין לתמונה בעת ריחוף מעל שורת קטגוריות. כשהדימוי משתנה בתוך גבולותיו המקוריים, הנוף אינו צומח ללא צורך.

יתר על כן, שורה חדשה מתחת לתמונת הקטגוריה הופכת לגלויה באמצעות אנימציה בעת ריחוף מעליה. שורה זו מציגה מבחר של הפוסטים האחרונים לקטגוריה. כל תצוגה מקדימה של פוסט היא קישור לפוסט. כל רכיב אינטראקטיבי אחר בשורת הקטגוריה פותח את דף הפירוט של הקטגוריה.

כדי לתת אשליה של יותר תוכן ממה שיש, השתמשתי בכיתה "מחסנית" של 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>
  );
};