TrueCropper.js

Lightweight, real-size image, vanilla JS image cropping tools

Features:

  • Made only with native, delicious vanilla JS
  • Zero dependencies
  • No z-index, make modals easy
  • Work with real image pixel size
  • Supports touch devices
  • Set results in dataset of image

Installation

npm install truecropper

Basic usage

<img src="image.jpg" id="myImageCropper" />

<script>
// import the library
import TrueCropper from "truecropper";
import "truecropper/dist/truecropper.css"; // or import "truecropper.scss"
//.. other code ..//
const cropper = new TrueCropper("#myImageCropper");
const result = cropper.getValue();
</script>

Working with Image Sizes in the Library

In a browser, each image has two types of sizes:

  • Original size — the actual resolution of the image (e.g., 2048×2048).
  • Displayed size — the size at which the image is rendered on the page (e.g., 512×512).

Getting Image Sizes in JavaScript

  • Displayed size can be obtained using Image.width.
  • Original size (Image.naturalWidth and Image.naturalHeight) becomes available only after the image has loaded. Attempting to access it earlier will return 0, which may cause a race condition. This is particularly important when initializing tools such as cropping.

Unlike other cropping tools, the library provides three modes for working with image sizes:

  • Real (real) — operates with pixels of the original image (naturalWidth, naturalHeight).
  • Percentage-based (percent) — works with percentages of the original size.
  • Relative (relative) — uses pixels based on the displayed size of the image on the page.

Cropping Initialization

When initialized, the cropper modifies the DOM by replacing the <img> element with a <div class="truecropper">. It then adds the original <img> inside this <div>. To prevent this behavior, add the truecropper class to the parent element.

<div class="truecropper"><img src="image.jpg" id="myImageCropper"/></div>

Component Settings

Settings can be specified in two ways:

Method 1

Create an instance of the component by passing an object with the settings, for example:

const cropper = new TrueCropper("#imageId", { aspectRatio: 500/400, ... });
Method 2

Specify the parameters directly in the dataset attribute of the element with the id imageId, for example:

<img src="..." data-truecropper-aspect-ratio="0.4" />

In this case, the settings specified via the dataset have higher priority.

Retrieve Crop Region

The library supports two methods for obtaining the crop result. This flexibility allows you to choose the method that best fits your application's needs.

Method 1: Use the getValue() method.

When you call getValue() on your cropper instance, it returns an object containing the current crop region’s coordinates and dimensions. This method is ideal if you want a simple, programmatic way to get the crop data.

const cropper = new TrueCropper("#myImage", {});
const result = cropper.getValue();
// result might be: { x: 10, y: 20, width: 200, height: 150 }

Method 2: Retrieve the values directly from the image's dataset.

The crop values are stored as data attributes on the image element. You can access these values by querying the element’s dataset. This method is useful if you need to work directly with the DOM or integrate with other scripts that expect data attributes.

const dataset = document.querySelector("#myImage").dataset;
const cropData = {
  x: dataset.truecropperX,
  y: dataset.truecropperY,
  width: dataset.truecropperWidth,
  height: dataset.truecropperHeight
};

Notes on Percentage Values

If you use percentage values for the crop region, note that the cropper does not return a decimal (e.g., 0.8) but rather the whole number percentage (e.g., 80), representing 80%.

Similarly, when using the setValue method to define the crop region, you should provide percentage values as integers (for example, 80) rather than as decimals (for example, 0.8).

Crop Component Parameters: startSize vs. defaultSize

The startSize parameter is used during the initial initialization of the crop component to set the starting dimensions and position of the cropping area. The defaultSize parameter, on the other hand, is used to initialize the component when a new image is loaded.

If defaultSize is provided and startSize is not, then defaultSize will be used as the initial settings.

So why is the startSize parameter needed? Consider the following example. Suppose you have an avatar upload form where the default crop settings are defined as:

{ x: 0, y: 0, width: 50%, height: 50% }.

A user uploads an image with crop settings of:

{ x: 10%, y: 10%, width: 50%, height: 50% }.

Later, when the edit form is opened, you need to display the crop settings { x: 10%, y: 10%, width: 50%, height: 50% } that the user previously set. However, if the image is changed, you want to revert to the default crop settings, for example with centered coordinates and dimensions { width: 50%, height: 50% }.

To address this scenario, you should set:

  • startSize: { x: 10%, y: 10%, width: 50%, height: 50% } – to display the crop settings saved by the user during editing.
  • defaultSize: { x: 0, y: 0, width: 50%, height: 50% } – to be used when a new image is loaded.

