作为一个摄影爱好者(伪),我一直希望能在博客上方便、快速地展示照片的 EXIF 信息。EXIF(Exchangeable Image File Format)是嵌入在数码照片文件中的一组元数据,它记录了拍摄时的关键信息,例如快门速度、光圈值、ISO、镜头型号、拍摄时间、相机机型等。这些信息不仅能让人回顾拍摄时的状态,也能帮助摄影爱好者更好地分析和改进拍摄技巧。对我而言,它更像是一种“照片的指纹”,让作品多了一层可回味的技术味道。
早些年,我的图片和其他静态文件都托管在七牛云上,当时利用它提供的 EXIF 接口实现信息展示,简单、方便,而且完全没有性能压力(代码附后)。但随着网站访问量逐渐上升,外链流量的费用也水涨船高。考虑到成本和可控性,我最终还是决定停用七牛云,改回自托管的方式,也因此萌生了直接在主题中实现 EXIF 展示的想法。
其实早在这之前,我写过一篇《用 WP Simple EXIF 让博客图片展示更多细节信息》,介绍过一个博友开发的开源插件。那款插件功能相当完善,配置项也非常丰富,本质上同样是基于 PHP 的 EXIF 扩展实现的。不过,它的样式相对花哨,不太符合我博客简洁的视觉风格。于是,这次我借助 AI 的帮助,从零开始按照自己的需求重新调整了一套方案,在保持简洁的同时加入了缓存机制,进一步优化性能。本文就是对这次改造的记录与分享,如果你也想在博客中展示照片的 EXIF 信息,希望这篇内容能给你一些启发。

