How to Rotate and Deskew Images with JavaScript: CSS, Canvas, and SDK

Sometimes, we may need to rotate an image on a web page, like showing a loading animation. In a document-scanning web app, we need to rotate skewed document images or document images scanned in the wrong direction.

What you’ll build: An HTML5 page that rotates images using three approaches — CSS transform, HTML5 Canvas, and Dynamic Web TWAIN SDK — with support for arbitrary angles, interpolation methods, and automatic skew detection.

Key Takeaways

  • JavaScript can rotate images purely visually via CSS transform: rotate(), or destructively via the HTML5 Canvas API, which produces a new bitmap.
  • The Canvas approach requires computing a bounding rectangle for the rotated image and translating the drawing origin to the canvas center before calling ctx.rotate().
  • Dynamic Web TWAIN’s RotateEx method offloads rotation to a native process, supports multiple interpolation algorithms, and can auto-detect skew angle — ideal for batch document image processing.
  • For production document scanners, EXIF orientation metadata and lossy re-encoding are common pitfalls that the Canvas-only approach does not handle.

Common Developer Questions

  • How do I rotate an image by a custom angle in JavaScript without losing quality?
  • How can I auto-detect and fix a skewed document image in the browser?
  • What is the difference between rotating an image with CSS and rotating with Canvas?

In this article, we are going to talk about three ways to rotate an image with JavaScript:

  • CSS’s Transform Property
  • HTML5’s Canvas
  • Dynamic Web TWAIN, a document-scanning SDK

Prerequisites

To follow along, you need a modern browser (Chrome, Firefox, Edge, or Safari) and a text editor. To use the Dynamic Web TWAIN approach in Step 5, get a 30-day free trial license for Dynamsoft Barcode Reader.

Build an HTML5 Page to Rotate Images

Let’s build an HTML5 page to do this.

Step 1: Create a New HTML File

Create a new HTML file with the following template:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Rotate an Image with JavaScript</title>
  <style>
  .home {
    display: flex;
    align-items: center;
    flex-direction: column;
  }
  </style>
</head>
<body>
  <div class="home">
    <h2>Rotate an Image</h2>
  </div>
  <script></script>
</body>
</html>

Step 2: Load an Image File into the Page

Add an input element for selecting files and use a button element to trigger it. The image file will be loaded into two image elements, one for displaying the rotated result and one for storing the original image.

HTML:

<button id="loadFileButton">Load a File</button>
<input style="display:none;" type="file" id="file" onchange="loadImageFromFile();" accept=".jpg,.jpeg,.png,.bmp" />
<div class="imageContainer">
  <img id="image"/>
  <img id="imageHidden"/>
</div>
<style>
  .imageContainer {
    max-width: 50%;
  }

  #image {
    max-width: 100%;
  }

  #imageHidden {
    display: none;
  }
</style>

JavaScript:

function loadImageFromFile(){
  let fileInput = document.getElementById("file");
  let files = fileInput.files;
  if (files.length == 0) {
    return;
  }
  let file = files[0];
  fileReader = new FileReader();
  fileReader.onload = function(e){
    document.getElementById("image").src = e.target.result;
    document.getElementById("imageHidden").src = e.target.result;
  };
  fileReader.onerror = function () {
    console.warn('oops, something went wrong.');
  };
  fileReader.readAsDataURL(file);
}

Step 3: Rotate an Image Using CSS Transform

We can use the CSS property transform to rotate an HTML element like the following:

element.style.transform = 'rotate(45deg)';

We can also specify the transform-origin CSS property to set the origin of the rotation transform. By default, the origin of a transform is center which we will use.

Let’s implement it in the page.

  1. Add an input element for specifying the rotation angle.

    HTML:

    <label for="degree">Degree (0-360):</label>
    <input type="number" id="degree" name="degree" min="0" max="360" value="0"/>
    
  2. Listen to the change event of the input element to rotate the image with CSS.

    document.getElementById("degree").addEventListener("change",function(e){
      const degree = e.target.value;
      rotateImage(degree);
    })
    
    function rotateImage(degree){
      const image = document.getElementById("image");
      const imageHidden = document.getElementById("imageHidden");
      if (!image.src) {
        return; //no image selected
      }
      image.src = imageHidden.src;
      image.style.transform = 'rotate(' + degree + 'deg)';
    }
    

Using CSS does not actually change the image data, so its processing speed is good. But it may have an overflow problem blocking other elements. We can set the overflow CSS property of its container to auto to avoid blocking other elements but the image will be cut.

