纯前端实现主题对比图生成器,不用再手动拼 PS 了
起因
最近在整理项目文档,想展示应用的亮色和暗色主题对比。本来想用 Photoshop 做,但想想每次都要重复操作太烦了。在线工具试了几个,要么收费要么要上传图片。
干脆自己写一个吧。
最终效果
在线体验:tageecc.github.io/theme-merge…
核心功能:
- 上传两张图(亮色/暗色)
- 对角线分割合并
- 角度可调(-45° 到 45°)
- 支持剪贴板粘贴
- 本地处理,不上传服务器
技术实现
技术选型
考虑到要快速实现,选了最简单的方案:
- HTML5 Canvas 处理图片
- 纯 JavaScript,不依赖任何库
- GitHub Pages 托管
目标是打开浏览器就能用。
核心算法
对角线分割的关键是 Canvas 的 clip() 方法:
function drawMergedImage() {
// 先画暗色主题作为底图
ctx.drawImage(darkImg, 0, 0);
// 保存状态
ctx.save();
// 创建对角线裁剪路径
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(canvas.width, 0);
ctx.lineTo(x1, y1); // 计算旋转后的端点
ctx.lineTo(x2, y2);
ctx.lineTo(0, canvas.height);
ctx.closePath();
// 应用裁剪并画亮色主题
ctx.clip();
ctx.drawImage(lightImg, 0, 0);
ctx.restore();
}
角度计算这块稍微有点绕,用了基础的三角函数:
const angle = parseFloat(angleSlider.value);
const angleRad = (angle * Math.PI) / 180;
const diagonal = Math.sqrt(canvas.width ** 2 + canvas.height ** 2);
const baseAngle = Math.atan2(canvas.height, canvas.width);
// 计算旋转后的对角线端点
const x1 = canvas.width + diagonal * Math.cos(baseAngle + angleRad);
const y1 = -diagonal * Math.sin(baseAngle + angleRad);
const x2 = -diagonal * Math.cos(baseAngle + angleRad);
const y2 = canvas.height + diagonal * Math.sin(baseAngle + angleRad);
交互优化
一开始只支持文件上传,后来加了几个改进:
1. 点击左右区域分别上传
判断点击位置在对角线哪一边:
function isPointOnLightSide(x, y) {
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const dx = x - centerX;
const dy = y - centerY;
// 旋转坐标系判断位置
const rotatedX = dx * Math.cos(-angleRad) - dy * Math.sin(-angleRad);
const rotatedY = dx * Math.sin(-angleRad) + dy * Math.cos(-angleRad);
return rotatedY < rotatedX * baseSlope;
}
2. 支持剪贴板粘贴
这个功能提升很大,现在截图后直接 Ctrl+V 就行:
document.addEventListener('paste', (e) => {
const items = e.clipboardData?.items;
for (let i = 0; i < items.length; i++) {
if (items[i].type.startsWith('image/')) {
const file = items[i].getAsFile();
loadImageToSide(file, determineSide());
break;
}
}
});
遇到的坑
坑1:Canvas 尺寸问题
一开始用 CSS 设置 canvas 大小,结果图片糊了。后来才知道:
- CSS 控制显示大小
- width/height 属性控制实际分辨率
坑2:大图卡顿
上传 4K 图片时,如果自己算每个像素会很慢。解决方案是直接用 Canvas 的 clip(),让浏览器处理裁剪,比自己算快多了。
坑3:移动端适配
手机上 canvas 会超出屏幕。用了 max-width 和 max-height 的 CSS:
canvas {
max-width: 90vw;
max-height: 70vh;
width: auto;
height: auto;
}
显示时缩放,下载时保持原分辨率。
使用场景
这个工具特别适合:
- 技术文档 - 展示功能的不同主题
- 产品介绍 - 展示 UI 设计稿
- 博客配图 - 写技术文章时的对比图
- 开源项目 README - 展示项目效果
改进空间
现在够用了,但还有些可以优化的:
- 垂直分割模式 - 除了对角线,加个上下分割
- 更多导出格式 - 现在只支持 PNG,可以加 JPG、WebP
- 批量处理 - 一次处理多组图片
- 预设模板 - 保存常用的角度设置
总结
整个项目花了大概一个小时,代码不到 500 行。Canvas 的 clip() 确实挺好用的,配合基础的三角函数就能实现不错的效果。
如果你也需要做主题对比图,可以试试:
在线使用:tageecc.github.io/theme-merge…
开源代码:github.com/tageecc/the…
代码写得比较简单,有问题欢迎提 issue 或者直接改。
如果觉得有用,欢迎点赞收藏!也可以给 GitHub 项目一个 ⭐