网站链接在微信里变成一张图文卡片

昨晚我把博客链接发到微信里,对面收到的是一串光秃秃的蓝字。我明明见过别人家的链接发出来是一张带标题、带摘要、带缩略图的卡片,为什么我的就是一串网址?

查了一圈才发现,这两者之间隔着的不是公众号、不是 JS-SDK、也不是什么后端签名,而只是网页 <head> 里的几个标签——外加一张方形的图。

先看"之前"长什么样。下面这张图是我把三个链接原样粘进聊天框的结果,全部按纯文本处理:

直接粘贴的链接在微信里是纯文本

原理:微信在看 head 里的几个标签

微信内置浏览器打开网页时会读一组叫 Open Graph 的标签,卡片上的内容就来自它们:

<meta property="og:title" content="页面标题">
<meta property="og:description" content="页面描述">
<meta property="og:image" content="https://你的域名.com/share.jpg">
<meta property="og:url" content="https://你的域名.com/当前页面.html">
<meta property="og:type" content="website">

有这些标签,微信就能拼出一张卡片:标题、摘要、缩略图各就各位。没有,它只能退化成一条纯文本链接。整个过程不需要公众号,不需要小程序,也不需要后端生成签名——文章里流传的"要接 JS-SDK 才有卡片"是老黄历了。

我的博客只缺一张图

用 view-source 看自己博客的文章页,发现 Typecho 1.3 的核心其实已经替我输出了一大半:

<meta property="og:type" content="article" />
<meta property="og:url" content="https://kx.gy/archives/kx-park-manual.html" />
<meta name="twitter:title" property="og:title" content="KX-Park 主题手册" />
<meta name="twitter:description" property="og:description" content="Typecho 个人生活博客主题 · …" />
<meta property="og:site_name" content="开心公园" />
<meta name="twitter:card" content="summary" />

标题、描述、地址全齐,唯独没有 og:image——而它恰好是决定卡片上有没有图的那一项。缺了它,微信最多给你一个纯文字卡片。

最简做法:加五五行,任何网站通用

如果你的网站是手写的静态页,把开头那几行 meta 补进 <head> 就完事了。只有三点要注意,全是微信自己的脾气:

  • og:image 必须是 https 开头的绝对路径,相对路径微信抓不到;
  • 图用 JPG 或 PNG,别用 WebP——微信对它的支持没有保证;
  • 尺寸建议 478×478 的正方形,2MB 以内。聊天列表里的缩略图是方的,横图会被它居中裁掉两边。

如果你的博客用模板引擎,那就别一页一页手填,在模板层按"当前页标题 / 摘要 / 封面图"动态输出即可。

我嫌手动填图,让主题自己裁

我的情况更懒一点:每篇文章都有封面,但封面全是 1912×948 的横图,直接交给微信会被裁成两侧各丢一半;而且它们都是 WebP,还托管在图床上,抓不抓得动要看图床脸色。

于是干脆在主题里加了个小端点:把登记过的封面图取回来,居中裁成 478×478,编码成 JPEG 再缓存,og:image 直接指向这张本站自己的方图。核心逻辑拢共几十行:

$img = imagecreatefromstring($data);          // 拉回来的封面原图
$side = min(imagesx($img), imagesy($img));    // 取短边
$sx = (int) ((imagesx($img) - $side) / 2);
$sy = (int) ((imagesy($img) - $side) / 2);
$out = imagecreatetruecolor(478, 478);
$white = imagecolorallocate($out, 255, 255, 255);
imagefilledrectangle($out, 0, 0, 478, 478, $white);
imagecopyresampled($out, $img, 0, 0, $sx, $sy, 478, 478, $side, $side);
imagejpeg($out, null, 88);                    // 输出正方形 JPEG

中间还踩了个小坑:站点 Logo 是带透明通道的 PNG,而 JPEG 没有透明通道,不先铺一层白底直接编码,透明区会变成一整块黑——Logo 直接糊没了。

header.php 里再补上输出(尺寸声明只在确实裁出方图时给,生成不了就退回原图地址,让微信自己看):

<meta property="og:image" content="<?php echo $share; ?>">
<meta property="og:image:type" content="image/jpeg">
<meta property="og:image:width" content="478">
<meta property="og:image:height" content="478">

效果

首页和文章页的卡片分别长这样,缩略图就是主题自己裁出来的那张方图:

首页链接的卡片

文章链接的卡片

三个坑,跟代码无关

第一,直接粘贴的链接永远是纯文本。 这不是标签没写对——微信对粘贴进来的链接默认按纯文本处理。要先用微信内置浏览器打开过一次:把链接发给文件传输助手,长按选「搜一搜」,从搜索结果里「访问网页」,之后再分享就是卡片了。微信搜索相当于替这个链接做了一次内容抓取。

第二,新域名可能被拦。 域名比较新、或者第一次在微信里传播时,可能弹出「无法确认该网页的安全性,请谨慎访问」。这是微信对新域名的安全机制,去 urlsec.qq.com 提交申诉,说明用途,一般一到两个工作日解除。

第三,微信会缓存卡片。 某个链接在被修好之前分享过一次,之后可能一直显示旧样子。给它加个 ?v=2 之类的参数再发,就能逼微信重抓一次。


这些逻辑从 KX-Park 1.17.38 起已经内置:封面自动裁方、站点分享图可在后台设置,主题用户更新即可。这篇文章发出去的时候,卡片右侧那张方形小图,就是它自己裁的。