Step 4: Rotate an Image Using HTML5 Canvas

HTML5 provides a canvas element which provides a way to manipulate image data. We can use it to actually get a rotated image.

  1. Add a hidden canvas element to the page.

    <canvas id="canvasHidden"></canvas>
    <style>
      #canvasHidden {
        display: none;
      }
    </style>
    
  2. Add a select element to select which rotating method to use.

    <label>
      Method:
      <select id="methodSelect">
        <option>CSS</option>
        <option>Canvas</option>
      </select>
    </label>
    
  3. Rotate an image with canvas when the selected method is “Canvas”.

    if (method == 0) {
      image.src = imageHidden.src;
      image.style.transform = 'rotate(' + degree + 'deg)';
    }else if (method == 1){
      image.style.transform = '';
      rotateImageWithCanvas(degree);
    }
    

Let’s now talk about how to actually rotate an image with canvas.

  1. Get the new size of the rotated image.

    After rotation, the image will have a new size. We have to set the canvas’s size to match the new size. Suppose we need to rotate an image around its center, we can have to following functions to calculate the new size.

    First, get the four corner points and calculate their positions after rotation. Then, get the bounding rect based on the points.

    function getBoundingRect(width,height,degree) {
      let rad = Degree2Rad(degree);
      let points = [{x:0,y:0},{x:width,y:0},{x:width,y:height},{x:0,y:height}];
      let minX = undefined;
      let minY = undefined;
      let maxX = 0;
      let maxY = 0;
      for (let index = 0; index < points.length; index++) {
        const point = points[index];
        const rotatedPoint = getRotatedPoint(point.x,point.y,width/2,height/2,rad);
        if (minX == undefined) {
          minX = rotatedPoint.x;
        }else{
          minX = Math.min(rotatedPoint.x,minX);
        }
        if (minY == undefined) {
          minY = rotatedPoint.y;
        }else{
          minY = Math.min(rotatedPoint.y,minY);
        }
        maxX = Math.max(rotatedPoint.x,maxX);
        maxY = Math.max(rotatedPoint.y,maxY);
      }
      let rectWidth = maxX - minX;
      let rectHeight = maxY - minY;
      let rect = {
        x: minX,
        y: minY,
        width: rectWidth,
        height: rectHeight
      }
      return rect;
    }
    
    function Degree2Rad(degree){
      return degree*Math.PI/180
    }
    
    //https://gamedev.stackexchange.com/questions/86755/how-to-calculate-corner-positions-marks-of-a-rotated-tilted-rectangle
    function getRotatedPoint(x,y,cx,cy,theta){
      let tempX = x - cx;
      let tempY = y - cy;
    
      // now apply rotation
      let rotatedX = tempX*Math.cos(theta) - tempY*Math.sin(theta);
      let rotatedY = tempX*Math.sin(theta) + tempY*Math.cos(theta);
    
      // translate back
      x = rotatedX + cx;
      y = rotatedY + cy;
      let point = {x:x,y:y};
      return point;
    }
    
  2. Set the canvas’s size as the rotated image’s size.

    const canvas = document.getElementById("canvasHidden");
    const imgWidth = imageHidden.naturalWidth;
    const imgHeight = imageHidden.naturalHeight;
    const rect = getBoundingRect(imgWidth,imgHeight,degree);
    canvas.width = rect.width;
    canvas.height = rect.height;
    
  3. Get the context of the canvas to perform actions.

    const ctx = canvas.getContext("2d");
    
  4. Use translate to set the new (0,0) position as the center of the canvas.

    ctx.translate(canvas.width/2,canvas.height/2);
    
  5. Use rotate to set the transformation matrix.

    ctx.rotate(Degree2Rad(degree));
    
  6. Use drawImage to draw the image content.

    ctx.drawImage(imageHidden, -imgWidth/2, -imgHeight/2);
    

    We need to specify the x-axis and y-axis coordinates in the destination canvas at which to place the top-left corner of the source image. So here, since the new origin is the center of the canvas, we need to use -imgWidth/2 and -imgHeight/2.

  7. Display the rotated image.

    document.getElementById("image").src = canvas.toDataURL();
    

Step 5: Rotate and Deskew with Dynamic Web TWAIN

Dynamic Web TWAIN is a document scanning SDK making it possible to scan documents in the browser. It provides various image processing methods. We can use its RotateEx method to rotate an image.

The advantage of using it is that it can be used to batch process a large volume of images as the processing is done using a native process.

