OpenGraph 图自动生成 April 28, 2024
什么是 OpenGraph 💡
以下介绍由 Kimi 生成。 大白话就是:这些数据专门告诉社交平台,我们这个网页的基础信息等数据。
OpenGraph 是一种由 Facebook 开发的元标签(meta tags),用于网页上的社交分享。当一个网页链接被分享到社交媒体平台时,这些标签可以提供额外的信息,比如网页的标题、描述、图片等,从而在分享时创建一个更丰富、更吸引人的预览。
OpenGraph 标签通常放在 HTML 页面的 <head>
部分,它们允许社交媒体平台了解页面内容,以便在用户分享链接时生成一个预览。一个典型的 OpenGraph 标签集可能包括:
<meta property ="og:title" content ="The Rock" />
<meta property ="og:type" content ="video.movie" />
<meta property ="og:url" content ="http://www.imdb.com/title/tt0117500/" />
<meta property ="og:image" content ="http://ia.media-imdb.com/rock.jpg" />
<meta property ="og:description" content ="A group of U.S. Marines, under command of a renegade general, take over Alcatraz and threaten San Francisco Bay with biological weapons." />
<meta property ="og:site_name" content ="IMDb" />
og:title
指定了分享内容的标题。og:type
描述了内容的类型。og:url
提供了内容的网址。og:image
指定了分享时使用的图片。og:description
描述了内容的简短介绍。og:site_name
指定了分享内容的网站名称。这些标签对于社交媒体优化(Social Media Optimization, SMO)和提高网页在社交网络上的可见性非常重要。
起因 其中 og-image
部分为了分享到社交媒体时能展示更多的信息和更美观,一些博主们会制作专门的 OG 图片,甚至还有专门生成 OG 图片的工具,如:picprose 、cover-paint 等。
Dayu 行和小胡的 OG 图
不过我觉得每次都要手动制作着实是不太方便,尤其是对于我这种懒人来说,而且如果以前的文章多的话制作起来更是一个大工程了。
所以,有没有简单的工具可以完成这件事情呢?
不用多说,肯定是有的,而且这里又要提到 蜗牛老哥 。
是的,继昨天的热力图后,又一次受到蜗牛老哥传道分享,这次是一款利用 Vercel 自部署,自动根据参数信息生成 OG 图的 API 服务。
第一次看到这个项目是在蜗牛老哥的 部署动态生成 OG Image 的 API 一文,不过当时这个项目还是有缺陷的 —— 不支持中文字库,所有的中文都会变成豆腐块显示,虽然文中提到了可以动态压缩字体,但是当时我还没有开始使用 SSG 工具展示博客,这部分无法实现,所以在测试过几次后就没有再继续尝试了。
这次在热力图弄完之后忽然又想起这个项目来,感觉自己目前这个状态应该够实力完成这个功能了,索性就趁热打铁一起处理了。
依旧是折腾了一下午才弄好,最终在各个社交平台的具体预览效果如下:
我基本上完全参照了 Dayu 的设计样式 思路 我这次的大概思路是
因为博客现在是 11ty,所以我可以在获取完文章数据后,将需要留下的字符串汇总。 利用字体文件压缩库根据第一步获取的字符串精简。 将字体文件上传到网站上去。 Vercel 上的服务通过 URL 请求精简后的字体文件,一般压缩后的字体只有几百 K,这个时候已经完全没有问题。。 成功加载字体后就可以绘制 OG 图了。 实现 因为 Vercel 限制的缘故,在项目运行时无法加载太大的资源文件,所以我们需要将用不到的字体全部删除,这样字体文件自然就减少了,这里压缩字体的代码我参考的这位大佬的 生成动态字体文件
const Fontmin = require ("fontmin" );
module .exports = (titleText ) => {
const fontmin = new Fontmin ()
.src ("assets/SmileySans.ttf" )
.use (
Fontmin .glyph ({
text : titleText,
hinting : false ,
})
)
.dest ("src/assets/fonts" );
fontmin.run ((err, files ) => {
if (err) throw err;
console .log ("compress font success\n" );
});
};
定义压缩字体的函数
config.addCollection ("posts" , async function (collection ) {
collection = await api.posts
.browse ({
include : "tags,authors" ,
limit : loadData,
order : "published_at desc" ,
filter : "visibility:public" ,
})
.catch ((err ) => {
console .error (err);
});
const titleText = collection
.map (function (item ) {
let tags = item.tags .map (tag => tag.name ).join (',' );
let desc = item.excerpt != null ? item.excerpt .substring (0 ,100 ) : '' ;
return item.title + desc +tags;
})
.join ("," );
fontmin (titleText);
return collection;
});
在我 11ty 的数据初始化过程中引用
如果是动态博客可能在这一部就要卡住了,因为我之前用 Ghost 的时候就是卡在这里,但是结合我最近折腾 Github Action 的经验,我这里给的一条可行的方案是:
提供一个拥有全站文章标题、tags、简介的 rss 或 json 文件地址 利用 Github Action 之类的工具在每次文章更新后及时重新生成字体文件。 Vercel 请求 Github Action 中新的字体文件。 之后如果有时间我再来试试看这个方式的可行度。 Vercel 上的部分
import { ImageResponse } from '@vercel/og';
import { NextRequest } from 'next/server';
export const config = {
runtime: 'edge',
};
export default async function handler(request: NextRequest) {
try {
// 请求压缩好的字体文件到缓存中来
const fontData = await fetch(
new URL('https://1900.live/assets/fonts/SmileySans.ttf', import.meta.url),
).then((res) => res.arrayBuffer());
const { searchParams } = new URL(request.url);
// 获取url参数
const originalTitle = searchParams.get('title');
const originalTags = searchParams.get('tags');
const originalDesc = searchParams.get('desc');
const originalDate = searchParams.get('date');
let title = originalTitle;
let tags = originalTags;
let desc = originalDesc;
let date = originalDate;
// 设置各项参数的默认值和长度处理
if (!title) {
title = 'A Hugo blog about Charles Chin.';
} else {
let dot = title.length >= 20 ? '...':'';
title = title.slice(0, 22) + dot;
}
if (!tags){
tags = '分享';
}else{
tags = tags.slice(0, 10);
}
if(!desc){
desc = 'All work and no play makes Jack a dull boy'
}else{
desc = desc.slice(0, 90) + '...';
}
if(!date){
date = '1900/01/01';
}
// 绘制图像
return new ImageResponse(
(
<div style={{
display: 'flex',
flexDirection: 'column',
height: '400px',
width: '800px',
backgroundColor: 'white',
border: 'solid 1px',
justifyContent: 'flex-end'
}}>
<div style={{
display: 'flex',
justifyContent: 'center'
}}>
<span style={{
backgroundColor: 'red',
padding: '5px 15px',
color: 'white',
borderRadius: '5px',
fontStyle: 'italic',
letterSpacing: '3px'
}}>
{tags}
</span>
</div>
<div style={{
display: 'flex',
padding: '40px 0px 25px 0px',
minHeight: '210px',
flexDirection: 'column',
alignItems: 'center',
}}>
<p style={{
fontSize: '50px',
fontWeight: 'bolder',
margin: '0',
fontStyle: 'italic',
textAlign: 'center',
lineHeight: '1.2',
padding: '0 35px'
}}>
{title}
</p>
<p style={{
fontSize: '28px',
fontWeight: 'bolder',
marginTop: '40px',
fontStyle: 'italic',
textAlign: 'center',
padding: '0 35px',
color: '#8b949e',
textIndent: '2em'
}}>
「 {desc} 」
</p>
</div>
<div style={{
display: 'flex',
justifyContent: 'space-between',
marginBottom: '16px'
}}>
<div style={{
display: 'flex',
marginLeft: '20px'
}}>
<img src="https://cdn.1900.live/20190640/ico.png" style={{
width: '50px',
height: '50px',
borderRadius: '50%'
}} alt="icon" />
<div style={{
display: 'flex',
flexDirection: 'column',
lineHeight: '1',
justifyContent: 'center',
fontSize: '14px',
fontStyle: 'italic',
fontWeight: '900',
marginLeft: '13px'
}}>
<span style={{
fontSize: '20px'
}}>
@1900
</span>
</div>
</div>
<div style={{
display: 'flex',
alignItems: 'center'
}}>
<span style={{
fontSize: '20px',
marginRight: '20px',
fontWeight: 'bold',
fontStyle: 'italic'
}}>
{date}
</span>
</div>
</div>
{/* The last div is empty and has no content or styles, so it's not included in the JSX */}
</div>
),
{
width: 800,
height: 400,
fonts: [
{
name: 'SmileySans',
data: fontData,
style: 'italic',
},
],
},
);
} catch (e: any) {
console.log(`${e.message}`);
return new Response(`Failed to generate the image`, {
status: 500,
});
}
}
这部分代码没怎么动脑子,感觉写的很傻
如果你喜欢我这个样式可以直接 Fork 我的项目 部署即可。
一切完成后使用 https://verceldomain.com/api/og
并附带对应的 title、tags、desc、date 调用即可,这里的各个参数一定要 encode
一下,避免出问题。
还有就是,有条件的一定要绑个域名,默认域名好像已经被墙了,无法访问。
示例:
https://og.190102.xyz/api/og?title=%E5%8D%9A%E5%AE%A2%E6%9B%B4%E6%96%B0%E7%83%AD%E5%8A%9B%E5%9B%BE+-+%E5%8F%AA%E6%98%AF%E7%8E%A9%E7%8E%A9+%7C+All+work+and+no+play+makes+Jack+a+dull+boy&desc=%E8%BF%99%E4%B8%AA%E5%8A%9F%E8%83%BD%E6%9C%80%E6%97%A9%E6%98%AF%E7%9C%8B%E5%88%B0%E6%A4%92%E7%9B%90%E8%B1%86%E8%B1%89+%E5%A6%82%E4%BD%95%E7%BB%99+Hugo+%E5%8D%9A%E5%AE%A2%E6%B7%BB%E5%8A%A0%E7%83%AD%E5%8A%9B%E5%9B&date=2024-04-27
这里一定要将传递的参数 encode 一下
TODO 后续看如何加上图片背景 图片目前访问有点慢,有必要在 11ty 生成阶段将图片只是存在本地吗? 分享个 Recat 排版小技巧 因为 vercel 中绘制图片使用的是 React 的,而我又没学过,也不太想深入去学习,所以刚开始画布局的时候各种苦手,不得要领。
后来发现 React 的结构、样式好像和 Html 差不多,所以我就尝试着先在网页上将大概的布局用熟悉的 HTML 画出来,再将得到的 Html 通过 ChatGPT 转换成成 React 代码,再使用 Vercel 提供的 https://og-playground.vercel.app/ 工具调整细节。
整个排版过程一下子就轻松、愉快起来了😄。
加入评论