因为找不到好用的前端占位图,于是我自己写了个谁都能用的占位图功能

最近,我在写一个前端组件库。事情本身不复杂,组件写完,需要给组件写上配套示例。

麻烦就出在图片上。

CardAvatarImageList......这几个组件的示例,不放图根本看不出效果。一个纯色 div 写着「这里是图片」,别人看的时候直接就是:"这跟没做有什么区别?"

于是我就找到一个比较能用的: picsum.photos。

html 复制代码
<img src="https://picsum.photos/800/600" />
<img src="https://picsum.photos/seed/card-1/400/300" />

在我本地跑得挺好------因为我挂着代理。

真正的问题是某一天,忘开代理了,然后组件示例页打开一片空白,转半天。

每个示例一到两张图,Network 面板里一整列 picsum 的请求排着队 pending,最慢的一条卡了 8 秒才回来,还有三四条直接超时变红。整个示例页面,从点进去到图全部出来,12 秒。

🥚疼!

越来越糟了

这个事儿本来不大,但是我本着一颗探索的心,我发现:

第一,不只是慢,是不稳定。 慢还能忍,最恶心的是时快时慢。同一个页面刷新三次,三次布局都不一样,因为有的图加载出来了有的没有,页面高度一直跳。

第二,CI 直接崩了。 我的组件库跑了视觉回归测试,用 Playwright 截图跟基线快照对比。图片来自第三方,又是随机图,每次跑出来的截图都不一样,快照对比全红。我一开始以为是我的组件写崩了,查了好久,最后发现是图的问题,当时直嘬牙花子。🥚疼!*2

第三,一个占位图的需求,我引了三个外网域名。 大家应该有熟悉这套组合的:

  • 占位色块 → placehold.co
  • 用户头像 → DiceBear 的 API
  • 列表假数据 → JSONPlaceholder

三个服务,三个域名,全在外网。速度不说了,它们的路径风格、参数命名、缓存策略互不相同,我得同时记三套 API。

第四,假数据全是英文。 JSONPlaceholder 返回的是 Leanne GrahamSincere@april.bizGwenborough。我要验证的是中文姓名换行、超长中文标题截断、中文地址在窄屏下的表现。拿英文数据测中文排版,等于没测。上线以后果然出问题:两个字的名字和四个字的名字在同一行里,对不齐。🥚疼!*3

那段时间我搜了很久有没有国内的替代品。找到的东西大致三类:

  1. 个人搭的小服务,能用,但没准哪天就消失了;
  2. 只做单一功能的,占位图归占位图,头像还得另找;
  3. 还有一些要注册、要申请 Key、要在请求头里带 token;

我只是想在 <img src> 里写一个地址而已,不想为这个引一个 SDK。🥚疼!*4

找了一圈没找到顺手的 --------- 那,不行就自己写了!

说干就干

➡️就它: devimg.cn

我将用最直接、最干脆、最不废话、最不绕弯子的方式给你解释这个网站: 国内开发者在写页面的时候,需要的所有临时素材,都在同一个域名下,直接写进 <img src> 就能用。

不注册,不要 Key,没有 SDK。

我给自己定的第一条原则是:这东西必须比查文档还快 。任何需要「先安装、先注册、先看文档」的设计都砍掉。所以最终的形态就是一条 URL。

第二条原则是:同一条 URL,需要可以永远返回同一张图。seed 的接口全部走 Cache-Control: immutable,你截一百次快照,拿到的都是同一张。

怎么用

下面的地址复制到浏览器地址栏里就能看到效果,不用做任何准备工作。

占位图

最基本的用法,给个宽高就完事:

html 复制代码
<img src="https://cdn.devimg.cn/800/600" />
<img src="https://cdn.devimg.cn/400/300?text=Banner&bg=409eff&fg=ffffff" />

需要画面固定不变的,走 seed,同一个 seed 配色永远一样:

html 复制代码
<img src="https://cdn.devimg.cn/seed/my-app/800/600" />

如果你以前用 placehold.co,那种 800x600 的写法和路径配色也照样认:

html 复制代码
<img src="https://cdn.devimg.cn/800x600?text=Banner" />
<img src="https://cdn.devimg.cn/800/600/409eff/ffffff" />