How to center crop area

If the x or y coordinates are not specified in the startSize or defaultSize settings, then after the component is initialized, the crop area is automatically centered.

Touch Devices CSS Behavior

On touch devices, the control elements (handles) used to move the crop area are enlarged threefold to provide easier interaction with less precise input methods. If you want to change this behavior, modify the following CSS code:

@media (hover: none) and (pointer: coarse) {
  .truecropper__handle {
      width: 30px;
      height: 30px;
    }
}

This media query applies only to devices that do not support hover and use coarse pointers—a typical characteristic of touch screens. Here, the control points are set to 30px in both width and height, but you can adjust these values to better suit your requirements.

What is epsilon

When running the program, values containing subpixels are generated. The returned value is rounded to whole pixels, which may lead to the image’s aspect ratio (width/height) on the server not matching the expected aspectRatio.

To correct these distortions and other rounding errors when working with floating-point numbers, the following check is used:

if (Math.abs(width / height - aspectRatio) < epsilon) {
  // The value is considered valid
}

Here, epsilon is a small positive value that accounts for calculation inaccuracies. The default value for epsilon is 0.05, but you can adjust it in the options to suit your requirements. This approach ensures that even minor rounding errors in the computed aspect ratio are considered acceptable.

Using the onError function

You can set an onError function for the cropper to handle errors that occur during the initialization or when the image's src is updated. For example:

new TrueCropper("#imageId", {
onError: (instance, error) => {
// some code
}
});

This function is triggered primarily when there are errors in the cropper's initialization or when the image's src is updated. Refer to the documentation for a list of possible errors. For instance, this function can be useful for handling a minSize error if the image is smaller than the specified minSize.

Maximum image height recommendation

By default, the cropper does not have a maximum image height set in the DOM. It is recommended that you set it manually. For example:

.my-image {
  max-height: 300px;
}

Demo

A
W H
W H
x y W H
x y W H

Examples

moveTo, setValue, resizeTo, scaleBy functions

This example illustrates various methods for managing the cropping area.

code

const imageElement = document.querySelector("#example4") as HTMLImageElement;
const cropper = new TrueCropper(imageElement);

document.querySelector("#example4MoveLeft")?.addEventListener("click", () => {
  const currentValue = cropper.getValue();
  cropper.moveTo({ x: currentValue.x - 10, y: currentValue.y });
})
document.querySelector("#example4MoveRight")?.addEventListener("click", () => {
  const currentValue = cropper.getValue();
  cropper.moveTo({ x: currentValue.x + 10, y: currentValue.y });
})
document.querySelector("#example4MoveTop")?.addEventListener("click", () => {
  const currentValue = cropper.getValue();
  cropper.moveTo({ x: currentValue.x, y: currentValue.y - 10 });
})
document.querySelector("#example4MoveBottom")?.addEventListener("click", () => {
  const currentValue = cropper.getValue();
  cropper.moveTo({ x: currentValue.x, y: currentValue.y + 10 });
})

document.querySelector("#example4ResizeLeftTop")?.addEventListener("click", () => {
  const currentValue = cropper.getValue();
  // for example use setValue
  const status = cropper.setValue({ x: currentValue.x - 10, y: currentValue.y - 10, width: currentValue.width + 10, height: currentValue.height + 10 });
  console.log(status);
})
document.querySelector("#example4ResizeLeftBottom")?.addEventListener("click", () => {
  const currentValue = cropper.getValue();
  // for example use setValue
  const status = cropper.setValue({ x: currentValue.x - 10, y: currentValue.y, width: currentValue.width + 10, height: currentValue.height + 10 });
  console.log(status);
})
document.querySelector("#example4ResizeRightTop")?.addEventListener("click", () => {
  const currentValue = cropper.getValue();
  // for example use resizeTo
  const status = cropper.resizeTo({ width: currentValue.width + 10, height: currentValue.height + 10 }, { x: 0, y: 1 });
  console.log(status);
})
document.querySelector("#example4ResizeRightBottom")?.addEventListener("click", () => {
  const currentValue = cropper.getValue();
  // for example use resizeTo
  const status = cropper.resizeTo({ width: currentValue.width + 10, height: currentValue.height + 10 }, { x: 0, y: 0 });
  console.log(status);
})

