#编码 , #分享 , #工具箱
什么是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 ,
})
)
// 文件生成成后放在网站的资源目录中,Rollup在后面会复制到dist目录里去
. dest ( " src/assets/fonts " );
// 开始精简字体
fontmin . run (( err , files ) => {
if ( err ) throw err ;
console . log ( " compress font success \n " );
});
};
定义压缩字体的函数
// Get all posts
config . addCollection ( " posts " , async function ( collection ) {
// 获取Ghost博客的所有文章
collection = await api . posts
. browse ({
include : " tags,authors " ,
limit : loadData ,
order : " published_at desc " ,
filter : " visibility:public " ,
})
. catch (( err ) => {
console . error ( err );
});
// 获取所有文章的tags字符串、title字符串、desc简介字符串
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 ( " , " );
// 调用之前定义的压缩函数,将所有文章的标题、tags、简介字符传进去
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/ 工具调整细节。
整个排版过程一下子就轻松、愉快起来了😄。
加入评论