默认返回 SVG,体积小、缩放不糊。小程序里必须要位图的,加个 .webp / .png 后缀就行。

头像:支持中文首字

这个是我个人最想要的功能。DiceBear 那套风格很好看,但它不认中文,张三 进去出来是空白或者乱码。

html 复制代码
<img src="https://cdn.devimg.cn/avatar/devimg/张三/128" />
<img src="https://cdn.devimg.cn/avatar/lorelei/Luna/128" />
<img src="https://cdn.devimg.cn/avatar/devimg/张三/128?bg=3b5bdb&fg=ffffff&shape=square" />

除了自己做的中文首字头像,也把 DiceBear、Jdenticon、Minidenticons 的开源风格接了进来,加起来 50 多种。同一个 style 加同一个名字,永远是同一张脸------做用户列表的时候特别省事,不用自己维护一份头像映射表。

风格太多记不住的话,GET /avatar/styles 会返回完整目录,带每种风格的许可信息。

真实照片:按用途取图,而不是按 ID 抽奖

picsum 的图很漂亮,但它是随机的。我要做一个电商商品卡,抽到一张森林风景,那这个 demo 就白做了。

所以照片接口是按用途取的:

html 复制代码
<img src="https://cdn.devimg.cn/photo/400/400?scene=product&seed=p-1" />
<img src="https://cdn.devimg.cn/photo/1200/400?scene=banner&seed=hero" />
<img src="https://cdn.devimg.cn/photo/640/480?cat=美食&seed=food-1" />

scene 目前有十几种:product 商品、food 餐饮、news 资讯、travel 出行、hotel 住宿、banner 轮播、education 课程、realestate 房源等等。做电商列表就传 product,做资讯流就传 news,出来的图跟你的业务是搭的。

seed 就固定,不带就每次随机,看你的场景选。

骨架屏和空状态(持续开发中,目前效果不太能满足需求)

这两个东西,以前我都是自己手写 SVG 或者拿 div 拼,写一次要十几分钟,而且换个尺寸就得重来。

html 复制代码
<img src="https://cdn.devimg.cn/skeleton/350/120?type=card" />
<img src="https://cdn.devimg.cn/skeleton/800/600?type=grid&cols=3&animate=1" />
<img src="https://cdn.devimg.cn/404" />
<img src="https://cdn.devimg.cn/scene/empty?theme=dark&title=购物车是空的" />

骨架屏有 page / card / row / grid 四种布局,支持明暗主题和 shimmer 动画。空状态有 404、空数据、断网、搜索无结果四种,标题副标题都能自己改成中文。

伪二维码

这个功能是临时起意加上的。主要用户二维码占位,拿真的二维码生成库生成一个吧,又怕真有人拿手机去扫,所以搞个看起来像二维码不是二维码的东西。

html 复制代码
<img src="https://cdn.devimg.cn/qr/checkout/128" />
<img src="https://cdn.devimg.cn/barcode/sku-mock/320/80" />

生成的是长得像 二维码和条形码的图案,不可扫描,响应头里带 X-DevImage-Pseudo-Code 标明身份。纯粹用来占位,不会有人误用。

中文 Mock 数据

路径风格照着 JSONPlaceholder 来的,前缀换成 /mock:

javascript 复制代码
const users = await fetch('https://cdn.devimg.cn/mock/users?_page=1&_limit=10').then(r => r.json());
const post = await fetch('https://cdn.devimg.cn/mock/posts/1').then(r => r.json());

返回的是中文数据,姓名是「张三」,地址是「上海」,文章标题是像样的中文句子。用户、文章、商品三类资源,各 100 条,同一个 id 每次返回一样的内容。

顺带一提,返回里的 avatarcoverimage 字段本身就指向 devimg 的图片接口,所以你 fetch 一次 /mock/posts,拿到的数据直接就能渲染成一个带封面图的完整列表,不用再自己补图。

从 picsum / placehold 迁过来,基本就是改个域名

这是我做兼容的时候花心思最多的地方。已经写在项目里的代码,没人愿意为了换个图床全局重构。

你原来写的 换成
picsum.photos/800/600 cdn.devimg.cn/photo/800/600
picsum.photos/seed/x/800/600 cdn.devimg.cn/seed/x/800/600
picsum.photos/id/1/800/600 cdn.devimg.cn/id/1/800/600
placehold.co/800x600 cdn.devimg.cn/800x600
jsonplaceholder.../users cdn.devimg.cn/mock/users

