从零到一:做一个拍照定位水印工具
从零到一:做一个拍照定位水印工具
拍照时自动获取 GPS 经纬度,叠加水印到照片上——一个 367 行单文件 HTML 搞定的纯前端小工具。
为什么做这个工具
外出拍照时,经常需要记录拍摄位置。手机自带的相机虽然能记录 EXIF 经纬度,但查看不方便,分享给别人时位置信息也不可见。
如果能拍照时直接把经纬度写在照片上,一眼就能看到拍摄地点,那多好。
于是我做了这个工具:拍照定位水印工具(Geo Watermark Photo Tool)。
效果
打开手机浏览器 → 授权摄像头和定位 → 拍照 → 照片左下角自动出现:
Lat: 31.230416, Lng: 121.473701
2026-08-26 14:30:25
半透明黑底白字,任何背景下都清晰可读。一键下载为 PNG。
技术选型:为什么是纯前端
这个工具的核心需求就三个:拍照、定位、画水印。每一个浏览器都有原生 API 支持:
| 需求 | 浏览器 API | 说明 |
|---|---|---|
| 拍照 | navigator.mediaDevices.getUserMedia() |
访问摄像头,拿到视频流 |
| 定位 | navigator.geolocation.getCurrentPosition() |
获取 GPS 经纬度 |
| 水印 | Canvas 2D API | 在画布上绘制图片和文字 |
不需要后端服务器,不需要数据库,不需要任何框架。一个 index.html 文件就够了。
零依赖意味着:没有 npm install,没有构建步骤,没有部署配置。写完就能用,双击就能开。
核心实现
1. 打开摄像头
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'environment' } // 优先后置摄像头
});
videoElement.srcObject = stream;
videoElement.play();
facingMode: 'environment' 告诉浏览器优先使用后置摄像头——手机上拍照用后置摄像头更自然。
2. 拍照 + 定位同步
点击拍照按钮时,同时执行两件事:
// 从视频流截取一帧到 canvas
canvas.getContext('2d').drawImage(videoElement, 0, 0, width, height);
// 同时请求 GPS 定位
navigator.geolocation.getCurrentPosition(
position => {
const lat = position.coords.latitude;
const lng = position.coords.longitude;
// 拿到经纬度后画水印
drawWatermark(canvas, lat, lng);
},
error => { /* 处理定位失败 */ },
{ enableHighAccuracy: true, timeout: 10000 }
);
两个操作并行执行:Canvas 截图是同步的,GPS 定位是异步的。拿到经纬度后立刻在 canvas 上画水印。
3. 画水印
function drawWatermark(canvas, lat, lng) {
const ctx = canvas.getContext('2d');
const padding = 10;
const lineHeight = 24;
const fontSize = 16;
// 水印文字
const line1 = `Lat: ${lat.toFixed(6)}, Lng: ${lng.toFixed(6)}`;
const line2 = formatTimestamp(new Date());
// 计算背景矩形大小
const maxWidth = Math.max(
ctx.measureText(line1).width,
ctx.measureText(line2).width
) + padding * 2;
const bgHeight = lineHeight * 2 + padding * 2;
// 画半透明黑色背景
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
ctx.fillRect(padding, canvas.height - bgHeight - padding,
maxWidth, bgHeight);
// 画白色文字
ctx.fillStyle = '#ffffff';
ctx.font = `${fontSize}px sans-serif`;
ctx.fillText(line1, padding * 2, canvas.height - bgHeight);
ctx.fillText(line2, padding * 2, canvas.height - bgHeight + lineHeight);
}
关键点:先画半透明黑色背景,再画白色文字。这样无论照片背景是亮还是暗,水印都清晰可读。
4. 下载
const dataUrl = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.download = `geo-photo-${Date.now()}.png`;
link.href = dataUrl;
link.click();
canvas.toDataURL() 把画布内容转为 PNG 数据流,创建一个临时下载链接,点击即下载。
踩过的坑
坑 1:HTTPS 是硬性要求
getUserMedia 和 getCurrentPosition 都要求安全上下文(HTTPS 或 localhost)。在 HTTP 下直接调用会被浏览器拒绝。
这意味着这个工具不能直接在局域网用 IP 访问,必须部署到 HTTPS 环境,或者通过开发隧道访问。
坑 2:定位权限和摄像头权限是分开的
用户授权了摄像头,不等于授权了定位。两个权限需要分别请求,分别处理拒绝的情况。
我的做法是:拍照时先截取画面,然后请求定位。如果定位失败,照片依然保留,只是水印部分显示"定位不可用"。
坑 3:后置摄像头不是所有设备都有
桌面电脑只有前置摄像头,facingMode: 'environment' 会失败。需要做 fallback:
try {
stream = await getUserMedia({ video: { facingMode: 'environment' } });
} catch {
stream = await getUserMedia({ video: true }); // 回退到默认摄像头
}
UI 设计
采用了卡片式布局 + 渐变背景的设计风格:
- 背景:
linear-gradient(135deg, #667eea 0%, #764ba2 100%)紫色渐变 - 卡片:白色背景,圆角 18px,大阴影
- 按钮:渐变色,hover 有微动画
- 移动适配:
max-width: 560px,viewport 锁定缩放
整个 UI 没有用任何 CSS 框架,纯手写。单文件应用的好处就是不用操心样式冲突和依赖加载顺序。
项目结构
geo-watermark-photo/
├── index.html # 全部代码(HTML + CSS + JS 内嵌)
├── README.md # 项目说明
└── blog/
└── making-geo-watermark-photo.md # 本篇博客
没有 package.json,没有 node_modules/,没有构建配置。整个项目 3 个文件,核心代码 367 行。
总结
这个项目证明了:不是所有应用都需要 React + Node + 数据库。有些工具,一个 HTML 文件就够了。
浏览器原生 API 已经非常强大——摄像头、定位、Canvas、文件下载,这些能力组合起来可以做很多实用的小工具。
下次有类似的需求,先问问自己:这个真的需要后端吗? 也许浏览器就能搞定。
Powered by Huawei Cloud
- 点赞
- 收藏
- 关注作者
评论(0)