यूआई और यूएक्स लक्ष्य
एक सिंहावलोकन पृष्ठ के साथ मेरा मुख्य लक्ष्य जो प्रकाशित पोस्ट के लिए सभी श्रेणियां दिखाता है, वास्तव में उपलब्ध श्रेणियों की खोज क्षमता को बढ़ाना था। मैंने इस परिवर्तन को पृष्ठ की दृश्य सुंदरता को बढ़ाने के साथ-साथ इस प्रगतिशील वेब ऐप पर अन्य पृष्ठों के साथ UX को संरेखित करने के अवसर के रूप में भी देखा।
निम्न स्क्रीनशॉट दिखाता है कि पृष्ठ रीडिज़ाइन से पहले कैसा दिखता था।
क्या बदलना पड़ा
पुराने कार्यान्वयन में दो प्रमुख मुद्दे थे। सबसे पहले, लेआउट वास्तव में अमान्य था, क्योंकि 5 से कम तत्वों वाली श्रेणियों ने उपलब्ध पोस्ट-पूर्वावलोकन कवर छवियों को पूरी चौड़ाई में फिट करने के लिए बढ़ाया था। दूसरा, पृष्ठ के शीर्ष पर कोई प्राथमिक क्रिया पट्टी दिखाई नहीं दे रही थी - निश्चित रूप से एक प्रस्तुत किया जाना चाहिए, क्योंकि यह उपयोगकर्ता को मुख्य नेविगेशन तत्व प्रदान करता है।
इसके अलावा, यह स्पष्ट हो जाता है कि श्रेणियों के लिए अवलोकन पृष्ठ वास्तव में उन्हें दृश्यमान बनाने का एक अच्छा काम नहीं करता है। इसके बजाय, उपयोगकर्ता ने मुख्य रूप से प्रत्येक श्रेणी से संबंधित पोस्ट के लिए कवर छवियां देखीं। केवल दूसरी बार देखने पर, प्रत्येक श्रेणी के आइकन के साथ-साथ शीर्षक वाली पंक्ति को पहचाना जाता है। सौंदर्य की दृष्टि से मनभावन होने के अलावा, यह UI केवल बुरी तरह से डिज़ाइन किया गया था।
गलतियों को सुधारना
चूंकि यह मेरा व्यक्तिगत वेब ऐप है, इसलिए मैं भी वह था जिसने शुरुआत में श्रेणियों के अवलोकन पृष्ठ पर चीजों को गड़बड़ कर दिया था। इसलिए बिना किसी बहाने के, मैं पूरे पृष्ठ पर फिर से काम करने के लिए बैठ गया।
नया संस्करण पंक्तियों का एकल स्तंभ दिखाता है, जहां प्रत्येक आइटम किसी श्रेणी के लिए एक बड़ी पूर्वावलोकन छवि दिखाता है। कस्टम छवि पूरी अनुमत चौड़ाई लेती है। दोनों अक्षों पर केंद्रित बीच में श्रेणी का शीर्षक है, जो इसे तुरंत दृश्यमान और पहचानने योग्य बनाता है। सुपाठ्यता में सुधार करने के लिए, मैंने पाठ के नीचे काले से पारदर्शी तक एक सूक्ष्म ढाल जोड़ा।
छवि और टेक्स्ट पर होवर करने पर, एक अतिरिक्त जानकारी टेक्स्ट दिखाई देता है, जो इस श्रेणी के लिए कुल पोस्ट दिखाता है - एक ऐसी जानकारी जो पहले उपलब्ध नहीं थी।
नेविगेशन समस्या को ठीक करने के लिए, मैंने उपयोगकर्ता के लिए उपलब्ध सभी मुख्य मार्गों के साथ मौजूदा एक्शन बार का भी पुन: उपयोग किया।
पुनरावृत्ति के माध्यम से सुधार
अधिक सुखद उपयोगकर्ता अनुभव प्रदान करने के लिए, मैंने एक श्रेणी पंक्ति पर मँडराते समय छवि में एक सूक्ष्म स्केलिंग-एनीमेशन भी जोड़ा। जैसे-जैसे छवि अपनी मूल सीमाओं के भीतर बढ़ती है, दृश्य अनावश्यक रूप से नहीं बढ़ता है।
इसके अलावा, श्रेणी छवि के नीचे एक नई पंक्ति एनीमेशन के माध्यम से उस पर मँडराते हुए दिखाई देती है। यह पंक्ति श्रेणी के लिए नवीनतम पदों के चयन को दर्शाती है। प्रत्येक पोस्ट-पूर्वावलोकन पोस्ट की एक कड़ी है। श्रेणी-पंक्ति में प्रत्येक अन्य इंटरैक्टिव तत्व श्रेणी के लिए विवरण पृष्ठ खोलता है।
वास्तव में वहां की तुलना में अधिक सामग्री का भ्रम देने के लिए, मैंने डेज़ी-यूआई "स्टैक" -क्लास का उपयोग प्रत्येक पोस्ट-पूर्वावलोकन को वर्तमान वाले के नीचे अधिक पूर्वावलोकन-आइटम की छाप देने के लिए एक नकली स्टैक देने के लिए किया।
छोटे उपकरणों पर, कोई पूर्वावलोकन प्रस्तुत नहीं किया जाता है। एक उपयोगकर्ता विवरण पृष्ठ पर जाने के लिए केवल बड़ी श्रेणी-छवि पर क्लिक कर सकता है, जहां सभी पोस्ट सूचीबद्ध हैं।
समीक्षा और दृष्टिकोण
जैसा कि आप देख सकते हैं, यहां तक कि एक साधारण पृष्ठ जिसमें केवल तत्वों का अवलोकन प्रदान करना होता है, दोनों दृश्य और वैचारिक त्रुटियों के साथ-साथ एक अच्छे और काम करने वाले UI के साथ-साथ UX को डिजाइन करने के लिए सीखने की सामग्री का स्रोत हो सकता है।
मैंने टेक्स्ट इनपुट द्वारा फ़िल्टर करने के लिए उपयोगकर्ताओं के लिए कोई स्थानीय टेक्स्ट खोज नहीं जोड़ा। अभी के लिए, मैं स्क्रॉल करके और साथ ही होवर करके पृष्ठ पर अन्वेषण को बढ़ावा देना चाहता हूं। यदि यह भविष्य में एक समस्या बन जाती है (उदाहरण के लिए क्योंकि उपयोगकर्ताओं को वह सामग्री नहीं मिल रही है जिसकी वे तलाश कर रहे हैं), तो मैं कुछ फ़िल्टरिंग विकल्प जोड़ूंगा। यह आवश्यक भी हो सकता है क्योंकि पदों की संख्या के साथ-साथ श्रेणियां बढ़ती रहती हैं। बेशक, मैं इस पेज को अपने लिए आज़माने के लिए आपका स्वागत करता हूँ।
स्रोत कोड
निम्नलिखित पाठ में दोनों पेज कंटेनर के साथ-साथ श्रेणी समूहों के लिए पूर्ण, असंशोधित कोड है। सब कुछ टाइपस्क्रिप्ट में 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>
);
};