document.querySelector("#example4DownScale")?.addEventListener("click", () => {
  // { x: 0.5, y: 0.5 } is center
  // { x: 0, y: 1 } is left bottom
  // { x: 1, y: 0 } is right top
  // { x: 0.4, y: 0.3 } is 40% from left, is 30% from top
  const status = cropper.scaleBy(0.5, { x: 0.5, y: 0.5 });
  console.log(status);
})
document.querySelector("#example4UpScale")?.addEventListener("click", () => {
  const status = cropper.scaleBy(2);
  console.log(status);
})

Image preview

This example demonstrates how to implement an image preview feature.

preview
code

const imageElement = document.querySelector("#example5") as HTMLImageElement;
const imageElementPreview = document.querySelector("#examplePreview5") as HTMLImageElement;
const cropper = new TrueCropper(imageElement, {
  onCropEnd: (instance, data) => {
    const canvas = instance.getImagePreview();
    if (canvas) {
      imageElementPreview.src = canvas.toDataURL();
    }
  }
});

Image replace via file input

The cropper supports replacing the image through a file input element. You can attach an event handler to the file input so that when a user selects a new image, it automatically loads into the cropper, replacing the current image.

code
// Select the image element for cropping
const imageElement = document.querySelector("#myImage") as HTMLImageElement;
const cropper = new TrueCropper(imageElement);

// Select the file input element
const fileInput = document.querySelector("#myFileInput");

// Add an event listener for file input change
fileInput?.addEventListener("change", (event) => {
  const target = event.target as HTMLInputElement;
  const files = target.files;

  // Exit if no files are selected
  if (!files || files.length === 0) return;

  const reader = new FileReader();

  reader.onload = (event) => {
    const result = event.target?.result;
    if (!result) return;

    // Set the cropped image source
    cropper.setImage(result.toString());
    // Alternatively, update the image element's source directly
    // imageElement.src = result.toString();
  };

  // Read the selected file as a data URL
  reader.readAsDataURL(files[0]);
});

Modal

This example demonstrates how to change the image using a button, which then opens a modal window where the cropping interface is presented. This approach allows the user to work with the image in a dedicated, isolated interface, improving the editing experience and ensuring that the cropping functionality is easily accessible.

code
// Select the image element for cropping
const imageElement = document.querySelector("#example2") as HTMLImageElement;
const imageElementButton = document.querySelector("#exampleButtonImage2") as HTMLImageElement;
const cropper = new TrueCropper(imageElement, {
  aspectRatio: 1,
  onCropEnd: (instance, data) => {
    const canvas = instance.getImagePreview();
    if(canvas) {
      imageElementButton.src = canvas.toDataURL();
    }
  },
});

imageElementButton.src = imageElement.src;
// Select the file input element
const fileInput = document.querySelector("#exampleInput2");

// Add an event listener for file input change
fileInput?.addEventListener("change", (event) => {
  const target = event.target as HTMLInputElement;
  const files = target.files;

  // Exit if no files are selected
  if (!files || files.length === 0) return;

  const reader = new FileReader();

  reader.onload = (event) => {
    const result = event.target?.result;
    if (!result) return;

    // Set the cropped image source
    cropper.setImage(result.toString());
  };

  // Read the selected file as a data URL
  reader.readAsDataURL(files[0]);
});

onError

This example shows how to use the onError handler to catch errors that occur during the cropper's operation. For instance, if the loaded image is smaller than the specified minSize, the onError function will be triggered. This allows you to implement additional logic, such as displaying an error message or reverting to alternative settings, to handle such cases gracefully.

The minimum size of image is 500x500

code

const errorDiv = document.querySelector("#exampleError3") as HTMLImageElement;
// Select the image element for cropping
const imageElement = document.querySelector("#example3") as HTMLImageElement;
const cropper = new TrueCropper(imageElement, {
  minSize: {
    width: 500,
    height: 500
  },
  onError: (instance, error) => {
    errorDiv.innerText = error.message;
    errorDiv.classList.remove('d-none');
    imageElement.classList.add('d-none');
  }
});

// Select the file input element
const fileInput = document.querySelector("#exampleInput3");

// Add an event listener for file input change
fileInput?.addEventListener("change", (event) => {
  const target = event.target as HTMLInputElement;
  const files = target.files;

  // Exit if no files are selected
  if (!files || files.length === 0) return;

  const reader = new FileReader();

  reader.onload = (event) => {
    const result = event.target?.result;
    if (!result) return;

    errorDiv.innerText = "";
    errorDiv.classList.add('d-none');
    // Set the cropped image source
    // cropper.setImage(result.toString());
    imageElement.src = result.toString();
    imageElement.classList.remove('d-none');
  };

  // Read the selected file as a data URL
  reader.readAsDataURL(files[0]);
});

