Skip to main content

How to Rotate and Flip Images Online with JavaScript Canvas

A simple image rotation and flip tool is one of the most common canvas utilities. With Konva you can load any image, rotate it by 90-degree steps, flip it horizontally or vertically, and export the result — all in the browser with no server needed.

Instructions: Click "Load Image" to upload a photo (or use the default). Use the buttons to rotate 90° clockwise/counter-clockwise, flip horizontally or vertically. Click "Save as PNG" to download the result.

import Konva from 'konva';

// --- Controls ---
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 = 'Load Image';
const rotateCW = document.createElement('button');
rotateCW.textContent = 'Rotate 90° →';
const rotateCCW = document.createElement('button');
rotateCCW.textContent = '← Rotate 90°';
const flipH = document.createElement('button');
flipH.textContent = 'Flip Horizontal';
const flipV = document.createElement('button');
flipV.textContent = 'Flip Vertical';
const saveBtn = document.createElement('button');
saveBtn.textContent = 'Save as PNG';
const resetBtn = document.createElement('button');
resetBtn.textContent = 'Reset';

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

// --- Stage ---
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);

// checkerboard background to show transparency
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();

    // fit image to stage
    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;
}

// Load default image
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;
  // hide checkerboard and export only the image area
  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();
});