50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | RandomImageGenerator(随机图像生成组件)
基于Vue 3和Tailwind CSS的随机图片流组件,可从Lorem Picsum服务获取不同尺寸的图片。主要功能包括:创建5行3列的网格布局,每张图片随机生成300-309像素的尺寸;使用Vue的组合式API管理图片数据;实现图片加载失败自动重试机制;通过v-for动态渲染图片网格。技术亮点包括:使用ref响应式管理图片数组、onMounted生命周期初始化图片、v-for绑定动态样式、er
📅 我们继续 50 个小项目挑战!—— RandomImageGenerator组件
仓库地址:https://github.com/SunACong/50-vue-projects
项目预览地址:https://50-vue-projects.vercel.app/

使用 Vue 3 的组合式 API (<script setup>) 和 Tailwind CSS 创建一个动态的随机图片流展示页面。这个组件会从 Lorem Picsum 服务获取不同尺寸的随机图片,并在图片加载失败时自动重试,非常适合用作作品集、博客或设计类网站的背景或内容填充。
🎯 应用目标
- 创建一个包含多张随机图片的网格布局
- 每张图片从 Lorem Picsum 服务获取,尺寸随机
- 图片加载失败时自动尝试加载新的随机图片
- 使用 Vue 3 的响应式系统管理图片数据
- 实现页面加载时自动初始化图片流
🔧 技术实现点
| 技术点 | 描述 |
|---|---|
Vue 3 <script setup> |
使用 ref, onMounted |
ref |
images 数组存储所有图片信息(URL、宽、高) |
onMounted |
组件挂载完成后调用 generateImages 初始化图片 |
v-for |
遍历 images 数组,渲染图片元素 |
:key |
为每个图片项提供唯一的 key(使用 index) |
:style |
动态绑定图片的 width 和 height 样式 |
@error 事件 |
监听图片加载失败,调用 handleImageError 重新加载 |
Math.random() |
生成随机数,用于: |
- 图片尺寸
- URL 查询参数(防止缓存)
- 重试时生成新图片 |
| Tailwind CSSflex-wrap/max-w-6xl/object-cover| 创建响应式网格容器和图片样式 |
📚 核心数据与状态
1. 图片数据 (images)
这是一个响应式数组,使用 ref 包裹。每个数组项是一个对象,包含:
url: 图片的完整 URLwidth: 图片的宽度(像素)height: 图片的高度(像素)
2. 配置常量
| 常量 | 值 | 作用 |
|---|---|---|
baseURL |
'https://picsum.photos' |
Lorem Picsum 服务的基础 URL |
rows |
5 |
网格的行数 |
columns |
3 |
网格的列数 |
totalImages |
rows * columns = 15 |
总共需要生成的图片数量 |
3. 辅助函数
| 函数 | 作用 |
|---|---|
getRandomSize() |
返回一个介于 300 到 309 像素之间的随机整数(Math.floor(Math.random() * 10) + 300) |
generateImages() |
生成 rows * columns 张图片的数据,并赋值给 images.value |
handleImageError(index) |
当索引为 index 的图片加载失败时,为其生成一张新的随机图片 |
🖌️ 组件实现
🎨 模板结构 <template>
<template>
<div class="font-roboto flex min-h-screen flex-col items-center justify-center bg-white p-4">
<h1 class="title my-6 text-center text-3xl font-bold">Random Image Feed</h1>
<div class="container mx-auto flex max-w-6xl flex-wrap items-center justify-center">
<div v-for="(image, index) in images" :key="index" class="m-3">
<img
:src="image.url"
:alt="'Random image ' + (index + 1)"
class="h-full w-full max-w-full object-cover"
:style="{ width: image.width + 'px', height: image.height + 'px' }"
@error="handleImageError(index)" />
</div>
</div>
</div>
</template>
模板结构解析:
-
外层容器 (
div):flex min-h-screen flex-col items-center justify-center:使用 Flexbox 将整个内容在视口中垂直居中。flex-col表示子元素垂直排列。bg-white:白色背景。p-4:整体内边距。font-roboto:使用 Roboto 字体(需引入)。
-
标题 (
h1):text-3xl:大号字体。font-bold:粗体。text-center:居中对齐。my-6:上下外边距。
-
图片容器 (
div.container):flex max-w-6xl flex-wrap:创建一个最大宽度为6xl(80rem ≈ 1280px) 的 Flex 容器,并允许子元素换行 (flex-wrap)。mx-auto:水平居中。items-center justify-center:子元素在交叉轴(垂直)和主轴(水平,换行后)上都居中。- 这个容器内的
div元素(每个包含一张图片)会根据屏幕宽度自动排列成多行。
-
单个图片项 (
div和img):v-for="(image, index) in images":遍历images数组。:key="index":为每个列表项提供唯一的 key。注意:如果images数组顺序会变或项目会被删除/插入,使用index作为 key 可能不是最佳实践。但在此例中,数组长度固定且顺序不变,index是可接受的。class="m-3":每个图片项周围有0.75rem的外边距,形成网格间隙。<img>元素::src="image.url":绑定图片 URL。:alt="...":动态生成 alt 文本,提升可访问性。class="h-full w-full max-w-full object-cover":h-full/w-full:尝试填满父容器div。max-w-full:防止图片宽度超过其容器。object-cover:关键! 确保图片按比例缩放并覆盖整个容器,常用于图片网格,避免留白。
:style="{ width: image.width + 'px', height: image.height + 'px' }":关键! 使用内联样式精确设置图片的宽度和高度(以像素为单位)。这是实现不同尺寸图片网格的核心。Tailwind 的w-*/h-*类无法直接处理变量。@error="handleImageError(index)":监听图片加载失败事件,将当前图片的index传递给处理函数。
💻 脚本逻辑 <script setup>
<script setup>
import { ref, onMounted } from 'vue'
const images = ref([])
const baseURL = 'https://picsum.photos'
const rows = 5
const columns = 3
const getRandomSize = () => Math.floor(Math.random() * 10) + 300
const generateImages = () => {
const newImages = []
for (let i = 0; i < rows * columns; i++) {
const width = getRandomSize()
const height = getRandomSize()
// 用随机 query 避免缓存重复
const url = `${baseURL}/${width}/${height}?random=${Math.random()}`
newImages.push({ url, width, height })
}
images.value = newImages
}
const handleImageError = (index) => {
const width = getRandomSize()
const height = getRandomSize()
images.value[index] = {
...images.value[index],
url: `${baseURL}/${width}/${height}?random=${Math.random()}`,
width,
height,
}
}
onMounted(generateImages)
</script>
脚本逻辑详解:
-
getRandomSize()函数:Math.random()生成[0, 1)的随机小数。* 10得到[0, 10)。Math.floor()向下取整,得到[0, 9]的整数。+ 300得到[300, 309]的整数。确保图片不会太小。
-
generateImages()函数:- 创建一个空数组
newImages。 - 使用
for循环生成rows * columns(15) 张图片。 - 为每张图片生成随机的
width和height。 - 构造 URL:
${baseURL}/${width}/${height}?random=${Math.random()}。/width/height是 Picsum 的标准格式。?random=...查询参数是关键!它确保即使width和height相同,浏览器也会认为是不同的 URL,从而避免从缓存加载完全相同的图片。这对于展示“随机”图片流至关重要。
- 将包含
url,width,height的对象推入newImages。 - 最后将
newImages赋值给images.value,触发视图更新。
- 创建一个空数组
-
handleImageError(index)函数:- 当某张图片加载失败(如网络问题、Picsum 服务暂时不可用)时,该函数被调用。
- 传入
index参数,知道是哪一张图片出错。 - 生成新的
width和height。 - 构造一个新的、带有新随机查询参数的 URL。
- 使用数组索引赋值
images.value[index] = { ... }更新该特定图片的数据。...images.value[index]展开原对象(虽然这里只有url,width,height会被覆盖,但保留了结构)。- 覆盖
url,width,height为新值。
- 由于
images是响应式的,这个更新会立即触发视图重新渲染该图片,尝试加载新的随机图片。
-
onMounted(generateImages):- 使用
onMounted生命周期钩子,在组件挂载到 DOM 后立即调用generateImages函数,初始化图片流。
- 使用
🎨 Tailwind CSS 样式重点
| 类名 | 作用 |
|---|---|
font-roboto |
使用 Roboto 字体 |
min-h-screen |
最小高度为视口高度 |
flex / flex-col / items-center / justify-center |
Flexbox 布局,垂直居中 |
bg-white |
白色背景 |
p-4 |
内边距 |
text-3xl / text-center / font-bold / my-6 |
标题样式 |
max-w-6xl |
最大宽度 (80rem ≈ 1280px) |
flex-wrap |
允许子元素换行 |
mx-auto |
水平居中 |
m-3 |
外边距 (0.75rem),形成网格间隙 |
w-full / h-full / max-w-full / object-cover |
图片样式,确保按比例覆盖容器 |
:style |
动态设置精确的像素尺寸 |
⚠️ 注意事项与潜在问题
index作为key:虽然在此例中(固定长度、不改变顺序)是安全的,但通常建议使用唯一 ID 作为key。如果未来功能扩展(如删除、插入、排序图片),使用index会导致性能问题和状态混乱。- 图片加载性能:一次性加载 15 张图片(即使尺寸不大)可能会对网络和页面性能造成压力,尤其是在移动设备或慢速网络上。可以考虑:
- 懒加载 (Lazy Loading):使用
loading="lazy"属性或 Intersection Observer API,只在图片进入视口时才加载。 - 分页或无限滚动:不要一次性加载所有图片。
- 懒加载 (Lazy Loading):使用
- Picsum 服务依赖:应用完全依赖于外部服务
picsum.photos。如果该服务宕机或更改 API,你的图片流将无法显示。handleImageError只能处理单次失败,如果服务大面积不可用,用户会看到多次闪烁的重试。 - 随机性:
getRandomSize()产生的尺寸范围 (300-309px) 非常窄,图片尺寸差异不大。可以扩大范围(如200-600)以获得更丰富的视觉效果。 - 响应式:
flex-wrap提供了基本的响应式,但可以进一步优化不同屏幕尺寸下的rows/columns或max-w-*。
📁 常量定义 + 组件路由
constants/index.js 添加组件预览常量:
{
id: 48,
title: 'RandomImageGenerator',
image: 'https://50projects50days.com/img/projects-img/48-random-image-feed.png',
link: 'RandomImageGenerator',
},
router/index.js 中添加路由选项:
{
path: '/RandomImageGenerator',
name: 'RandomImageGenerator',
component: () => import('@/projects/RandomImageGenerator.vue'),
},
🏁 总结
项目很好地展示了如何利用外部 API(Lorem Picsum)快速搭建视觉内容,并通过 @error 事件和响应式数据更新来提升用户体验的健壮性。通过结合 Vue 3 的响应式特性、生命周期管理、事件处理和 Tailwind CSS 的实用类,我们实现了图片的动态生成、精确尺寸控制和错误恢复机制。 happy coding! ✨
你可以扩展以下功能:
- ✅ 添加加载状态:在图片加载完成前显示骨架屏 (Skeleton Screen) 或加载动画。
- ✅ 实现懒加载:提升初始加载性能。
- ✅ 增加交互:点击图片放大预览,或添加喜欢/收藏功能。
- ✅ 搜索或筛选:允许用户通过关键词搜索 Picsum 图片(Picsum 支持
?grayscale等参数)。 - ✅ 动态调整网格:提供按钮让用户选择每行显示的图片数量。
- ✅ 错误边界:如果
handleImageError也失败(如网络完全断开),可以显示一个通用的“加载失败”占位符。
👉 下一篇,我们将完成TodoList组件,一个视觉舒适的TODO UI设计。🚀
感谢阅读,欢迎点赞、收藏和分享 😊
更多推荐



所有评论(0)