next/image で Masonry レイアウトを実現する
この記事は最終更新日から2年以上が経過しています。
概要
next/image で Masonry レイアウトを実現するのは難しい という記事を書いたがレベルが上がって実現できるようになったので訂正記事を書く
出来上がったもの
実装
- 画像のURLとともに、アスペクト比を管理するようにした
- アスペクト比をもとに、
next/image
で画像を表示するように変更 break-inside-avoid
をつけないと画像が column からはみ出すので注意
import Image from 'next/image'
import Link from 'next/link'
import { customLoader } from '../../../lib/image-loader'
import DateFormatter from '../../date-formatter'
type PostCardProps = {
title: string
coverImage: {
url: string
aspectRatio?: string
}
date: string
slug: string
tags: string[]
}
const PostCard = ({ title, coverImage, date, slug, tags }: PostCardProps) => {
return (
<div className="break-inside-avoid block relative mb-4">
<Link as={`/posts/${slug}`} href="/posts/[slug]">
<a>
<figure
style={{
position: 'relative',
aspectRatio: coverImage.aspectRatio ?? '4/3'
}}
className="w-full rounded-md"
>
<Image
loader={customLoader}
src={coverImage.url}
alt={`Cover Image for ${title}`}
layout="fill"
objectFit="contain"
/>
</figure>
<div className="flex absolute inset-0 flex-col p-2 bg-black/40">
<div className="">
<h1 className="mb-1 text-xl font-bold">{title}</h1>
<DateFormatter dateString={date} />
</div>
<div className="flex flex-wrap gap-1 mt-auto">
{tags.map((tag, index) => (
<span
key={index}
className="px-2 text-black bg-white/60 rounded-md"
>
#{tag}
</span>
))}
</div>
</div>
</a>
</Link>
</div>
)}
type Props = {
posts: PostCardProps[]
}
const PostCards = ({ posts }: Props) => {
return (
<div className="columns-1 p-4 md:columns-2 lg:columns-3 xl:columns-4">
{posts.map(post => (
<PostCard {...post} key={post.slug} />
))}
</div>
)}
export default PostCards