UI ve UX hedefleri
Yayınlanan gönderiler için tüm kategorileri gösteren bir genel bakış sayfasıyla asıl amacım, gerçekte mevcut olan kategorilerin keşfedilebilirliğini artırmaktı. Ayrıca bu değişikliği, sayfanın görsel güzelliğini geliştirmek ve kullanıcı deneyimini bu ilerici web uygulamasındaki diğer sayfalarla uyumlu hale getirmek için bir fırsat olarak gördüm.
Aşağıdaki ekran görüntüsü, sayfanın yeniden tasarlanmadan önce nasıl göründüğünü gösterir.
Ne değiştirmek zorunda kaldı
Eski uygulamanın iki önemli sorunu vardı. İlk olarak, 5'ten az öğeye sahip kategoriler, kullanılabilir önizleme sonrası kapak resimlerini tüm genişliğe uyacak şekilde ölçeklediğinden, düzen gerçekten geçersizdi. İkincisi, sayfanın üst kısmında görünür bir birincil eylem çubuğu yoktu - kullanıcıya ana gezinme öğeleri sağladığı için kesinlikle bir tane oluşturulmuş olmalıdır.
Ayrıca, kategorilere yönelik genel bakış sayfasının, onları görünür kılmak için gerçekten iyi bir iş yapmadığı ortaya çıkıyor. Bunun yerine, kullanıcı öncelikle her bir kategoriyle ilgili gönderilerin kapak resimlerini gördü. Yalnızca ikinci kez bakıldığında, her kategorinin başlığının yanı sıra bir simge içeren satır tanınır. Estetik açıdan hoş olmamasının yanı sıra, bu kullanıcı arayüzü kötü tasarlanmıştı.
Hataları düzeltmek
Bu benim kişisel web uygulamam olduğu için, başlangıçta kategorilere genel bakış sayfasında işleri karıştıran da bendim. Daha fazla mazeret olmadan, tüm sayfayı yeniden çalışmak için oturdum.
Yeni sürüm, her öğenin bir kategori için büyük bir önizleme resmi gösterdiği tek bir satır sütunu gösterir. Özel görüntü, izin verilen tüm genişliği alır. Her iki eksende ortalanmış, ortadaki kategori başlığı, onu hemen görünür ve tanınabilir hale getirir. Okunabilirliği artırmak için metnin altına siyahtan şeffafa ince bir geçiş ekledim.
Resmin ve metnin üzerine gelindiğinde, bu kategori için toplam gönderi sayısını gösteren ek bir bilgi metni görünür hale gelir - bu, daha önce mevcut olmayan bir bilgidir.
Gezinme sorununu gidermek için, mevcut eylem çubuğunu, kullanıcının kullanabileceği tüm ana rotalarla yeniden kullandım.
Yineleme yoluyla iyileştirme
Daha hoş bir kullanıcı deneyimi sağlamak için, bir kategori satırının üzerine gelindiğinde resme ince bir ölçeklendirme animasyonu da ekledim. Görüntü orijinal sınırları içinde ölçeklendikçe görünüm gereksiz yere büyümez.
Ayrıca, kategori görüntüsünün altındaki yeni bir satır, üzerine gelindiğinde animasyon aracılığıyla görünür hale gelir. Bu satır, kategori için en son gönderilerin bir seçimini gösterir. Her gönderi önizlemesi, gönderiye bir bağlantıdır. Kategori satırındaki diğer her etkileşimli öğe, kategori için ayrıntı sayfasını açar.
Gerçekte olduğundan daha fazla içerik yanılsaması vermek için, her bir önizleme sonrası, mevcut olanların altında daha fazla önizleme öğesi izlenimi vermek için sahte bir yığın vermek için Daisy-UI'lerin "yığın" sınıfını kullandım.
Küçük cihazlarda önizleme oluşturulmaz. Bir kullanıcı, tüm gönderilerin listelendiği ayrıntı sayfasına gitmek için yalnızca büyük kategori resmine tıklayabilir.
İnceleme ve görünüm
Gördüğünüz gibi, yalnızca öğelere genel bir bakış sunması gereken basit bir sayfa bile hem görsel hem de kavramsal hataların yanı sıra iyi ve çalışan bir kullanıcı arayüzünün yanı sıra UX'in nasıl tasarlanacağına dair öğrenme materyali kaynağı olabilir.
Kullanıcıların metin girişine göre filtrelemesi için herhangi bir yerel metin araması eklemedim. Şimdilik, gezinmenin yanı sıra kaydırarak sayfada keşfetmeyi teşvik etmeyi amaçlıyorum. Bu gelecekte bir sorun haline gelirse (örneğin, kullanıcılar aradıkları içeriği bulamadıklarından), bazı filtreleme seçenekleri ekleyeceğim. Bu, kategorilerin yanı sıra gönderilerin sayısı da artmaya devam ettikçe gerekli hale gelebilir. Tabii ki, sayfayı kendiniz denemenizi rica ediyorum.
kaynak kodu
Aşağıdaki metin, hem sayfa kapsayıcı hem de kategori grupları için eksiksiz, değiştirilmemiş kodu içerir. Her şey TypeScript'te React.js ve Tailwind.css ile yazılmıştır. Bazı kısımları tekrar kullanabileceğinizi düşünüyorsanız, size yardımcı olabildiğim için mutluyum.
//
// 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>
);
};