从零到一:做一个拍照定位水印工具

举报
yd_213866132 发表于 2026/08/26 14:50:00 2026/08/26
【摘要】 从零到一:做一个拍照定位水印工具拍照时自动获取 GPS 经纬度,叠加水印到照片上——一个 367 行单文件 HTML 搞定的纯前端小工具。 为什么做这个工具外出拍照时,经常需要记录拍摄位置。手机自带的相机虽然能记录 EXIF 经纬度,但查看不方便,分享给别人时位置信息也不可见。如果能拍照时直接把经纬度写在照片上,一眼就能看到拍摄地点,那多好。于是我做了这个工具:拍照定位水印工具(Geo W...

从零到一:做一个拍照定位水印工具

拍照时自动获取 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 是硬性要求

getUserMediagetCurrentPosition 都要求安全上下文(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

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。