Options

Configuration options

aspectRatio

Sets a fixed aspect ratio for the crop region.

epsilon

Epsilon is a small positive value that takes into account the inaccuracies in the calculations of the aspect.

maxSize

Defines the maximum allowable dimensions for the crop region.

Note: unit accepts a value of 'real', 'percent', or 'relative'. Defaults to 'real'.

minSize

Specifies the minimum dimensions for the crop region.

Note: unit accepts a value of 'real', 'percent', or 'relative'. Defaults to 'real'.

startSize

Determines the initial crop region when the cropper is first initialized.

Note: unit accepts a value of 'real', 'percent', or 'relative'. Defaults to 'real'.
Note2: If x is not defined, the crop region is centered horizontally. If y is undefined, it is centered vertically.
Note3: If startSize is undefined, for the first initialized used defaultSize.

defaultSize

Specifies the crop region size for subsequent initializations (e.g., after calling instance.setImage()).

Note: unit accepts a value of 'real', 'percent', or 'relative'. Defaults to 'real'.
Note2: If x is undefined, the crop region is centered horizontally. If y is undefined, it is centered vertically.

returnMode

Determines how the crop region values are returned.

allowFlip

Determines whether the crop selection can be flipped horizontally and vertically.

allowNewSelection

Controls whether the user can create a new selection on the image.

allowMove

Enables or disables the ability to move the crop selection around the image.

allowResize

Controls whether the crop selection can be resized.

Callback Functions

onInitialize

Invoked before the cropping interface is rendered.

onInitialize: function(instance, data) {
  // do things here
}
onCropStart

Called when the user begins modifying the crop region.

onCropStart: function(instance, data) {
  console.log(data.x, data.y, data.width, data.height);
}
onCropChange

Fires continuously as the crop region is being adjusted.

onCropChange: function(instance, data) {
  console.log(data.x, data.y, data.width, data.height);
}
onCropEnd

Triggered when the user finishes adjusting the crop region.

onCropEnd: function(instance, data) {
  console.log(data.x, data.y, data.width, data.height);
}
onError

Invoked when an error occurs during initialization or when the image source is updated.

onError: function(instance, data) {
  console.error(data.message);
}

Methods

The following methods enable interaction with the TrueCropper instance:

getValue(returnMode?: string): { x, y, width, height }

Returns the crop region's values, rounded to whole numbers. If no returnMode is specified, it defaults to the option provided during initialization.

const value = cropInstance.getValue();
// value = { x: 21, y: 63, width: 120, height: 120 }

const percentValue = cropInstance.getValue("percent");
// percentValue = { x: 10, y: 30, width: 57, height: 57 }
getImagePreview(): HTMLCanvasElement

Returns a preview of the cropped image.

const canvas = cropper.getImagePreview();
imagePreview.src = canvas.toDataURL();
getImageProps(): { real: { width, height }, relative: { width, height } }

Returns an object with the image's dimensions in both its real and relative sizes: { real: { width, height }, relative: { width, height } }.

If the status is not ready, it returns zero or the previous image values for width and height.

getStatus(): TrueCropperStatus

Retrieves the current status of the TrueCropper instance.

moveTo({ x, y }, mode?): void

Moves the crop region to the specified coordinates.

setValue({ x, y, width, height }, mode?): { ok, message }

Sets the crop region with the specified properties.

resizeTo({ width, height }, { x, y }?, mode?): { ok, message }

Resizes the crop region to the specified dimensions.

The optional { x, y } parameter specifies the origin point for resizing. Defaults to { x: 0.5, y: 0.5 } (center).

scaleBy(factor, { x, y }?, mode?): { ok, message }

Scales the crop region by the given factor.

The optional { x, y } parameter specifies the origin point for resizing. Defaults to { x: 0.5, y: 0.5 } (center).

setImage(src: string)

Changes the image source to the specified URL.

reset()

Resets the crop region to its default position and size.

destroy()

Destroys the TrueCropper instance and restores the original <img> element.


Statuses

TrueCropper.js maintains its current status in the image's dataset, and you can also query it using the getStatus() method. The possible statuses are:


Errors

When an image is initialized or reloaded, TrueCropper.js validates the initialization parameters. If an error occurs, the onError callback is fired (if set); otherwise, the error propagates.

List of possible errors: