Skip to main content

使用 JavaScript Canvas 在线旋转和翻转图片

简单的图片旋转和翻转工具是最常见的 canvas 实用功能之一。使用 Konva,你可以加载任意图片,以 90 度为步进进行旋转,水平或垂直翻转,并导出结果——ทั้งหมด都在浏览器中完成,无需服务器。

说明: 点击“加载图片”以上传照片(或使用默认图片)。使用按钮顺时针/逆时针旋转 90°,水平或垂直翻转。点击“另存为 PNG”下载结果。

import Konva from 'konva';

// --- 控件 ---
const controls = document.createElement('div');
controls.style.cssText = 'display:flex;gap:6px;align-items:center;margin-bottom:4px;flex-wrap:wrap;';

const loadBtn = document.createElement('button');
loadBtn.textContent = '加载图片';
const rotateCW = document.createElement('button');
rotateCW.textContent = '顺时针旋转 90° →';
const rotateCCW = document.createElement('button');
rotateCCW.textContent = '← 逆时针旋转 90°';
const flipH = document.createElement('button');
flipH.textContent = '水平翻转';
const flipV = document.createElement('button');
flipV.textContent = '垂直翻转';
const saveBtn = document.createElement('button');
saveBtn.textContent = '保存为 PNG';
const resetBtn = document.createElement('button');
resetBtn.textContent = '重置';

const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'image/*';
fileInput.style.display = 'none';

[loadBtn, rotateCCW, rotateCW, flipH, flipV, saveBtn, resetBtn].forEach(b => controls.appendChild(b));
controls.appendChild(fileInput);
const container = document.getElementById('container');
container.parentNode.insertBefore(controls, container);

// --- 画布 ---
const stageWidth = window.innerWidth;
const stageHeight = window.innerHeight - 40;

const stage = new Konva.Stage({
container: 'container',
width: stageWidth,
height: stageHeight,
});

const bgLayer = new Konva.Layer();
stage.add(bgLayer);

// 用棋盘格背景显示透明度
const gridSize = 20;
for (let x = 0; x < stageWidth; x += gridSize) {
for (let y = 0; y < stageHeight; y += gridSize) {
const isEven = ((x / gridSize) + (y / gridSize)) % 2 === 0;
if (!isEven) {
bgLayer.add(new Konva.Rect({
x, y, width: gridSize, height: gridSize,
fill: '#f0f0f0', listening: false,
}));
}
}
}

const layer = new Konva.Layer();
stage.add(layer);

let konvaImage = null;
let currentRotation = 0;
let currentScaleX = 1;
let currentScaleY = 1;

function loadImage(src) {
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = function () {
if (konvaImage) konvaImage.destroy();

// 将图片适配到画布
const ratio = Math.min(
(stageWidth - 40) / img.width,
(stageHeight - 40) / img.height,
1
);
const w = img.width * ratio;
const h = img.height * ratio;

currentRotation = 0;
currentScaleX = 1;
currentScaleY = 1;

konvaImage = new Konva.Image({
image: img,
x: stageWidth / 2,
y: stageHeight / 2,
width: w,
height: h,
offsetX: w / 2,
offsetY: h / 2,
rotation: 0,
});
layer.add(konvaImage);
};
img.src = src;
}

// 加载默认图片
loadImage('https://konvajs.org/assets/darth-vader.jpg');

loadBtn.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (ev) => loadImage(ev.target.result);
reader.readAsDataURL(file);
});

rotateCW.addEventListener('click', () => {
if (!konvaImage) return;
currentRotation += 90;
konvaImage.rotation(currentRotation);
});

rotateCCW.addEventListener('click', () => {
if (!konvaImage) return;
currentRotation -= 90;
konvaImage.rotation(currentRotation);
});

flipH.addEventListener('click', () => {
if (!konvaImage) return;
currentScaleX *= -1;
konvaImage.scaleX(currentScaleX);
});

flipV.addEventListener('click', () => {
if (!konvaImage) return;
currentScaleY *= -1;
konvaImage.scaleY(currentScaleY);
});

resetBtn.addEventListener('click', () => {
if (!konvaImage) return;
currentRotation = 0;
currentScaleX = 1;
currentScaleY = 1;
konvaImage.rotation(0);
konvaImage.scaleX(1);
konvaImage.scaleY(1);
});

saveBtn.addEventListener('click', () => {
if (!konvaImage) return;
// 隐藏棋盘格,仅导出图片区域
bgLayer.hide();
const rect = konvaImage.getClientRect();
const dataURL = stage.toDataURL({
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
pixelRatio: 2,
});
const link = document.createElement('a');
link.download = 'rotated-image.png';
link.href = dataURL;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
bgLayer.show();
});