头像为什么总是糊的:128px 与 devicePixelRatio
用户上传头像,代码是这样的:
var n = 128;
var c = document.createElement('canvas');
c.width = n; c.height = n;
// 居中裁剪后画进去
var data = c.toDataURL('image/png');
页面里头像的 CSS 尺寸是:
#av{ width:96px; height:96px; border-radius:50%; object-fit:cover; }
看起来很合理:128px 的图显示在 96px 的框里,还留了余量。
漏算了一个东西
96px 是 CSS 像素。手机上还有 devicePixelRatio:
| 设备 | DPR | 96 CSS px 实际需要 | |---|---|---| | 普通屏 | 1 | 96 × 96 | | Retina | 2 | 192 × 192 | | 多数安卓旗舰 | 3 | 288 × 288 |
128px 的源图放在 288 个物理像素的位置上,就是被拉伸了 2.25 倍——必然糊。
改成 384
var n = 384; // 96px × 4,任何 DPR 都够
ctx.fillStyle = '#fff'; // JPEG 不支持透明,先铺白底
ctx.fillRect(0, 0, n, n);
ctx.drawImage(img, sx, sy, s, s, 0, 0, n, n);
var data = c.toDataURL('image/jpeg', 0.92);
为什么用 JPEG 而不是 PNG:384 的 PNG 动辄 300KB+,JPEG q0.92 通常只有 30–80KB,画质差距肉眼几乎看不出。
铺白底这一步不能省。透明 PNG 转 JPEG 时,透明区域会变成黑色,头像会出现一圈黑边。
QQ 头像的另一个坑
第三方返回的头像字段有讲究:
figureurl— 30×30figureurl_qq_1— 40×40figureurl_qq_2— 100×100
很多示例代码拿到第一个能用的 URL 就存了,结果存的是 30×30。显示在 96px 的位置上,放大三倍。
正确做法是优先取大图;如果拿得到 QQ 号,直接用官方的高清接口:
https://q1.qlogo.cn/g?b=qq&nk=<QQ号>&s=640
s 参数可以给到 640,清晰度完全够用。
小结
- 头像尺寸按
CSS 尺寸 × 4准备,别按 1 倍算。 - 有损格式 + 白底,体积能降一个数量级。
- 拿头像时先看字段名,
figureurl和figureurl_qq_2差了十倍像素。