Tractor Loader is a webpack loader to help you with your crops and other image adjustments. This loader performs edits to images based on an inline URL syntax. It integrates cleanly with the NextJS optimized image loader and works well with next dev. All transformations are performed at build time and have no impact on runtime performance.
Check the box to view live examples.
Overview
Why?
The NextJS <Image /> documentation recommends using static image imports for local images.
import cat from "./cat.jpg";
This syntax provides a natural location to express image modifications.
import cat from "./cat.jpg?crop=0,300,0,200&tractor";
Installation
Install tractor-loader with your package manager of choice.
npm install tractor-loader
Add it to the image pipeline in next.config.js.
/** @type {import('next').NextConfig} */
const nextConfig = {
webpack: (config, options) => {
config.module.rules.forEach((rule) => {
if (rule.loader === "next-image-loader") {
rule.use = [
{
loader: rule.loader,
options: rule.options,
},
{
loader: "tractor-loader",
},
];
rule.loader = undefined;
rule.options = undefined;
}
});
return config;
},
};
module.exports = nextConfig;
Add a new file tractor.d.ts to satisfy TypeScript.
declare module "*&tractor" {
// StaticImageData from "next/image";
const contents: {
src: string;
height: number;
width: number;
blurDataURL?: string;
blurWidth?: number;
blurHeight?: number;
};
export = contents;
}
MDX
Tractor loader can be used with @next/mdx using the tractor-loader-mdx remark plugin.
npm install tractor-loader-mdx
Edit the next.config.mjs to include the plugin. The source for this site is an example.
import tractorLoaderMDX from "tractor-loader-mdx";
// ...
const withMDX = createMDX({
extension: /.mdx?$/,
options: {
remarkPlugins: [tractorLoaderMDX],
},
});
Then in your components handler, such as mdx-components.tsx, handle TractorLoaderImage.
import type { MDXComponents } from "mdx/types";
import Image from "next/image";
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
TractorLoaderImage: (props) => {
return (
<Image
{...props}
alt={props.alt || ""}
className="my-0"
sizes="(max-width: 56rem) 50vw, 28rem"
/>
);
},
...components,
};
}
Any Tractor Loader syntax in your MDX will be processed. For example:
This is an MDX example:
![img](./cat.jpg?myrotate=145&crop=o-300,o-300,o300,o300&width=200&tractor)
This is an MDX example:
Application
Tractor loader can be applied to any static image import by adding a query string ending in &tractor, such as in the following example. Here 400 px is cropped from the top of the image, then it is coerced to a 16:9 aspect ratio.
import cat from "./cat.jpg?crop=400,0,0,0&aspect=16:9&tractor";
The query string is interpreted as a sequence of operations which are applied from left to right. The key of each key-value pair of the query string identifies an operation, and the value is operation-specific configuration.
Conventions
Units
Default units are pixels. If a '%' sign is used, the unit is a percentage of width or height depending on context.
Image Layout
Tractor loader uses an (x, y) coordinate system where the top-left of the image is (0, 0) and the bottom-right of the image is (width, height). The following shorthand applies to image regions.
code | name | meaning |
---|---|---|
t | top | y coordinate 0 |
b | bottom | y coordinate equal to image height |
l | left | x coordinate 0 |
r | right | x coordinate equal to image width |
w | width | image width |
h | height | image height |
Extension
Tractor loader can be extended by defining new operations or by giving reusable preset names to sequences of operations.
New Operations
New operations must implement a "parse" method that takes the value from the query string and returns a parsed value, and an "apply" method that takes that value, the sharp metadata for the image, and a sharp instance representing the image, and returns a new sharp instance.
export interface Operation {
parse: (v: string) => any;
apply: (
v: any,
metadata: sharp.Metadata,
working: sharp.Sharp,
) => sharp.Sharp;
}
New operations are registered via loader options. For example, here is a registration for an operation to rotate an image.
// ... in next.config.js webpack configuration ...
{
loader: "tractor-loader",
options: {
plugins: {
myrotate: {
parse: (v) => Number(v),
apply: (v, _, working) => working.rotate(v),
}
}
}
},
A registered operation can be used in the query string together with other operations.
Rotate 145 degrees then crop to a 600 by 600 centered region
Registered operations override built-in operations with the same name.
Presets
Common sequences of operations can be named as presets in the webpack configuration.
// ... in next.config.js webpack configuration ...
{
loader: "tractor-loader",
options: {
presets: {
mybanner: "crop=20%,0,20%,0&aspect=16:9",
}
}
},
Then the preset can be applied in the query string. The preset is expanded to its component operations wherever it appears and can be mixed with other presets and operations.
Apply the mybanner preset
Operations
Aspect
Constrains an image to a specific aspect ratio by adjusting one of the image dimensions while leaving the other unmodified. Performs the minimum adjustment necessary to achieve the desired ratio. See sharp resize for available options.
Syntax
aspect=w:h[;option:value]...
w:h aspect ratio to apply
options sharp options
Examples
Apply a 16:9 aspect ratio aligned to the left side
Crop
Crops an image to a target region. This uses a combination of extract and extend.
Syntax
crop=l,t,r,b[,ox,oy][;option:value]...
l,t,r,b left, top, right, bottom
ox,oy x and y for the 'o' origin
options sharp options
Each part is provided as an optional origin, followed by a value, followed by an optional '%' to use percent units. For example, l20% refers to an edge offset 20% of the image width from the left, while b100 refers to an edge offset 100 pixels from the bottom (offset direction is flipped for r and b origins). When an origin is not provided, the default origin for that part is applied.
By default the origin 'o' is the image center point. If ox and oy are provided, they redefine that point.
part | valid origins | default origin |
---|---|---|
l | l, r, o | l |
t | t, b, o | t |
r | l, r, o, w | r |
b | t, b, o, h | b |
ox | l, r | l |
oy | t, b | t |
Examples
Crop 400 pixels from the top and bottom.
Crop to a 200 by 150 region offset 20% from the left and 40% from the top.
Crop to a region 90 px greater than the image size in all directions, filling with green.
Crop to a 220 by 110 region offset 56% from the right and 44% from the top.
Height
Resizes an image to a height while preserving aspect ratio. See sharp resize for available options.
Syntax
height=h[;option:value]...
h height to apply
options sharp options
Examples
Resize to 100 pixels high
Width
Resizes an image to a width while preserving aspect ratio. See sharp resize for available options.
Syntax
width=w[;option:value]...
w width to apply
options sharp options
Examples
Resize to 120 pixels wide with the nearest-neighbor kernel
Image Credits
Cat Photo by Mikhail Vasilyev on Unsplash