#背景
素材库上线后立刻暴露两个问题:
- 手机拍的竖图在页面上横躺着——EXIF 方向信息没有被应用;
- 后台列表直接加载 4000px 的原图当缩略图,一页 20 张就是几十 MB,列表页比首页还慢。
不需要 Imagick 也能解决,GD 够用。
#做法
#1. 先读方向,再决定是否旋转
$exif = @exif_read_data($file); // 生产代码里用 set_error_handler 包裹,不用 @
$orientation = (int) ($exif['Orientation'] ?? 1);
$rotate = match ($orientation) {
3, 4 => 180,
5, 6 => 90, // 6 是"手机竖拍"最常见的情况
7, 8 => 270,
default => 0,
};imagerotate() 之后要特别处理透明通道:旋转会把 PNG 的透明区域变黑,需要 imagesavealpha() + imagealphablending(false)。
#2. 缩放要按最长边算,并且不超过原图
$ratio = min($max_width / $src_width, $max_height / $src_height, 1); // 关键:不超过 1
$dst_width = max(1, (int) round($src_width * $ratio));
$dst_height = max(1, (int) round($src_height * $ratio));
$dst = imagecreatetruecolor($dst_width, $dst_height);
imagecopyresampled($dst, $src, 0, 0, 0, 0, $dst_width, $dst_height, $src_width, $src_height);min(..., 1) 那一项是"不放大":把小图放大到 1200px 只会变糊,还会让文件变大。
#3. 输出优先 WebP,但要能降级
if (function_exists('imagewebp')) {
imagewebp($dst, $target, 82);
} else {
imagejpeg($dst, $target, 85);
}WebP 在同等观感下通常比 JPEG 小 25%~35%,但文件名与 MIME 必须跟着变(.webp + image/webp),否则部分 CDN 会按扩展名推断类型导致下载而不是显示。
#4. 上传阶段就要挡住异常体积
三件事在 store() 里一次做完:
- 像素上限:读
getimagesize(),超过 8000px 直接拒绝(解码 8000×8000 的图要几百 MB 内存,会把 PHP 进程打死); - 体积上限:图片 10MB / 视频 100MB,与
php.ini的upload_max_filesize保持一致并略小于它,否则 PHP 先拦下请求,用户只看到"上传失败"; - 真实类型校验:用
getimagesize()的返回类型判断,不信$_FILES['type'](客户端可伪造)。
#结果
- 列表页缩略图从"原图直出"变成 400px WebP,单张从 1.2MB 降到约 40KB;
- 手机竖拍照片不再横躺;
- 上传异常大图被提前拒绝,而不是让 PHP 崩在解码阶段。
#取舍
- 不在上传时生成多套尺寸:只在首次使用时按需生成并缓存,避免"上传时生成了 5 个尺寸,结果只用得上 1 个"。代价是首次访问略慢(毫秒级)。
- 不引入图像 CDN:对内容量不大的站点,图片处理放在应用内更简单可控;真要上 CDN 时,处理结果已经是标准 WebP,迁移无痛。
- 保留原图:只压缩派生图,原图留档——将来换分辨率或做印刷物料时你会需要它。
评论
还没有评论,来说点什么吧。