Here are the steps to use it:

  1. Include Dynamic Web TWAIN in the page.

    <script src="https://unpkg.com/dwt@18.4.2/dist/dynamsoft.webtwain.min.js"></script>
    
  2. Add Dynamic Web TWAIN as a new rotation method.

    <label>
      Method:
      <select id="methodSelect">
        <option>CSS</option>
        <option>Canvas</option>
        <option>Dynamic Web TWAIN</option>
      </select>
    </label>
    
  3. When it is selected as the method for rotation, initialize an instance of Web TWAIN and use it to rotate the image. You can apply for its license here.

    let DWObject;
    Dynamsoft.DWT.AutoLoad = false;
    Dynamsoft.DWT.ProductKey = "LICENSE-KEY"; 
    Dynamsoft.DWT.ResourcesPath = "https://unpkg.com/dwt@18.4.2/dist";
    async function rotateImage(degree){
      const method = document.getElementById("methodSelect").selectedIndex;
      const image = document.getElementById("image");
      const imageHidden = document.getElementById("imageHidden");
      if (!image.src) {
        return;
      }
      if (method == 0) {
        image.src = imageHidden.src;
        image.style.transform = 'rotate(' + degree + 'deg)';
      }else if (method == 1){
        image.style.transform = '';
        rotateImageWithCanvas(degree);
      }else if (method == 2){
        image.style.transform = '';
        if (!DWObject) {
          await init();
        }
        rotateImageWithDWT(degree);
      }
    }
    function init(){
      return new Promise((resolve, reject) => {
        const title = document.querySelector("h2").innerText;
        document.querySelector("h2").innerText = "Loading Dynamic Web TWAIN...";
        Dynamsoft.DWT.CreateDWTObjectEx(
        {
          WebTwainId: 'dwtcontrol'
        },
        function(obj) {
          DWObject = obj;
          document.querySelector("h2").innerText = title;
          resolve();
        },
        function(err) {
          console.log(err);
          document.querySelector("h2").innerText = "Failed to load Dynamic Web TWAIN";
          reject(err);
        }
      );
      })
    }
        
    function rotateImageWithDWT(degree){
      return new Promise(async (resolve, reject) => {
        if (DWObject) {
          DWObject.RemoveAllImages();
          let file = document.getElementById("file").files[0];
          let buffer = await file.arrayBuffer();
          DWObject.LoadImageFromBinary(buffer,
          function(){
            const method = document.getElementById("interpolationMethodSelect").selectedOptions[0].value;
            DWObject.RotateEx(0,degree,false,method,
            function(){
              document.getElementById("image").src = DWObject.GetImageURL(0);
            },
            function(errorCode, errorString){
              reject(errorString);
            });
          },
          function(errorCode, errorString){
            reject(errorString);
          })
             
        }else{
          reject();
        }
      })
    }
    
  4. New pixels have to be added during rotation which is called interpolation. Dynamic Web TWAIN provides several algorithms to do this.

    A select element is added for this.

    <div class="dwtcontrols">
      <label>
        Interpolation Method:
        <select id="interpolationMethodSelect">
          <option value="1">Nearest Neighbour</option>
          <option value="2">Bilinear</option>
          <option value="3">Bicubic</option>
          <option value="5" selected>Best Quality</option>
        </select>
      </label>
    </div>
    
  5. Web TWAIN also has a function to detect the skew angle of a document image. We can use it to determine the angle for rotation.

    DWObject.GetSkewAngle(
      0,
      function(angle) {
        document.getElementById("degree").value = 360+angle;
        rotateImage(360+angle);
      },
      function(errorCode, errorString) {
        console.log(errorString);
      }
    );
    

Common Issues and Edge Cases

  • EXIF orientation ignored: JPEG photos from phones often embed EXIF orientation data. The Canvas drawImage call does not honour EXIF, so the image may appear rotated before you even apply your own rotation. Read the EXIF orientation tag first and compensate.
  • Canvas toDataURL re-encodes as PNG by default. For large photos this produces very large files. Pass 'image/jpeg' and a quality value (e.g., canvas.toDataURL('image/jpeg', 0.92)) to keep file size manageable.
  • CSS rotation overflows its container. Rotating by non-90° angles can push the image outside its parent div, overlapping other elements. Set overflow: auto (or hidden) on the container and be aware that the image may be clipped.

Source Code

You can find all the code in the following repo:

https://github.com/tony-xlh/Rotate-Image-JavaScript