1. 实现思路
这次实现的核心思路,是在不依赖插件的前提下,让 WordPress 自动为文章中的图片叠加 EXIF 信息。具体做法是通过 the_content 钩子对文章内容进行过滤,利用正则表达式匹配所有 <img> 标签,然后将图片的地址(src)传入自定义的 simple_exif_process_image() 函数中进行处理。
在该函数中,会先尝试将图片的 URL 转换成本地路径,再通过 EXIF 读取函数提取图片的元数据。若数据存在,就拼接成新的 HTML 结构,在图片上方生成一层包含 EXIF 信息的透明条,实现信息展示的自动化与无侵入效果。
2. 读取 EXIF 数据
读取图片信息主要依赖 PHP 内置的 exif_read_data() 函数。为保证兼容与安全,我只针对 JPG、JPEG 和 TIF 格式进行解析。代码中对关键字段进行了提取与格式化,包括相机品牌、型号、镜头、焦距、光圈、快门、ISO 值和拍摄时间。针对部分相机数据不规范的情况,还做了一些兼容性处理:
- 焦距自动计算等效 35mm 数值
- 光圈与快门的分数形式转换为直观可读的格式
- 镜头信息同时支持
UndefinedTag:0xA434与LensModel字段
<?php
// 利用 PHP EXIF 扩展在图像上叠加图片元数据
if (!defined('ABSPATH')) exit;
// 过滤文章内容,自动处理图片
add_filter('the_content', function ($content) {
if (!is_singular() && !in_the_loop()) return $content;
return preg_replace_callback(
'/<img(.*?)src=[\'"](.*?)[\'"](.*?)>/i',
'simple_exif_process_image',
$content
);
}, 20);
/**
* 处理单张图片
*/
function simple_exif_process_image($m)
{
$img_tag = $m[0];
$src = $m[2];
$path = simple_exif_local_path($src);
if (!$path || !file_exists($path)) return $img_tag;
$exif = simple_exif_data($path);
if (empty($exif)) return $img_tag;
return simple_exif_output($img_tag, $exif);
}
/**
* 将 URL 转换为本地路径
*/
function simple_exif_local_path($src)
{
$upload = wp_upload_dir();
if (strpos($src, $upload['baseurl']) === 0) {
$path = str_replace($upload['baseurl'], $upload['basedir'], $src);
return file_exists($path) ? $path : '';
}
return '';
}
/**
* 输出包含 EXIF 信息的 HTML 结构
*/
function simple_exif_output($img, $exif)
{
$brand = isset($exif['make']) ? trim($exif['make']) : '';
$model = isset($exif['model']) ? trim($exif['model']) : '';
$lens = isset($exif['lens']) ? trim($exif['lens']) : '';
$focal = isset($exif['focal_length']) ? strtoupper($exif['focal_length']) : '';
$aperture = isset($exif['aperture']) ? $exif['aperture'] : '';
$shutter = isset($exif['shutter_speed']) ? $exif['shutter_speed'] : '';
$iso = isset($exif['iso']) ? $exif['iso'] : '';
$time = isset($exif['date_taken']) ? $exif['date_taken'] : '';
$sep = ' ';
$text = '相机:' . esc_html($brand . ' ' . $model) . $sep
. '镜头:' . esc_html($lens) . $sep
. '焦距:' . esc_html($focal) . $sep
. '光圈:f/' . esc_html($aperture) . $sep
. '快门:' . esc_html($shutter) . $sep
. 'ISO:' . esc_html($iso) . $sep
. '时间:' . esc_html($time);
return '<div class="wp-simple-exif-container">'
. $img
. '<div class="wp-simple-exif-data">' . $text . '</div>'
. '</div>';
}
/**
* 读取并解析 EXIF 信息
*/
function simple_exif_data($file)
{
if (!function_exists('exif_read_data')) return [];
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
if (!in_array($ext, ['jpg', 'jpeg', 'tif', 'tiff'])) return [];
$exif = @exif_read_data($file);
if (!$exif) return [];
$out = [];
if (!empty($exif['Make'])) $out['make'] = $exif['Make'];
if (!empty($exif['Model'])) $out['model'] = $exif['Model'];
if (!empty($exif['ISOSpeedRatings'])) $out['iso'] = $exif['ISOSpeedRatings'];
if (!empty($exif['DateTimeOriginal'])) $out['date_taken'] = $exif['DateTimeOriginal'];
if (!empty($exif['UndefinedTag:0xA434']))
$out['lens'] = $exif['UndefinedTag:0xA434'];
elseif (!empty($exif['LensModel']))
$out['lens'] = $exif['LensModel'];
// 焦距:优先使用等效 35mm
if (!empty($exif['FocalLengthIn35mmFilm'])) {
$out['focal_length'] = $exif['FocalLengthIn35mmFilm'] . 'MM';
} elseif (!empty($exif['FocalLength'])) {
$f = explode('/', $exif['FocalLength']);
$focal = ($f[1] ?? 1) ? round($f[0] / $f[1]) : $f[0];
$out['focal_length'] = $focal . 'MM';
}
// 光圈
if (!empty($exif['FNumber'])) {
$a = explode('/', $exif['FNumber']);
$out['aperture'] = ($a[1] ?? 1) ? round($a[0] / $a[1], 1) : $a[0];
}
// 快门
if (!empty($exif['ExposureTime'])) {
$s = explode('/', $exif['ExposureTime']);
if (count($s) == 2 && intval($s[1]) > 0)
$out['shutter_speed'] = '1/' . intval($s[1]);
else
$out['shutter_speed'] = $exif['ExposureTime'];
}
return $out;
}
3. 前端展示
前端部分主要通过 .wp-simple-exif-container 容器实现,将图片与 EXIF 信息放在一起,层叠展示。图片上方会出现一条半透明的黑色信息条,显示相机型号、镜头、光圈、快门、ISO、拍摄时间等字段,整体简洁不喧宾夺主。
同时加入了基础的交互动画:鼠标悬停时信息条平滑滑出,图片可轻微放大,既保持低调,又有一定动感。针对移动端,还调整了字体与间距,保证在小屏幕上也能清晰显示。
/* EXIF 信息容器样式 */
.wp-simple-exif-container {
position: relative;
display: inline-block;
max-width: 100%;
margin: 0;
padding: 0;
line-height: 0;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0, 0, 0, .1);
}
.wp-simple-exif-container img {
display: block;
max-width: 100%;
height: auto;
transition: transform .3s ease;
}
/* 信息条样式 */
.wp-simple-exif-data {
position: absolute;
top: -0.2px;
left: 0;
right: 0;
padding: 8px 12px;
background-color: rgba(0, 0, 0, .5);
color: #fff;
font-size: 13px;
line-height: 1.4;
transform: translateY(-100%);
transition: transform .3s ease;
z-index: 2;
text-align: center;
max-height: 60%;
overflow-y: auto;
white-space: pre-wrap;
}
/* 悬停显示效果 */
.wp-simple-exif-container:hover .wp-simple-exif-data {
transform: translateY(0);
}
/* 移动端适配 */
@media screen and (max-width: 600px) {
.wp-simple-exif-data {
font-size: 10px;
padding: 6px;
}
}
/* 可选:悬停放大图片效果 */
/*
.wp-simple-exif-container:hover img {
transform: scale(1.01);
}
*/
4. 与主题兼容
为了让这套 EXIF 展示方案与主题原有的灯箱(lightGallery)功能兼容,我在 main.js 中对灯箱的初始化代码进行了小幅调整,主要是扩展了图片选择器的范围。原本的选择器仅能识别普通的 <a><img></a> 结构,而现在增加了对 .wp-simple-exif-container 包裹图片的识别,从而保证图片在显示 EXIF 信息的同时,依旧可以正常触发灯箱的点击放大、预览与切换功能。
除此之外,我还针对 WordPress 自带的相册模块(Gallery Block)以及部分文章中 img 标签附加 <a> 链接的情况进行了兼容性测试。由于不同主题在渲染图片时的 DOM 层级、class 命名略有差异,适当调整选择器范围与优先级是必要的。目前这套方案在 Impeka 主题中表现稳定,既能保持主题的灯箱交互,也能在各类图片展示场景下顺利加载 EXIF 信息。如果你使用的是其他主题,只需根据其图片容器的 class 名称微调选择器,即可实现同样的兼容效果。

