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();
});