有一处需要注意:在 devimg 里 /800/600 返回的是合成色块 ,不是照片。想要照片得走 /photo/800/600。这是故意分开的,合成图和真照片本来就是两种需求,混在一起你没法控制拿到的是什么。

几个我自己比较满意的点

全部默认 SVG。 占位图、头像、骨架屏、场景图、码形占位都是服务端合成 SVG,一张几 KB,不用等图片解码,随便放大也不糊。真的需要位图再加 .webp 后缀转码。

seed 一致性是贯穿的。 占位图、头像、照片、二维码全都支持 seed,相同参数必定相同结果,而且响应头是 max-age=31536000, immutable。CI 视觉回归、UI 走查、设计对稿,都不会再出现「图变了」这种事。这条是我踩过坑之后最坚持的设计。

不用注册,不用 Key,没有 SDK。 打开就能用。默认限额 1000 次/分钟/IP,栅格转码 60 次/分钟/IP,正常开发怎么用都够。

部署在国内。 腾讯云 + COS + CDN,这是这个项目存在的全部理由。上面所有功能,别的服务或多或少都有,唯一的差别是打开的时候不用等。

中文第一。 头像认中文首字、Mock 数据是中文、照片题材用中文标注、场景图文案能换成中文。这些细节单拎出来都很小,但拼在一起,你测中文排版的时候会舒服很多。

顺便一提

有些东西还没做完,先讲清楚,免得大家抱着期待点进来然后失望而归:

  • 场景图目前还是文案 SVG,不是插画。 就是配色加一行标题的那种,能用,但不好看。插画版在做了(有没有擅长设计的小伙伴联系我,帮帮我?)。
  • 照片图库还在扩。 目前是 CC0 精选包,量不算大,某些冷门题材翻几页就重复了,争取持续更新,不过就开发阶段而言,目前数量绝对够用,且用不清。
  • Mock 只有三类资源,而且是只读的。 POST / PUT / DELETE 还没支持,嵌套资源(比如文章的评论)也还没有,我主要精力没在这快,但是mock数据这块我有挺大一块版图的,但是我自己一个人搞要好久,尽请期待吧。
  • 一个人在做。 前端、后端、运维、文档全是我自己,所以更新节奏取决于我最近加不加班,有没有在打游戏。(跟我一起搞一下?)。
  • 争取不跑路 用的人少,我服务器资源很宽松会一直运营,如果用的人多了,需要扩服务 ,可能会出 api key 的方式来维持。

最后

这个项目一开始纯粹是为了解决我自己的问题------让组件库示例在不挂代理的情况下,能在一秒内打开。做着做着发现,顺手能解决的问题还挺多,就一路做成了现在这样。

如果你也在被同样的事情烦,或者你有想要但现在还没有的功能(图标?Lottie?更多 Mock 资源?),欢迎来提。

群里我基本都在,有 bug 直接告诉我,有需求也直接说,能做的我尽量排上。

相关推荐
Z小明15 分钟前
第 1 章 Vite 项目初始化
前端·vue.js
IT_陈寒17 分钟前
Redis大KEY删除慢到手抖,这几个方法让我少熬一夜
前端·人工智能·后端
AICoder码农王19 分钟前
depcruise 实战:把架构约定变成可执行的检查
前端
paopaokaka_luck19 分钟前
基于springboot3+vue3+uniapp的河南非遗数字图谱小程序(协同过滤算法、数字图谱展示、ECharts 图形化分析)
java·前端·spring boot·学习·小程序·uni-app·echarts
合橱瑰20 分钟前
Vue3 与 ElementPlus 前端常见错误修复实践
前端·vue.js
PedroQue9921 分钟前
v1.4.0:新增 defineUniPage 宏声明页面配置,架构全面重构
前端·vite
晴天1628 分钟前
Node.js 中 `npm install` 命令分析-Day30
前端·npm·node.js
风之舞_yjf31 分钟前
Vue基础(35)_全局事件总线(GlobalEventBus)
前端·vue.js
程序员小八77740 分钟前
后端转全栈:前端思维转变(Vue 视角)
前端·vue.js·状态模式