5. 写在最后
整个方案思路清晰、实现轻量,不依赖额外插件,仅用少量 PHP 与 CSS 即可完成。它既能保持主题的简洁风格,又为照片增添了实用的细节展示。后续若要进一步优化,可以加入缓存机制(例如 transient 缓存),或将样式、字体等配置项外部化,方便按需调整。
如果你也希望在博客中为照片添加一点“摄影味”,不妨试试这种方式——简单、优雅、够极客。最后,我把之前用启牛云接口的方案一并附上,你如果恰巧也用的是七牛、百度或阿里 OSS 这类对象存储,可以参考下。
// 以下 JavaScript 代码原用于从七牛 CDN 获取图片 EXIF 信息并插入到页面中
// —— 2025.10.06 起停用七牛,改为使用本地 PHP 插件方案
$(document).ready(function () {
$('#grve-post-content, #grve-single-content')
.find('.wp-block-image img[src*="staticfile.shephe.com"], .wp-block-gallery img[src*="staticfile.shephe.com"]')
.each(function () {
var img = this;
var $el = $(this);
var src = img.src.toLowerCase();
var index = -1;
var ext = '';
if (src.lastIndexOf('.jpg') > -1) {
index = src.lastIndexOf('.jpg');
ext = '.jpg';
} else if (src.lastIndexOf('.jpeg') > -1) {
index = src.lastIndexOf('.jpeg');
ext = '.jpeg';
} else if (src.lastIndexOf('.png') > -1) {
index = src.lastIndexOf('.png');
ext = '.png';
} else {
return; // 非支持格式跳过
}
// 构建七牛 EXIF 请求地址
var exifUrl = src.slice(0, index) + ext + '?exif';
// 请求 EXIF 数据
$.ajax({
url: exifUrl,
success: function (res) {
var exif = res;
var parse = function (attr, label) {
return !attr ? '' : (label ? label + ' ' : '') + attr.value + ' ';
};
var datetime = exif.DateTimeOriginal.val.split(/\:|\s/);
var date = datetime[0] + '-' + datetime[1] + '-' + datetime[2];
var model = exif.Model ? exif.Model.val : '无';
var fnum = exif.FNumber ? exif.FNumber.val.split(/\//)[1] : '无';
var extime = exif.ExposureTime ? exif.ExposureTime.val : '无';
var iso = exif.ISOSpeedRatings ? exif.ISOSpeedRatings.val.split(/,\s/)[0] : '无';
var flength = exif.FocalLength ? exif.FocalLength.val : '无';
var exifData =
'日期:' + date +
' 器材:' + model +
' 光圈:' + fnum +
' 快门:' + extime +
' 感光度:' + iso +
' 焦距:' + flength;
var content =
'<div id="exif-text-margin" class="exif-caption" exif-data="' + exifData + '"></div>';
$el.after(content);
}
});
});
});
/* 显示七牛 EXIF 信息的样式 */
.wp-block-image figure {
position: relative !important;
}
.wp-block-image {
position: relative;
}
.exif-caption {
width: 100%;
}
/* 信息条样式 */
.exif-caption:before {
position: absolute !important;
top: 0;
right: 0;
left: 0;
display: block;
overflow: hidden;
margin: 0 auto;
height: 26px;
max-width: 100%;
background-color: #333;
color: #fff;
content: attr(exif-data);
text-align: center;
font-size: 13px;
line-height: 26px;
opacity: 0;
transition: opacity .5s;
}
/* 悬停效果 */
figure.wp-block-gallery:hover .exif-caption[exif-data]:before {
opacity: 0;
}
.wp-block-image figure:hover .exif-caption[exif-data]:before,
figure.wp-block-image:hover .exif-caption[exif-data]:before {
opacity: .7;
}
/* exif end */
给the_content加钩子没问题。取exif有现成的wp_read_image_metadata()了解一下。